@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.
- package/README.md +41 -95
- package/bin/cicd/cli.d.ts +6 -0
- package/bin/cicd/cli.js +128 -0
- package/bin/cicd/form.d.ts +29 -0
- package/bin/cicd/form.js +346 -0
- package/bin/cicd/generators/azure-devops.d.ts +2 -0
- package/bin/cicd/generators/azure-devops.js +370 -0
- package/bin/cicd/generators/bitbucket.d.ts +2 -0
- package/bin/cicd/generators/bitbucket.js +217 -0
- package/bin/cicd/generators/circleci.d.ts +2 -0
- package/bin/cicd/generators/circleci.js +274 -0
- package/bin/cicd/generators/github-actions.d.ts +14 -0
- package/bin/cicd/generators/github-actions.js +426 -0
- package/bin/cicd/generators/gitlab-ci.d.ts +2 -0
- package/bin/cicd/generators/gitlab-ci.js +237 -0
- package/bin/cicd/generators/index.d.ts +6 -0
- package/bin/cicd/generators/index.js +15 -0
- package/bin/cicd/generators/jenkins.d.ts +2 -0
- package/bin/cicd/generators/jenkins.js +248 -0
- package/bin/cicd/generators/template-loader.d.ts +17 -0
- package/bin/cicd/generators/template-loader.js +128 -0
- package/bin/cicd/index.d.ts +1 -0
- package/bin/cicd/index.js +5 -0
- package/bin/cli.d.ts +1 -5
- package/bin/cli.js +72 -7
- package/bin/commands/project.commands.d.ts +19 -6
- package/bin/commands/project.commands.js +602 -66
- package/bin/config/index.d.ts +5 -0
- package/bin/config/index.js +10 -0
- package/bin/config/manager.d.ts +98 -0
- package/bin/config/manager.js +222 -0
- package/bin/containerize/analyzers/bootstrap-analyzer.d.ts +46 -0
- package/bin/containerize/analyzers/bootstrap-analyzer.js +187 -0
- package/bin/containerize/analyzers/project-analyzer.d.ts +20 -0
- package/bin/containerize/analyzers/project-analyzer.js +150 -0
- package/bin/containerize/cli.d.ts +4 -0
- package/bin/containerize/cli.js +113 -0
- package/bin/containerize/form.d.ts +15 -0
- package/bin/containerize/form.js +152 -0
- package/bin/containerize/generators/ci-generator.d.ts +31 -0
- package/bin/containerize/generators/ci-generator.js +940 -0
- package/bin/containerize/generators/docker-compose-generator.d.ts +8 -0
- package/bin/containerize/generators/docker-compose-generator.js +187 -0
- package/bin/containerize/generators/dockerfile-generator.d.ts +8 -0
- package/bin/containerize/generators/dockerfile-generator.js +657 -0
- package/bin/containerize/generators/kubernetes-generator.d.ts +8 -0
- package/bin/containerize/generators/kubernetes-generator.js +134 -0
- package/bin/containerize/generators/template-loader.d.ts +36 -0
- package/bin/containerize/generators/template-loader.js +129 -0
- package/bin/containerize/index.d.ts +4 -0
- package/bin/containerize/index.js +13 -0
- package/bin/containerize/presets/preset-registry.d.ts +20 -0
- package/bin/containerize/presets/preset-registry.js +102 -0
- package/bin/costs/cli.d.ts +5 -0
- package/bin/costs/cli.js +185 -0
- package/bin/costs/form.d.ts +44 -0
- package/bin/costs/form.js +412 -0
- package/bin/costs/index.d.ts +4 -0
- package/bin/costs/index.js +25 -0
- package/bin/costs/pricing-manager.d.ts +84 -0
- package/bin/costs/pricing-manager.js +342 -0
- package/bin/costs/providers/index.d.ts +32 -0
- package/bin/costs/providers/index.js +153 -0
- package/bin/costs/sources/api-source.d.ts +10 -0
- package/bin/costs/sources/api-source.js +32 -0
- package/bin/costs/sources/index.d.ts +6 -0
- package/bin/costs/sources/index.js +15 -0
- package/bin/costs/sources/local-json-source.d.ts +23 -0
- package/bin/costs/sources/local-json-source.js +59 -0
- package/bin/costs/sources/remote-json-source.d.ts +11 -0
- package/bin/costs/sources/remote-json-source.js +53 -0
- package/bin/costs/types.d.ts +53 -0
- package/bin/costs/types.js +5 -0
- package/bin/dev/cli.d.ts +4 -0
- package/bin/dev/cli.js +136 -0
- package/bin/dev/form.d.ts +36 -0
- package/bin/dev/form.js +254 -0
- package/bin/dev/index.d.ts +1 -0
- package/bin/dev/index.js +5 -0
- package/bin/generate/cli.d.ts +1 -1
- package/bin/generate/cli.js +29 -2
- package/bin/generate/form.d.ts +5 -1
- package/bin/generate/form.js +3 -3
- package/bin/generate/templates/nonopinionated/config.tpl +12 -0
- package/bin/generate/templates/nonopinionated/event.tpl +10 -0
- package/bin/generate/templates/nonopinionated/guard.tpl +18 -0
- package/bin/generate/templates/nonopinionated/handler.tpl +12 -0
- package/bin/generate/templates/nonopinionated/interceptor.tpl +27 -0
- package/bin/generate/templates/opinionated/config.tpl +47 -0
- package/bin/generate/templates/opinionated/entity.tpl +1 -8
- package/bin/generate/templates/opinionated/event.tpl +15 -0
- package/bin/generate/templates/opinionated/guard.tpl +41 -0
- package/bin/generate/templates/opinionated/handler.tpl +23 -0
- package/bin/generate/templates/opinionated/interceptor.tpl +50 -0
- package/bin/generate/utils/command-utils.d.ts +20 -5
- package/bin/generate/utils/command-utils.js +145 -48
- package/bin/generate/utils/nonopininated-cmd.d.ts +10 -1
- package/bin/generate/utils/nonopininated-cmd.js +100 -1
- package/bin/generate/utils/opinionated-cmd.d.ts +10 -1
- package/bin/generate/utils/opinionated-cmd.js +128 -16
- package/bin/generate/utils/string-utils.d.ts +6 -0
- package/bin/generate/utils/string-utils.js +13 -1
- 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 -22
- 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/analyzers/platform-detector.d.ts +14 -0
- package/bin/migrate/analyzers/platform-detector.js +116 -0
- package/bin/migrate/cli.d.ts +6 -0
- package/bin/migrate/cli.js +98 -0
- package/bin/migrate/form.d.ts +25 -0
- package/bin/migrate/form.js +348 -0
- package/bin/migrate/generators/compose-to-k8s.d.ts +2 -0
- package/bin/migrate/generators/compose-to-k8s.js +324 -0
- package/bin/migrate/generators/compose-to-railway.d.ts +2 -0
- package/bin/migrate/generators/compose-to-railway.js +138 -0
- package/bin/migrate/generators/compose-to-render.d.ts +2 -0
- package/bin/migrate/generators/compose-to-render.js +148 -0
- package/bin/migrate/generators/generic-migration.d.ts +9 -0
- package/bin/migrate/generators/generic-migration.js +221 -0
- package/bin/migrate/generators/heroku-to-fly.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-fly.js +291 -0
- package/bin/migrate/generators/heroku-to-railway.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-railway.js +283 -0
- package/bin/migrate/generators/heroku-to-render.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-render.js +148 -0
- package/bin/migrate/generators/index.d.ts +7 -0
- package/bin/migrate/generators/index.js +17 -0
- package/bin/migrate/generators/template-loader.d.ts +21 -0
- package/bin/migrate/generators/template-loader.js +59 -0
- package/bin/migrate/index.d.ts +1 -0
- package/bin/migrate/index.js +5 -0
- package/bin/new/cli.d.ts +5 -1
- package/bin/new/cli.js +77 -14
- package/bin/new/form.d.ts +27 -4
- package/bin/new/form.js +605 -75
- package/bin/profile/analyzers/dockerfile-analyzer.d.ts +27 -0
- package/bin/profile/analyzers/dockerfile-analyzer.js +122 -0
- package/bin/profile/analyzers/image-analyzer.d.ts +19 -0
- package/bin/profile/analyzers/image-analyzer.js +85 -0
- package/bin/profile/cli.d.ts +4 -0
- package/bin/profile/cli.js +94 -0
- package/bin/profile/form.d.ts +56 -0
- package/bin/profile/form.js +401 -0
- package/bin/profile/index.d.ts +1 -0
- package/bin/profile/index.js +5 -0
- package/bin/profile/optimizers/index.d.ts +19 -0
- package/bin/profile/optimizers/index.js +137 -0
- package/bin/providers/add/form.d.ts +1 -1
- package/bin/providers/add/form.js +27 -6
- package/bin/providers/create/form.js +53 -3
- package/bin/scripts/form.js +27 -5
- package/bin/studio/cli.d.ts +15 -0
- package/bin/studio/cli.js +172 -0
- package/bin/studio/index.d.ts +5 -0
- package/bin/studio/index.js +9 -0
- package/bin/templates/cache.d.ts +54 -0
- package/bin/templates/cache.js +180 -0
- package/bin/templates/cli.d.ts +8 -0
- package/bin/templates/cli.js +294 -0
- package/bin/templates/fetcher.d.ts +49 -0
- package/bin/templates/fetcher.js +208 -0
- package/bin/templates/index.d.ts +11 -0
- package/bin/templates/index.js +37 -0
- package/bin/templates/manager.d.ts +116 -0
- package/bin/templates/manager.js +323 -0
- package/bin/templates/renderer.d.ts +49 -0
- package/bin/templates/renderer.js +204 -0
- package/bin/templates/types.d.ts +51 -0
- package/bin/templates/types.js +5 -0
- package/bin/utils/add-module-to-container.d.ts +14 -3
- package/bin/utils/add-module-to-container.js +327 -98
- package/bin/utils/cli-ui.d.ts +49 -3
- package/bin/utils/cli-ui.js +133 -13
- package/bin/utils/index.d.ts +4 -0
- package/bin/utils/index.js +4 -0
- package/bin/utils/input-validation.d.ts +50 -0
- package/bin/utils/input-validation.js +143 -0
- package/bin/utils/package-manager-commands.d.ts +24 -0
- package/bin/utils/package-manager-commands.js +50 -0
- package/bin/utils/safe-spawn.d.ts +35 -0
- package/bin/utils/safe-spawn.js +51 -0
- package/bin/utils/update-tsconfig-paths.d.ts +35 -0
- package/bin/utils/update-tsconfig-paths.js +326 -0
- package/package.json +165 -156
|
@@ -31,10 +31,100 @@ const child_process_1 = require("child_process");
|
|
|
31
31
|
const fs_1 = require("fs");
|
|
32
32
|
const os_1 = __importDefault(require("os"));
|
|
33
33
|
const path_1 = __importStar(require("path"));
|
|
34
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
34
35
|
const cli_ui_1 = require("../utils/cli-ui");
|
|
35
36
|
const compiler_1 = __importDefault(require("../utils/compiler"));
|
|
37
|
+
const safe_spawn_1 = require("../utils/safe-spawn");
|
|
36
38
|
/**
|
|
37
|
-
*
|
|
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.
|
|
38
128
|
*/
|
|
39
129
|
function getOutDir() {
|
|
40
130
|
const tsconfigBuildPath = (0, path_1.join)(process.cwd(), "tsconfig.build.json");
|
|
@@ -42,10 +132,11 @@ function getOutDir() {
|
|
|
42
132
|
(0, cli_ui_1.printError)("Cannot find tsconfig.build.json. Please create one in the root directory", "tsconfig-build-path");
|
|
43
133
|
process.exit(1);
|
|
44
134
|
}
|
|
45
|
-
const tsconfig =
|
|
46
|
-
const
|
|
135
|
+
const tsconfig = resolveTsConfig(tsconfigBuildPath);
|
|
136
|
+
const opts = tsconfig.compilerOptions;
|
|
137
|
+
const outDir = opts?.outDir;
|
|
47
138
|
if (!outDir) {
|
|
48
|
-
(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");
|
|
49
140
|
process.exit(1);
|
|
50
141
|
}
|
|
51
142
|
if (!(0, fs_1.existsSync)(outDir)) {
|
|
@@ -55,40 +146,63 @@ function getOutDir() {
|
|
|
55
146
|
return outDir;
|
|
56
147
|
}
|
|
57
148
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const config = [
|
|
65
|
-
"--watch",
|
|
66
|
-
"-r",
|
|
67
|
-
"tsconfig-paths/register",
|
|
68
|
-
`./src/${entryPoint}.ts`,
|
|
69
|
-
];
|
|
70
|
-
return config;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Load the configuration from the compiler
|
|
74
|
-
* @param compiler The compiler to load the configuration from
|
|
75
|
-
* @returns The configuration
|
|
149
|
+
* Build the tsx watch arguments for development mode.
|
|
150
|
+
* Uses tsx's built-in --watch flag for reliable cross-platform file watching
|
|
151
|
+
* (avoids nodemon + SIGTERM issues on Windows).
|
|
152
|
+
*
|
|
153
|
+
* @param opinionated - Whether to use opinionated configuration
|
|
154
|
+
* @returns The tsx arguments array
|
|
76
155
|
*/
|
|
77
|
-
async function
|
|
78
|
-
const { entryPoint } = await compiler_1.default.loadConfig();
|
|
79
|
-
const
|
|
80
|
-
|
|
156
|
+
async function buildDevArgs(opinionated) {
|
|
157
|
+
const { entryPoint, sourceRoot } = await compiler_1.default.loadConfig();
|
|
158
|
+
const args = ["--watch"];
|
|
159
|
+
if (opinionated) {
|
|
160
|
+
args.push("-r", "tsconfig-paths/register");
|
|
161
|
+
}
|
|
162
|
+
// Honor `sourceRoot` from expressots.config.ts so projects whose
|
|
163
|
+
// source lives under a non-default folder (e.g. `api/` or
|
|
164
|
+
// `services/`) still resolve their entry point correctly.
|
|
165
|
+
args.push(`./${sourceRoot}/${entryPoint}.ts`);
|
|
166
|
+
return args;
|
|
81
167
|
}
|
|
82
168
|
/**
|
|
83
169
|
* Dev command module
|
|
84
|
-
* @type {CommandModule<object,
|
|
170
|
+
* @type {CommandModule<object, DevCommandOptions>}
|
|
85
171
|
* @returns The command module
|
|
86
172
|
*/
|
|
87
173
|
exports.devCommand = {
|
|
88
174
|
command: "dev",
|
|
89
175
|
describe: "Start development server.",
|
|
90
|
-
|
|
91
|
-
|
|
176
|
+
builder: {
|
|
177
|
+
container: {
|
|
178
|
+
alias: "c",
|
|
179
|
+
type: "boolean",
|
|
180
|
+
default: false,
|
|
181
|
+
description: "Run development inside Docker container",
|
|
182
|
+
},
|
|
183
|
+
build: {
|
|
184
|
+
alias: "b",
|
|
185
|
+
type: "boolean",
|
|
186
|
+
default: false,
|
|
187
|
+
description: "Rebuild container before starting (with --container)",
|
|
188
|
+
},
|
|
189
|
+
detach: {
|
|
190
|
+
alias: "d",
|
|
191
|
+
type: "boolean",
|
|
192
|
+
default: false,
|
|
193
|
+
description: "Run container in background (with --container)",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
handler: async (argv) => {
|
|
197
|
+
if (argv.container) {
|
|
198
|
+
await runContainerDev({
|
|
199
|
+
build: argv.build ?? false,
|
|
200
|
+
detach: argv.detach ?? false,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
await (0, exports.runCommand)({ command: "dev" });
|
|
205
|
+
}
|
|
92
206
|
},
|
|
93
207
|
};
|
|
94
208
|
/**
|
|
@@ -115,6 +229,86 @@ exports.prodCommand = {
|
|
|
115
229
|
await (0, exports.runCommand)({ command: "prod" });
|
|
116
230
|
},
|
|
117
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
|
+
}
|
|
118
312
|
/**
|
|
119
313
|
* Helper function to execute a command
|
|
120
314
|
* @param command The command to execute
|
|
@@ -124,16 +318,68 @@ exports.prodCommand = {
|
|
|
124
318
|
*/
|
|
125
319
|
function execCmd(command, args, cwd = process.cwd()) {
|
|
126
320
|
return new Promise((resolve, reject) => {
|
|
127
|
-
|
|
321
|
+
// `safeSpawn` (cross-spawn) resolves Windows `.cmd` shims (npx,
|
|
322
|
+
// tsx, tsc, etc.) via PATHEXT and applies cmd.exe-aware escaping
|
|
323
|
+
// for every argv entry, while falling through to plain `spawn`
|
|
324
|
+
// with `shell: false` on Unix.
|
|
325
|
+
const proc = (0, safe_spawn_1.safeSpawn)(command, args, {
|
|
128
326
|
stdio: "inherit",
|
|
129
|
-
shell: true,
|
|
130
327
|
cwd,
|
|
131
328
|
});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
+
}
|
|
135
380
|
}
|
|
136
381
|
else {
|
|
382
|
+
cleanup();
|
|
137
383
|
reject(new Error(`Command failed with code ${code}`));
|
|
138
384
|
}
|
|
139
385
|
});
|
|
@@ -153,47 +399,340 @@ const compileTypescript = async () => {
|
|
|
153
399
|
await execCmd("npx", ["tsc", "-p", "tsconfig.build.json"]);
|
|
154
400
|
(0, cli_ui_1.printSuccess)("Built successfully", "compile-typescript");
|
|
155
401
|
};
|
|
402
|
+
/**
|
|
403
|
+
* Transform path aliases to relative paths in compiled JavaScript files.
|
|
404
|
+
* This runs after TypeScript compilation to ensure production builds work
|
|
405
|
+
* without runtime path resolution.
|
|
406
|
+
*
|
|
407
|
+
* @param outDir - The output directory (e.g., "./dist")
|
|
408
|
+
*/
|
|
409
|
+
const transformPathAliases = async (outDir) => {
|
|
410
|
+
const tsconfigPath = (0, path_1.join)(process.cwd(), "tsconfig.build.json");
|
|
411
|
+
if (!(0, fs_1.existsSync)(tsconfigPath)) {
|
|
412
|
+
return; // No tsconfig.build.json, skip transformation
|
|
413
|
+
}
|
|
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) {
|
|
422
|
+
return; // No path aliases defined, skip
|
|
423
|
+
}
|
|
424
|
+
// Build regex patterns for each alias
|
|
425
|
+
const aliasPatterns = [];
|
|
426
|
+
for (const [alias, targets] of Object.entries(paths)) {
|
|
427
|
+
if (!Array.isArray(targets) || targets.length === 0)
|
|
428
|
+
continue;
|
|
429
|
+
// Convert @alias/* to regex pattern
|
|
430
|
+
// Matches: require("@alias/something") or require('@alias/something')
|
|
431
|
+
const aliasBase = alias.replace("/*", "");
|
|
432
|
+
const targetBase = targets[0].replace("/*", "");
|
|
433
|
+
// Pattern to match require("@alias/...") or require('@alias/...')
|
|
434
|
+
const pattern = new RegExp(`require\\(["']${aliasBase.replace("@", "\\@")}/([^"']+)["']\\)`, "g");
|
|
435
|
+
aliasPatterns.push({
|
|
436
|
+
pattern,
|
|
437
|
+
alias: aliasBase,
|
|
438
|
+
target: targetBase,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
if (aliasPatterns.length === 0) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
// Recursively find all .js files in outDir
|
|
445
|
+
const findJsFiles = async (dir) => {
|
|
446
|
+
const files = [];
|
|
447
|
+
const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
|
|
448
|
+
for (const entry of entries) {
|
|
449
|
+
const fullPath = (0, path_1.join)(dir, entry.name);
|
|
450
|
+
if (entry.isDirectory()) {
|
|
451
|
+
files.push(...(await findJsFiles(fullPath)));
|
|
452
|
+
}
|
|
453
|
+
else if (entry.name.endsWith(".js")) {
|
|
454
|
+
files.push(fullPath);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return files;
|
|
458
|
+
};
|
|
459
|
+
const jsFiles = await findJsFiles(outDir);
|
|
460
|
+
let transformedCount = 0;
|
|
461
|
+
for (const file of jsFiles) {
|
|
462
|
+
let content = await fs_1.promises.readFile(file, "utf-8");
|
|
463
|
+
let modified = false;
|
|
464
|
+
// Get the directory of the current file relative to outDir
|
|
465
|
+
const fileDir = path_1.default.dirname(file);
|
|
466
|
+
for (const { pattern, alias, target } of aliasPatterns) {
|
|
467
|
+
// Calculate the relative path from this file to the target
|
|
468
|
+
const targetDir = (0, path_1.join)(outDir, baseUrl.replace("./", ""), target);
|
|
469
|
+
let relativePath = path_1.default.relative(fileDir, targetDir);
|
|
470
|
+
// Ensure it starts with ./ or ../
|
|
471
|
+
if (!relativePath.startsWith(".")) {
|
|
472
|
+
relativePath = "./" + relativePath;
|
|
473
|
+
}
|
|
474
|
+
// Replace Windows backslashes with forward slashes
|
|
475
|
+
relativePath = relativePath.replace(/\\/g, "/");
|
|
476
|
+
// Replace the alias with the relative path
|
|
477
|
+
const newContent = content.replace(pattern, (match, subPath) => {
|
|
478
|
+
modified = true;
|
|
479
|
+
return `require("${relativePath}/${subPath}")`;
|
|
480
|
+
});
|
|
481
|
+
if (newContent !== content) {
|
|
482
|
+
content = newContent;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (modified) {
|
|
486
|
+
await fs_1.promises.writeFile(file, content, "utf-8");
|
|
487
|
+
transformedCount++;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (transformedCount > 0) {
|
|
491
|
+
(0, cli_ui_1.printSuccess)(`Path aliases resolved in ${transformedCount} files`, "transform-paths");
|
|
492
|
+
}
|
|
493
|
+
};
|
|
156
494
|
/**
|
|
157
495
|
* Helper function to copy files to the dist directory
|
|
158
496
|
*/
|
|
159
497
|
const copyFiles = async (outDir) => {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
];
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
filesToCopy = ["tsconfig.json", "package.json"];
|
|
498
|
+
// Only copy package.json - path aliases are resolved at build time
|
|
499
|
+
// No need for tsconfig files or register-path.js in production
|
|
500
|
+
const filesToCopy = ["package.json"];
|
|
501
|
+
for (const file of filesToCopy) {
|
|
502
|
+
if ((0, fs_1.existsSync)(file)) {
|
|
503
|
+
await fs_1.promises.copyFile(file, (0, path_1.join)(outDir, path_1.default.basename(file)));
|
|
504
|
+
}
|
|
171
505
|
}
|
|
172
|
-
filesToCopy.forEach((file) => {
|
|
173
|
-
fs_1.promises.copyFile(file, (0, path_1.join)(outDir, path_1.default.basename(file)));
|
|
174
|
-
});
|
|
175
506
|
};
|
|
176
507
|
/**
|
|
177
508
|
* Helper function to clear the screen
|
|
178
509
|
*/
|
|
179
510
|
const clearScreen = () => {
|
|
511
|
+
// `cls` and `clear` are built-ins / well-known executables.
|
|
512
|
+
// Invoking them via `shell: true` is safe here because there are no
|
|
513
|
+
// user-controlled args, but we keep `windowsHide: true` to suppress
|
|
514
|
+
// the Windows console flash.
|
|
180
515
|
const platform = os_1.default.platform();
|
|
181
516
|
const command = platform === "win32" ? "cls" : "clear";
|
|
182
|
-
(0, child_process_1.spawn)(command, { stdio: "inherit", shell: true });
|
|
517
|
+
(0, child_process_1.spawn)(command, [], { stdio: "inherit", shell: true, windowsHide: true });
|
|
183
518
|
};
|
|
519
|
+
/**
|
|
520
|
+
* Run development in Docker container with auto-setup
|
|
521
|
+
* This is the seamless "just works" experience
|
|
522
|
+
*/
|
|
523
|
+
async function runContainerDev(options) {
|
|
524
|
+
console.log(chalk_1.default.cyan("\nš³ ExpressoTS Container Development\n"));
|
|
525
|
+
const cwd = process.cwd();
|
|
526
|
+
const composeDevFile = (0, path_1.join)(cwd, "docker-compose.development.yml");
|
|
527
|
+
const dockerfileDevFile = (0, path_1.join)(cwd, "Dockerfile.development");
|
|
528
|
+
const dockerSetupFile = (0, path_1.join)(cwd, "docker-setup.js");
|
|
529
|
+
const dockerDepsDir = (0, path_1.join)(cwd, ".docker-deps");
|
|
530
|
+
const packageDockerJson = (0, path_1.join)(cwd, "package.docker.json");
|
|
531
|
+
// Check if Docker is running
|
|
532
|
+
if (!isDockerRunning()) {
|
|
533
|
+
console.log(chalk_1.default.red("ā Docker is not running."));
|
|
534
|
+
console.log(chalk_1.default.gray(" Please start Docker Desktop or Docker daemon."));
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
// Step 1: Auto-generate Docker files if missing
|
|
538
|
+
if (!(0, fs_1.existsSync)(dockerfileDevFile) || !(0, fs_1.existsSync)(composeDevFile)) {
|
|
539
|
+
console.log(chalk_1.default.yellow("š Docker files not found. Generating..."));
|
|
540
|
+
try {
|
|
541
|
+
// Import and run containerize
|
|
542
|
+
const { containerizeProject } = await Promise.resolve().then(() => __importStar(require("../containerize/form")));
|
|
543
|
+
await containerizeProject({
|
|
544
|
+
target: "docker",
|
|
545
|
+
environment: "development",
|
|
546
|
+
preset: "standard",
|
|
547
|
+
analyze: true,
|
|
548
|
+
skipCompose: false,
|
|
549
|
+
includeCi: false,
|
|
550
|
+
});
|
|
551
|
+
console.log();
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
console.log(chalk_1.default.red("ā Failed to generate Docker files."));
|
|
555
|
+
console.log(chalk_1.default.gray(" Run manually: expressots containerize docker --env development"));
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// Step 1.5: Check bootstrap config and create missing env files if needed
|
|
560
|
+
try {
|
|
561
|
+
const { analyzeBootstrapConfig, shouldCopyEnvFiles, getEnvFileForEnvironment, } = await Promise.resolve().then(() => __importStar(require("../containerize/analyzers/bootstrap-analyzer")));
|
|
562
|
+
const bootstrapConfig = await analyzeBootstrapConfig();
|
|
563
|
+
if (bootstrapConfig.hasEnvFileConfig &&
|
|
564
|
+
shouldCopyEnvFiles(bootstrapConfig)) {
|
|
565
|
+
const devEnvFile = getEnvFileForEnvironment(bootstrapConfig, "development");
|
|
566
|
+
// Check if required env file is missing
|
|
567
|
+
if (bootstrapConfig.missingEnvFiles.includes(devEnvFile)) {
|
|
568
|
+
console.log(chalk_1.default.yellow(`ā ļø Required env file missing: ${devEnvFile}`));
|
|
569
|
+
// Auto-create template if configured or prompt user
|
|
570
|
+
if (bootstrapConfig.autoCreateTemplate) {
|
|
571
|
+
console.log(chalk_1.default.gray(` Creating template ${devEnvFile}...`));
|
|
572
|
+
await createEnvTemplate(cwd, devEnvFile, "development", bootstrapConfig.requiredVariables);
|
|
573
|
+
console.log(chalk_1.default.green(` ā Created ${devEnvFile}`));
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
// Provide helpful instructions
|
|
577
|
+
console.log(chalk_1.default.cyan("\nš” To fix this, either:"));
|
|
578
|
+
console.log(chalk_1.default.gray(` 1. Create ${devEnvFile} with your environment variables`));
|
|
579
|
+
console.log(chalk_1.default.gray(` 2. Add autoCreateTemplate: true to envFileConfig in bootstrap`));
|
|
580
|
+
console.log(chalk_1.default.gray(` 3. Use skipFileLoading: true for container deployments`));
|
|
581
|
+
console.log();
|
|
582
|
+
// Still continue - the container might work if env vars are set in docker-compose
|
|
583
|
+
console.log(chalk_1.default.yellow(` ā ļø Container may fail if ${devEnvFile} is required`));
|
|
584
|
+
console.log();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
// Show required variables that need to be set
|
|
588
|
+
if (bootstrapConfig.requiredVariables.length > 0) {
|
|
589
|
+
console.log(chalk_1.default.cyan("š Required environment variables:"));
|
|
590
|
+
bootstrapConfig.requiredVariables.forEach((varName) => {
|
|
591
|
+
console.log(chalk_1.default.gray(` ⢠${varName}`));
|
|
592
|
+
});
|
|
593
|
+
console.log(chalk_1.default.gray(` Set these in ${devEnvFile} or docker-compose.development.yml`));
|
|
594
|
+
console.log();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
// Non-fatal - continue with container startup
|
|
600
|
+
console.log(chalk_1.default.gray(" (Bootstrap analysis skipped)"));
|
|
601
|
+
}
|
|
602
|
+
// Step 2: Auto-run docker:setup if local dependencies exist
|
|
603
|
+
if ((0, fs_1.existsSync)(packageDockerJson) && (0, fs_1.existsSync)(dockerSetupFile)) {
|
|
604
|
+
// Check if .docker-deps needs to be updated
|
|
605
|
+
const needsSetup = !(0, fs_1.existsSync)(dockerDepsDir) || isDirEmpty(dockerDepsDir);
|
|
606
|
+
if (needsSetup) {
|
|
607
|
+
console.log(chalk_1.default.yellow("š¦ Setting up local dependencies..."));
|
|
608
|
+
try {
|
|
609
|
+
const setupResult = (0, safe_spawn_1.safeSpawnSync)(process.execPath, ["docker-setup.js"], {
|
|
610
|
+
cwd,
|
|
611
|
+
stdio: "inherit",
|
|
612
|
+
encoding: "utf-8",
|
|
613
|
+
});
|
|
614
|
+
if (setupResult.error)
|
|
615
|
+
throw setupResult.error;
|
|
616
|
+
if (typeof setupResult.status === "number" &&
|
|
617
|
+
setupResult.status !== 0) {
|
|
618
|
+
throw new Error(`exited with code ${setupResult.status}`);
|
|
619
|
+
}
|
|
620
|
+
console.log();
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
console.log(chalk_1.default.red("ā Failed to setup local dependencies."));
|
|
624
|
+
console.log(chalk_1.default.gray(" Run manually: npm run docker:setup"));
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// Step 3: Start the containers
|
|
630
|
+
console.log(chalk_1.default.yellow(`š Using docker-compose.development.yml`));
|
|
631
|
+
const args = ["-f", composeDevFile, "up"];
|
|
632
|
+
if (options.build) {
|
|
633
|
+
console.log(chalk_1.default.yellow("šØ Rebuilding containers..."));
|
|
634
|
+
args.splice(2, 0, "--build");
|
|
635
|
+
}
|
|
636
|
+
if (options.detach) {
|
|
637
|
+
args.push("-d");
|
|
638
|
+
}
|
|
639
|
+
console.log(chalk_1.default.yellow("š Starting development containers...\n"));
|
|
640
|
+
// Print dev info
|
|
641
|
+
console.log(chalk_1.default.bold("Development Environment:"));
|
|
642
|
+
console.log(` š App: http://localhost:3000`);
|
|
643
|
+
console.log(` š Debug: localhost:9229`);
|
|
644
|
+
console.log();
|
|
645
|
+
console.log(chalk_1.default.bold("Commands:"));
|
|
646
|
+
console.log(` ${chalk_1.default.gray("expressots dev -c")} Start containers`);
|
|
647
|
+
console.log(` ${chalk_1.default.gray("expressots dev -c -b")} Rebuild & start`);
|
|
648
|
+
console.log(` ${chalk_1.default.gray("expressots dev -c -d")} Start in background`);
|
|
649
|
+
console.log(` ${chalk_1.default.gray("docker-compose -f docker-compose.development.yml down")} Stop`);
|
|
650
|
+
console.log();
|
|
651
|
+
console.log(chalk_1.default.green("š Hot reload enabled - edit files to see changes"));
|
|
652
|
+
if (!options.detach) {
|
|
653
|
+
console.log(chalk_1.default.gray("Press Ctrl+C to stop\n"));
|
|
654
|
+
}
|
|
655
|
+
// Run docker-compose
|
|
656
|
+
runDockerComposeCommand(args, cwd, options.detach);
|
|
657
|
+
if (options.detach) {
|
|
658
|
+
console.log(chalk_1.default.green("\nā
Containers started in background."));
|
|
659
|
+
console.log(chalk_1.default.gray(" View logs: docker-compose -f docker-compose.development.yml logs -f"));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Check if Docker is running
|
|
664
|
+
*/
|
|
665
|
+
function isDockerRunning() {
|
|
666
|
+
const result = (0, safe_spawn_1.safeSpawnSync)("docker", ["info"], {
|
|
667
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
668
|
+
});
|
|
669
|
+
return !result.error && result.status === 0;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Check if directory is empty
|
|
673
|
+
*/
|
|
674
|
+
function isDirEmpty(dir) {
|
|
675
|
+
try {
|
|
676
|
+
const files = (0, fs_1.readdirSync)(dir);
|
|
677
|
+
return files.length === 0;
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Create an environment template file
|
|
685
|
+
*/
|
|
686
|
+
async function createEnvTemplate(cwd, fileName, environment, requiredVariables) {
|
|
687
|
+
const filePath = (0, path_1.join)(cwd, fileName);
|
|
688
|
+
const commonVars = [
|
|
689
|
+
"PORT=3000",
|
|
690
|
+
`NODE_ENV=${environment}`,
|
|
691
|
+
"# Add your environment variables below",
|
|
692
|
+
];
|
|
693
|
+
const requiredVars = requiredVariables.map((key) => `${key}=`);
|
|
694
|
+
const template = [...commonVars, ...requiredVars].join("\n");
|
|
695
|
+
await fs_1.promises.writeFile(filePath, template, "utf-8");
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Run docker-compose command
|
|
699
|
+
*/
|
|
700
|
+
function runDockerComposeCommand(args, cwd, detach) {
|
|
701
|
+
// Try docker compose (v2) first, fall back to docker-compose (v1).
|
|
702
|
+
// `safeSpawnSync` (cross-spawn) handles platform-specific binary
|
|
703
|
+
// resolution and cmd.exe-aware argv escaping, so compose file paths
|
|
704
|
+
// and service names are forwarded as discrete arguments rather than
|
|
705
|
+
// re-interpreted by the OS shell.
|
|
706
|
+
const v2 = (0, safe_spawn_1.safeSpawnSync)("docker", ["compose", ...args], {
|
|
707
|
+
cwd,
|
|
708
|
+
stdio: "inherit",
|
|
709
|
+
});
|
|
710
|
+
if (v2.error || (typeof v2.status === "number" && v2.status !== 0)) {
|
|
711
|
+
const v1 = (0, safe_spawn_1.safeSpawnSync)("docker-compose", args, {
|
|
712
|
+
cwd,
|
|
713
|
+
stdio: "inherit",
|
|
714
|
+
});
|
|
715
|
+
if (v1.error || (typeof v1.status === "number" && v1.status !== 0)) {
|
|
716
|
+
console.log(chalk_1.default.red("Error running docker-compose"));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
// `detach` is honored implicitly by docker compose itself when "-d"
|
|
720
|
+
// is present in `args`. The previous branch tried to switch between
|
|
721
|
+
// execSync and spawnSync for that case, which served no real
|
|
722
|
+
// purpose and made shell injection harder to reason about.
|
|
723
|
+
void detach;
|
|
724
|
+
}
|
|
184
725
|
/**
|
|
185
726
|
* Helper function to run a command
|
|
186
|
-
* @param
|
|
727
|
+
* @param options The command options
|
|
187
728
|
*/
|
|
188
729
|
const runCommand = async ({ command, }) => {
|
|
189
|
-
const { opinionated, entryPoint } = await compiler_1.default.loadConfig();
|
|
730
|
+
const { opinionated, entryPoint, sourceRoot } = await compiler_1.default.loadConfig();
|
|
190
731
|
const outDir = getOutDir();
|
|
191
732
|
try {
|
|
192
733
|
switch (command) {
|
|
193
734
|
case "dev":
|
|
194
|
-
execCmd("tsx", opinionated
|
|
195
|
-
? await opinionatedConfig()
|
|
196
|
-
: await nonOpinionatedConfig());
|
|
735
|
+
await execCmd("tsx", await buildDevArgs(opinionated));
|
|
197
736
|
break;
|
|
198
737
|
case "build":
|
|
199
738
|
if (!outDir) {
|
|
@@ -202,6 +741,10 @@ const runCommand = async ({ command, }) => {
|
|
|
202
741
|
}
|
|
203
742
|
await cleanDist(outDir);
|
|
204
743
|
await compileTypescript();
|
|
744
|
+
// Transform path aliases to relative paths for production
|
|
745
|
+
if (opinionated) {
|
|
746
|
+
await transformPathAliases(outDir);
|
|
747
|
+
}
|
|
205
748
|
await copyFiles(outDir);
|
|
206
749
|
break;
|
|
207
750
|
case "prod": {
|
|
@@ -209,19 +752,12 @@ const runCommand = async ({ command, }) => {
|
|
|
209
752
|
(0, cli_ui_1.printError)("Cannot run in prod mode. Please provide an outDir in tsconfig.build.json", "prod-command");
|
|
210
753
|
process.exit(1);
|
|
211
754
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
`./${outDir}/register-path.js`,
|
|
217
|
-
`./${outDir}/src/${entryPoint}.js`,
|
|
218
|
-
];
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
config = [`./${outDir}/${entryPoint}.js`];
|
|
222
|
-
}
|
|
755
|
+
// Honor `sourceRoot` so the compiled entry-point path
|
|
756
|
+
// matches whatever folder the user configured (the TS
|
|
757
|
+
// compiler preserves the source tree under `outDir`).
|
|
758
|
+
const config = [`./${outDir}/${sourceRoot}/${entryPoint}.js`];
|
|
223
759
|
clearScreen();
|
|
224
|
-
execCmd("node", config);
|
|
760
|
+
await execCmd("node", config);
|
|
225
761
|
break;
|
|
226
762
|
}
|
|
227
763
|
default:
|