@expressots/cli 3.0.0 → 4.0.0-preview.2

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 (180) hide show
  1. package/README.md +41 -95
  2. package/bin/cicd/cli.d.ts +6 -0
  3. package/bin/cicd/cli.js +126 -0
  4. package/bin/cicd/form.d.ts +29 -0
  5. package/bin/cicd/form.js +345 -0
  6. package/bin/cicd/generators/azure-devops.d.ts +2 -0
  7. package/bin/cicd/generators/azure-devops.js +370 -0
  8. package/bin/cicd/generators/bitbucket.d.ts +2 -0
  9. package/bin/cicd/generators/bitbucket.js +217 -0
  10. package/bin/cicd/generators/circleci.d.ts +2 -0
  11. package/bin/cicd/generators/circleci.js +274 -0
  12. package/bin/cicd/generators/github-actions.d.ts +14 -0
  13. package/bin/cicd/generators/github-actions.js +426 -0
  14. package/bin/cicd/generators/gitlab-ci.d.ts +2 -0
  15. package/bin/cicd/generators/gitlab-ci.js +237 -0
  16. package/bin/cicd/generators/index.d.ts +6 -0
  17. package/bin/cicd/generators/index.js +15 -0
  18. package/bin/cicd/generators/jenkins.d.ts +2 -0
  19. package/bin/cicd/generators/jenkins.js +248 -0
  20. package/bin/cicd/generators/template-loader.d.ts +17 -0
  21. package/bin/cicd/generators/template-loader.js +128 -0
  22. package/bin/cicd/index.d.ts +1 -0
  23. package/bin/cicd/index.js +5 -0
  24. package/bin/cli.d.ts +1 -1
  25. package/bin/cli.js +18 -3
  26. package/bin/commands/project.commands.d.ts +19 -6
  27. package/bin/commands/project.commands.js +390 -61
  28. package/bin/config/index.d.ts +5 -0
  29. package/bin/config/index.js +10 -0
  30. package/bin/config/manager.d.ts +98 -0
  31. package/bin/config/manager.js +222 -0
  32. package/bin/containerize/analyzers/bootstrap-analyzer.d.ts +46 -0
  33. package/bin/containerize/analyzers/bootstrap-analyzer.js +187 -0
  34. package/bin/containerize/analyzers/project-analyzer.d.ts +20 -0
  35. package/bin/containerize/analyzers/project-analyzer.js +150 -0
  36. package/bin/containerize/cli.d.ts +4 -0
  37. package/bin/containerize/cli.js +113 -0
  38. package/bin/containerize/form.d.ts +15 -0
  39. package/bin/containerize/form.js +154 -0
  40. package/bin/containerize/generators/ci-generator.d.ts +31 -0
  41. package/bin/containerize/generators/ci-generator.js +936 -0
  42. package/bin/containerize/generators/docker-compose-generator.d.ts +8 -0
  43. package/bin/containerize/generators/docker-compose-generator.js +186 -0
  44. package/bin/containerize/generators/dockerfile-generator.d.ts +8 -0
  45. package/bin/containerize/generators/dockerfile-generator.js +635 -0
  46. package/bin/containerize/generators/kubernetes-generator.d.ts +8 -0
  47. package/bin/containerize/generators/kubernetes-generator.js +133 -0
  48. package/bin/containerize/generators/template-loader.d.ts +36 -0
  49. package/bin/containerize/generators/template-loader.js +129 -0
  50. package/bin/containerize/index.d.ts +4 -0
  51. package/bin/containerize/index.js +13 -0
  52. package/bin/containerize/presets/preset-registry.d.ts +20 -0
  53. package/bin/containerize/presets/preset-registry.js +102 -0
  54. package/bin/costs/cli.d.ts +5 -0
  55. package/bin/costs/cli.js +183 -0
  56. package/bin/costs/form.d.ts +44 -0
  57. package/bin/costs/form.js +412 -0
  58. package/bin/costs/index.d.ts +4 -0
  59. package/bin/costs/index.js +25 -0
  60. package/bin/costs/pricing-manager.d.ts +84 -0
  61. package/bin/costs/pricing-manager.js +342 -0
  62. package/bin/costs/providers/index.d.ts +32 -0
  63. package/bin/costs/providers/index.js +153 -0
  64. package/bin/costs/sources/api-source.d.ts +10 -0
  65. package/bin/costs/sources/api-source.js +32 -0
  66. package/bin/costs/sources/index.d.ts +6 -0
  67. package/bin/costs/sources/index.js +15 -0
  68. package/bin/costs/sources/local-json-source.d.ts +23 -0
  69. package/bin/costs/sources/local-json-source.js +59 -0
  70. package/bin/costs/sources/remote-json-source.d.ts +11 -0
  71. package/bin/costs/sources/remote-json-source.js +53 -0
  72. package/bin/costs/types.d.ts +53 -0
  73. package/bin/costs/types.js +5 -0
  74. package/bin/dev/cli.d.ts +4 -0
  75. package/bin/dev/cli.js +134 -0
  76. package/bin/dev/form.d.ts +36 -0
  77. package/bin/dev/form.js +254 -0
  78. package/bin/dev/index.d.ts +1 -0
  79. package/bin/dev/index.js +5 -0
  80. package/bin/generate/cli.js +29 -2
  81. package/bin/generate/form.d.ts +5 -1
  82. package/bin/generate/form.js +3 -3
  83. package/bin/generate/templates/nonopinionated/config.tpl +12 -0
  84. package/bin/generate/templates/nonopinionated/event.tpl +10 -0
  85. package/bin/generate/templates/nonopinionated/guard.tpl +18 -0
  86. package/bin/generate/templates/nonopinionated/handler.tpl +12 -0
  87. package/bin/generate/templates/nonopinionated/interceptor.tpl +27 -0
  88. package/bin/generate/templates/opinionated/config.tpl +47 -0
  89. package/bin/generate/templates/opinionated/entity.tpl +1 -8
  90. package/bin/generate/templates/opinionated/event.tpl +15 -0
  91. package/bin/generate/templates/opinionated/guard.tpl +41 -0
  92. package/bin/generate/templates/opinionated/handler.tpl +23 -0
  93. package/bin/generate/templates/opinionated/interceptor.tpl +50 -0
  94. package/bin/generate/utils/command-utils.d.ts +7 -3
  95. package/bin/generate/utils/command-utils.js +95 -31
  96. package/bin/generate/utils/nonopininated-cmd.d.ts +10 -1
  97. package/bin/generate/utils/nonopininated-cmd.js +100 -1
  98. package/bin/generate/utils/opinionated-cmd.d.ts +10 -1
  99. package/bin/generate/utils/opinionated-cmd.js +112 -7
  100. package/bin/generate/utils/string-utils.d.ts +6 -0
  101. package/bin/generate/utils/string-utils.js +13 -1
  102. package/bin/help/form.js +11 -3
  103. package/bin/migrate/analyzers/platform-detector.d.ts +14 -0
  104. package/bin/migrate/analyzers/platform-detector.js +116 -0
  105. package/bin/migrate/cli.d.ts +6 -0
  106. package/bin/migrate/cli.js +96 -0
  107. package/bin/migrate/form.d.ts +25 -0
  108. package/bin/migrate/form.js +347 -0
  109. package/bin/migrate/generators/compose-to-k8s.d.ts +2 -0
  110. package/bin/migrate/generators/compose-to-k8s.js +324 -0
  111. package/bin/migrate/generators/compose-to-railway.d.ts +2 -0
  112. package/bin/migrate/generators/compose-to-railway.js +138 -0
  113. package/bin/migrate/generators/compose-to-render.d.ts +2 -0
  114. package/bin/migrate/generators/compose-to-render.js +148 -0
  115. package/bin/migrate/generators/generic-migration.d.ts +9 -0
  116. package/bin/migrate/generators/generic-migration.js +221 -0
  117. package/bin/migrate/generators/heroku-to-fly.d.ts +2 -0
  118. package/bin/migrate/generators/heroku-to-fly.js +291 -0
  119. package/bin/migrate/generators/heroku-to-railway.d.ts +2 -0
  120. package/bin/migrate/generators/heroku-to-railway.js +283 -0
  121. package/bin/migrate/generators/heroku-to-render.d.ts +2 -0
  122. package/bin/migrate/generators/heroku-to-render.js +148 -0
  123. package/bin/migrate/generators/index.d.ts +7 -0
  124. package/bin/migrate/generators/index.js +17 -0
  125. package/bin/migrate/generators/template-loader.d.ts +21 -0
  126. package/bin/migrate/generators/template-loader.js +59 -0
  127. package/bin/migrate/index.d.ts +1 -0
  128. package/bin/migrate/index.js +5 -0
  129. package/bin/new/cli.js +21 -6
  130. package/bin/new/form.d.ts +25 -4
  131. package/bin/new/form.js +285 -70
  132. package/bin/profile/analyzers/dockerfile-analyzer.d.ts +27 -0
  133. package/bin/profile/analyzers/dockerfile-analyzer.js +122 -0
  134. package/bin/profile/analyzers/image-analyzer.d.ts +19 -0
  135. package/bin/profile/analyzers/image-analyzer.js +85 -0
  136. package/bin/profile/cli.d.ts +4 -0
  137. package/bin/profile/cli.js +92 -0
  138. package/bin/profile/form.d.ts +56 -0
  139. package/bin/profile/form.js +400 -0
  140. package/bin/profile/index.d.ts +1 -0
  141. package/bin/profile/index.js +5 -0
  142. package/bin/profile/optimizers/index.d.ts +19 -0
  143. package/bin/profile/optimizers/index.js +137 -0
  144. package/bin/providers/add/form.d.ts +1 -1
  145. package/bin/providers/add/form.js +27 -6
  146. package/bin/providers/create/form.js +2 -1
  147. package/bin/scripts/form.js +27 -5
  148. package/bin/studio/cli.d.ts +15 -0
  149. package/bin/studio/cli.js +166 -0
  150. package/bin/studio/index.d.ts +5 -0
  151. package/bin/studio/index.js +9 -0
  152. package/bin/templates/cache.d.ts +54 -0
  153. package/bin/templates/cache.js +180 -0
  154. package/bin/templates/cli.d.ts +8 -0
  155. package/bin/templates/cli.js +292 -0
  156. package/bin/templates/fetcher.d.ts +49 -0
  157. package/bin/templates/fetcher.js +208 -0
  158. package/bin/templates/index.d.ts +11 -0
  159. package/bin/templates/index.js +37 -0
  160. package/bin/templates/manager.d.ts +116 -0
  161. package/bin/templates/manager.js +323 -0
  162. package/bin/templates/renderer.d.ts +49 -0
  163. package/bin/templates/renderer.js +204 -0
  164. package/bin/templates/types.d.ts +51 -0
  165. package/bin/templates/types.js +5 -0
  166. package/bin/utils/add-module-to-container.d.ts +2 -2
  167. package/bin/utils/add-module-to-container.js +15 -5
  168. package/bin/utils/cli-ui.d.ts +30 -3
  169. package/bin/utils/cli-ui.js +95 -13
  170. package/bin/utils/index.d.ts +4 -0
  171. package/bin/utils/index.js +4 -0
  172. package/bin/utils/input-validation.d.ts +50 -0
  173. package/bin/utils/input-validation.js +143 -0
  174. package/bin/utils/package-manager-commands.d.ts +24 -0
  175. package/bin/utils/package-manager-commands.js +50 -0
  176. package/bin/utils/safe-spawn.d.ts +35 -0
  177. package/bin/utils/safe-spawn.js +51 -0
  178. package/bin/utils/update-tsconfig-paths.d.ts +35 -0
  179. package/bin/utils/update-tsconfig-paths.js +286 -0
  180. package/package.json +154 -154
package/bin/cli.js CHANGED
@@ -6,10 +6,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.BUNDLE_VERSION = void 0;
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
- const process_1 = require("process");
10
9
  const yargs_1 = __importDefault(require("yargs"));
11
10
  const helpers_1 = require("yargs/helpers");
12
11
  const project_commands_1 = require("./commands/project.commands");
12
+ const containerize_1 = require("./containerize");
13
+ const cicd_1 = require("./cicd");
14
+ const migrate_1 = require("./migrate");
15
+ const profile_1 = require("./profile");
16
+ const dev_1 = require("./dev");
17
+ const costs_1 = require("./costs");
18
+ const templates_1 = require("./templates");
13
19
  const generate_1 = require("./generate");
14
20
  const cli_1 = require("./help/cli");
15
21
  const info_1 = require("./info");
@@ -18,12 +24,13 @@ const providers_1 = require("./providers");
18
24
  const cli_2 = require("./providers/create/cli");
19
25
  const cli_ui_1 = require("./utils/cli-ui");
20
26
  const scripts_1 = require("./scripts");
27
+ const studio_1 = require("./studio");
21
28
  /**
22
29
  * The current version of the ExpressoTS Bundle.
23
30
  * core, adapters, and cli.
24
31
  */
25
- exports.BUNDLE_VERSION = "3.0.0";
26
- process_1.stdout.write(`\n${[chalk_1.default.bold.green("šŸŽ Expressots")]}\n\n`);
32
+ exports.BUNDLE_VERSION = "4.0.0-preview.2";
33
+ (0, cli_ui_1.printHeader)();
27
34
  (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
28
35
  .scriptName("expressots")
29
36
  .command((0, new_1.createProject)())
@@ -34,7 +41,15 @@ process_1.stdout.write(`\n${[chalk_1.default.bold.green("šŸŽ Expressots")]}\n\n
34
41
  .command((0, providers_1.addProviderCMD)())
35
42
  .command((0, providers_1.removeProviderCMD)())
36
43
  .command((0, generate_1.generateProject)())
44
+ .command((0, containerize_1.containerize)())
45
+ .command((0, cicd_1.cicdCommand)())
46
+ .command((0, migrate_1.migrateCommand)())
47
+ .command((0, profile_1.profileCommand)())
48
+ .command((0, dev_1.devContainerCommand)())
49
+ .command((0, costs_1.costsCommand)())
50
+ .command((0, templates_1.templatesCommand)())
37
51
  .command((0, scripts_1.scriptsCommand)())
52
+ .command((0, studio_1.studioCommand)())
38
53
  .command((0, info_1.infoProject)())
39
54
  .command((0, cli_1.helpCommand)())
40
55
  .demandCommand(1, "You need at least one command before moving on")
@@ -1,10 +1,18 @@
1
1
  import { CommandModule } from "yargs";
2
+ /**
3
+ * Dev command options interface
4
+ */
5
+ interface DevCommandOptions {
6
+ container?: boolean;
7
+ build?: boolean;
8
+ detach?: boolean;
9
+ }
2
10
  /**
3
11
  * Dev command module
4
- * @type {CommandModule<object, object>}
12
+ * @type {CommandModule<object, DevCommandOptions>}
5
13
  * @returns The command module
6
14
  */
7
- export declare const devCommand: CommandModule<object, object>;
15
+ export declare const devCommand: CommandModule<object, DevCommandOptions>;
8
16
  /**
9
17
  * Build command module
10
18
  * @type {CommandModule<object, object>}
@@ -18,9 +26,14 @@ export declare const buildCommand: CommandModule<object, object>;
18
26
  */
19
27
  export declare const prodCommand: CommandModule<object, object>;
20
28
  /**
21
- * Helper function to run a command
22
- * @param command The command to run
29
+ * Run command options
23
30
  */
24
- export declare const runCommand: ({ command, }: {
31
+ interface RunCommandOptions {
25
32
  command: string;
26
- }) => Promise<void>;
33
+ }
34
+ /**
35
+ * Helper function to run a command
36
+ * @param options The command options
37
+ */
38
+ export declare const runCommand: ({ command, }: RunCommandOptions) => Promise<void>;
39
+ export {};
@@ -31,8 +31,10 @@ 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
  * Helper function to load and extract outDir from tsconfig.build.json
38
40
  */
@@ -42,8 +44,15 @@ function getOutDir() {
42
44
  (0, cli_ui_1.printError)("Cannot find tsconfig.build.json. Please create one in the root directory", "tsconfig-build-path");
43
45
  process.exit(1);
44
46
  }
45
- const tsconfig = JSON.parse((0, fs_1.readFileSync)(tsconfigBuildPath, "utf-8"));
46
- const outDir = tsconfig.compilerOptions.outDir;
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;
47
56
  if (!outDir) {
48
57
  (0, cli_ui_1.printError)("Cannot find outDir in tsconfig.build.json. Please provide an outDir.", "tsconfig-build-path");
49
58
  process.exit(1);
@@ -55,40 +64,63 @@ function getOutDir() {
55
64
  return outDir;
56
65
  }
57
66
  /**
58
- * Load the configuration from the compiler
59
- * @param compiler The compiler to load the configuration from
60
- * @returns The configuration
61
- */
62
- async function opinionatedConfig() {
63
- const { entryPoint } = await compiler_1.default.loadConfig();
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
67
+ * Build the tsx watch arguments for development mode.
68
+ * Uses tsx's built-in --watch flag for reliable cross-platform file watching
69
+ * (avoids nodemon + SIGTERM issues on Windows).
70
+ *
71
+ * @param opinionated - Whether to use opinionated configuration
72
+ * @returns The tsx arguments array
76
73
  */
77
- async function nonOpinionatedConfig() {
78
- const { entryPoint } = await compiler_1.default.loadConfig();
79
- const config = ["--watch", `./src/${entryPoint}.ts`];
80
- return config;
74
+ async function buildDevArgs(opinionated) {
75
+ const { entryPoint, sourceRoot } = await compiler_1.default.loadConfig();
76
+ const args = ["--watch"];
77
+ if (opinionated) {
78
+ args.push("-r", "tsconfig-paths/register");
79
+ }
80
+ // Honor `sourceRoot` from expressots.config.ts so projects whose
81
+ // source lives under a non-default folder (e.g. `api/` or
82
+ // `services/`) still resolve their entry point correctly.
83
+ args.push(`./${sourceRoot}/${entryPoint}.ts`);
84
+ return args;
81
85
  }
82
86
  /**
83
87
  * Dev command module
84
- * @type {CommandModule<object, object>}
88
+ * @type {CommandModule<object, DevCommandOptions>}
85
89
  * @returns The command module
86
90
  */
87
91
  exports.devCommand = {
88
92
  command: "dev",
89
93
  describe: "Start development server.",
90
- handler: async () => {
91
- await (0, exports.runCommand)({ command: "dev" });
94
+ builder: {
95
+ container: {
96
+ alias: "c",
97
+ type: "boolean",
98
+ default: false,
99
+ description: "Run development inside Docker container",
100
+ },
101
+ build: {
102
+ alias: "b",
103
+ type: "boolean",
104
+ default: false,
105
+ description: "Rebuild container before starting (with --container)",
106
+ },
107
+ detach: {
108
+ alias: "d",
109
+ type: "boolean",
110
+ default: false,
111
+ description: "Run container in background (with --container)",
112
+ },
113
+ },
114
+ handler: async (argv) => {
115
+ if (argv.container) {
116
+ await runContainerDev({
117
+ build: argv.build ?? false,
118
+ detach: argv.detach ?? false,
119
+ });
120
+ }
121
+ else {
122
+ await (0, exports.runCommand)({ command: "dev" });
123
+ }
92
124
  },
93
125
  };
94
126
  /**
@@ -124,11 +156,15 @@ exports.prodCommand = {
124
156
  */
125
157
  function execCmd(command, args, cwd = process.cwd()) {
126
158
  return new Promise((resolve, reject) => {
127
- const proc = (0, child_process_1.spawn)(command, args, {
159
+ // `safeSpawn` (cross-spawn) resolves Windows `.cmd` shims (npx,
160
+ // tsx, tsc, etc.) via PATHEXT and applies cmd.exe-aware escaping
161
+ // for every argv entry, while falling through to plain `spawn`
162
+ // with `shell: false` on Unix.
163
+ const proc = (0, safe_spawn_1.safeSpawn)(command, args, {
128
164
  stdio: "inherit",
129
- shell: true,
130
165
  cwd,
131
166
  });
167
+ proc.on("error", (err) => reject(err));
132
168
  proc.on("close", (code) => {
133
169
  if (code === 0) {
134
170
  resolve();
@@ -153,47 +189,343 @@ const compileTypescript = async () => {
153
189
  await execCmd("npx", ["tsc", "-p", "tsconfig.build.json"]);
154
190
  (0, cli_ui_1.printSuccess)("Built successfully", "compile-typescript");
155
191
  };
192
+ /**
193
+ * Transform path aliases to relative paths in compiled JavaScript files.
194
+ * This runs after TypeScript compilation to ensure production builds work
195
+ * without runtime path resolution.
196
+ *
197
+ * @param outDir - The output directory (e.g., "./dist")
198
+ */
199
+ const transformPathAliases = async (outDir) => {
200
+ const tsconfigPath = (0, path_1.join)(process.cwd(), "tsconfig.build.json");
201
+ if (!(0, fs_1.existsSync)(tsconfigPath)) {
202
+ return; // No tsconfig.build.json, skip transformation
203
+ }
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) {
215
+ return; // No path aliases defined, skip
216
+ }
217
+ // Build regex patterns for each alias
218
+ const aliasPatterns = [];
219
+ for (const [alias, targets] of Object.entries(paths)) {
220
+ if (!Array.isArray(targets) || targets.length === 0)
221
+ continue;
222
+ // Convert @alias/* to regex pattern
223
+ // Matches: require("@alias/something") or require('@alias/something')
224
+ const aliasBase = alias.replace("/*", "");
225
+ const targetBase = targets[0].replace("/*", "");
226
+ // Pattern to match require("@alias/...") or require('@alias/...')
227
+ const pattern = new RegExp(`require\\(["']${aliasBase.replace("@", "\\@")}/([^"']+)["']\\)`, "g");
228
+ aliasPatterns.push({
229
+ pattern,
230
+ alias: aliasBase,
231
+ target: targetBase,
232
+ });
233
+ }
234
+ if (aliasPatterns.length === 0) {
235
+ return;
236
+ }
237
+ // Recursively find all .js files in outDir
238
+ const findJsFiles = async (dir) => {
239
+ const files = [];
240
+ const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
241
+ for (const entry of entries) {
242
+ const fullPath = (0, path_1.join)(dir, entry.name);
243
+ if (entry.isDirectory()) {
244
+ files.push(...(await findJsFiles(fullPath)));
245
+ }
246
+ else if (entry.name.endsWith(".js")) {
247
+ files.push(fullPath);
248
+ }
249
+ }
250
+ return files;
251
+ };
252
+ const jsFiles = await findJsFiles(outDir);
253
+ let transformedCount = 0;
254
+ for (const file of jsFiles) {
255
+ let content = await fs_1.promises.readFile(file, "utf-8");
256
+ let modified = false;
257
+ // Get the directory of the current file relative to outDir
258
+ const fileDir = path_1.default.dirname(file);
259
+ for (const { pattern, alias, target } of aliasPatterns) {
260
+ // Calculate the relative path from this file to the target
261
+ const targetDir = (0, path_1.join)(outDir, baseUrl.replace("./", ""), target);
262
+ let relativePath = path_1.default.relative(fileDir, targetDir);
263
+ // Ensure it starts with ./ or ../
264
+ if (!relativePath.startsWith(".")) {
265
+ relativePath = "./" + relativePath;
266
+ }
267
+ // Replace Windows backslashes with forward slashes
268
+ relativePath = relativePath.replace(/\\/g, "/");
269
+ // Replace the alias with the relative path
270
+ const newContent = content.replace(pattern, (match, subPath) => {
271
+ modified = true;
272
+ return `require("${relativePath}/${subPath}")`;
273
+ });
274
+ if (newContent !== content) {
275
+ content = newContent;
276
+ }
277
+ }
278
+ if (modified) {
279
+ await fs_1.promises.writeFile(file, content, "utf-8");
280
+ transformedCount++;
281
+ }
282
+ }
283
+ if (transformedCount > 0) {
284
+ (0, cli_ui_1.printSuccess)(`Path aliases resolved in ${transformedCount} files`, "transform-paths");
285
+ }
286
+ };
156
287
  /**
157
288
  * Helper function to copy files to the dist directory
158
289
  */
159
290
  const copyFiles = async (outDir) => {
160
- const { opinionated } = await compiler_1.default.loadConfig();
161
- let filesToCopy = [];
162
- if (opinionated) {
163
- filesToCopy = [
164
- "./register-path.js",
165
- "tsconfig.build.json",
166
- "package.json",
167
- ];
168
- }
169
- else {
170
- filesToCopy = ["tsconfig.json", "package.json"];
291
+ // Only copy package.json - path aliases are resolved at build time
292
+ // No need for tsconfig files or register-path.js in production
293
+ const filesToCopy = ["package.json"];
294
+ for (const file of filesToCopy) {
295
+ if ((0, fs_1.existsSync)(file)) {
296
+ await fs_1.promises.copyFile(file, (0, path_1.join)(outDir, path_1.default.basename(file)));
297
+ }
171
298
  }
172
- filesToCopy.forEach((file) => {
173
- fs_1.promises.copyFile(file, (0, path_1.join)(outDir, path_1.default.basename(file)));
174
- });
175
299
  };
176
300
  /**
177
301
  * Helper function to clear the screen
178
302
  */
179
303
  const clearScreen = () => {
304
+ // `cls` and `clear` are built-ins / well-known executables.
305
+ // Invoking them via `shell: true` is safe here because there are no
306
+ // user-controlled args, but we keep `windowsHide: true` to suppress
307
+ // the Windows console flash.
180
308
  const platform = os_1.default.platform();
181
309
  const command = platform === "win32" ? "cls" : "clear";
182
- (0, child_process_1.spawn)(command, { stdio: "inherit", shell: true });
310
+ (0, child_process_1.spawn)(command, [], { stdio: "inherit", shell: true, windowsHide: true });
183
311
  };
312
+ /**
313
+ * Run development in Docker container with auto-setup
314
+ * This is the seamless "just works" experience
315
+ */
316
+ async function runContainerDev(options) {
317
+ console.log(chalk_1.default.cyan("\n🐳 ExpressoTS Container Development\n"));
318
+ const cwd = process.cwd();
319
+ const composeDevFile = (0, path_1.join)(cwd, "docker-compose.development.yml");
320
+ const dockerfileDevFile = (0, path_1.join)(cwd, "Dockerfile.development");
321
+ const dockerSetupFile = (0, path_1.join)(cwd, "docker-setup.js");
322
+ const dockerDepsDir = (0, path_1.join)(cwd, ".docker-deps");
323
+ const packageDockerJson = (0, path_1.join)(cwd, "package.docker.json");
324
+ // Check if Docker is running
325
+ if (!isDockerRunning()) {
326
+ console.log(chalk_1.default.red("āŒ Docker is not running."));
327
+ console.log(chalk_1.default.gray(" Please start Docker Desktop or Docker daemon."));
328
+ return;
329
+ }
330
+ // Step 1: Auto-generate Docker files if missing
331
+ if (!(0, fs_1.existsSync)(dockerfileDevFile) || !(0, fs_1.existsSync)(composeDevFile)) {
332
+ console.log(chalk_1.default.yellow("šŸ“ Docker files not found. Generating..."));
333
+ try {
334
+ // Import and run containerize
335
+ const { containerizeProject } = await Promise.resolve().then(() => __importStar(require("../containerize/form")));
336
+ await containerizeProject({
337
+ target: "docker",
338
+ environment: "development",
339
+ preset: "standard",
340
+ analyze: true,
341
+ skipCompose: false,
342
+ includeCi: false,
343
+ });
344
+ console.log();
345
+ }
346
+ catch (error) {
347
+ console.log(chalk_1.default.red("āŒ Failed to generate Docker files."));
348
+ console.log(chalk_1.default.gray(" Run manually: expressots containerize docker --env development"));
349
+ return;
350
+ }
351
+ }
352
+ // Step 1.5: Check bootstrap config and create missing env files if needed
353
+ try {
354
+ const { analyzeBootstrapConfig, shouldCopyEnvFiles, getEnvFileForEnvironment, } = await Promise.resolve().then(() => __importStar(require("../containerize/analyzers/bootstrap-analyzer")));
355
+ const bootstrapConfig = await analyzeBootstrapConfig();
356
+ if (bootstrapConfig.hasEnvFileConfig &&
357
+ shouldCopyEnvFiles(bootstrapConfig)) {
358
+ const devEnvFile = getEnvFileForEnvironment(bootstrapConfig, "development");
359
+ // Check if required env file is missing
360
+ if (bootstrapConfig.missingEnvFiles.includes(devEnvFile)) {
361
+ console.log(chalk_1.default.yellow(`āš ļø Required env file missing: ${devEnvFile}`));
362
+ // Auto-create template if configured or prompt user
363
+ if (bootstrapConfig.autoCreateTemplate) {
364
+ console.log(chalk_1.default.gray(` Creating template ${devEnvFile}...`));
365
+ await createEnvTemplate(cwd, devEnvFile, "development", bootstrapConfig.requiredVariables);
366
+ console.log(chalk_1.default.green(` āœ“ Created ${devEnvFile}`));
367
+ }
368
+ else {
369
+ // Provide helpful instructions
370
+ console.log(chalk_1.default.cyan("\nšŸ’” To fix this, either:"));
371
+ console.log(chalk_1.default.gray(` 1. Create ${devEnvFile} with your environment variables`));
372
+ console.log(chalk_1.default.gray(` 2. Add autoCreateTemplate: true to envFileConfig in bootstrap`));
373
+ console.log(chalk_1.default.gray(` 3. Use skipFileLoading: true for container deployments`));
374
+ console.log();
375
+ // Still continue - the container might work if env vars are set in docker-compose
376
+ console.log(chalk_1.default.yellow(` āš ļø Container may fail if ${devEnvFile} is required`));
377
+ console.log();
378
+ }
379
+ }
380
+ // Show required variables that need to be set
381
+ if (bootstrapConfig.requiredVariables.length > 0) {
382
+ console.log(chalk_1.default.cyan("šŸ“‹ Required environment variables:"));
383
+ bootstrapConfig.requiredVariables.forEach((varName) => {
384
+ console.log(chalk_1.default.gray(` • ${varName}`));
385
+ });
386
+ console.log(chalk_1.default.gray(` Set these in ${devEnvFile} or docker-compose.development.yml`));
387
+ console.log();
388
+ }
389
+ }
390
+ }
391
+ catch (error) {
392
+ // Non-fatal - continue with container startup
393
+ console.log(chalk_1.default.gray(" (Bootstrap analysis skipped)"));
394
+ }
395
+ // Step 2: Auto-run docker:setup if local dependencies exist
396
+ if ((0, fs_1.existsSync)(packageDockerJson) && (0, fs_1.existsSync)(dockerSetupFile)) {
397
+ // Check if .docker-deps needs to be updated
398
+ const needsSetup = !(0, fs_1.existsSync)(dockerDepsDir) || isDirEmpty(dockerDepsDir);
399
+ if (needsSetup) {
400
+ console.log(chalk_1.default.yellow("šŸ“¦ Setting up local dependencies..."));
401
+ try {
402
+ const setupResult = (0, safe_spawn_1.safeSpawnSync)(process.execPath, ["docker-setup.js"], {
403
+ cwd,
404
+ stdio: "inherit",
405
+ encoding: "utf-8",
406
+ });
407
+ if (setupResult.error)
408
+ throw setupResult.error;
409
+ if (typeof setupResult.status === "number" &&
410
+ setupResult.status !== 0) {
411
+ throw new Error(`exited with code ${setupResult.status}`);
412
+ }
413
+ console.log();
414
+ }
415
+ catch (error) {
416
+ console.log(chalk_1.default.red("āŒ Failed to setup local dependencies."));
417
+ console.log(chalk_1.default.gray(" Run manually: npm run docker:setup"));
418
+ return;
419
+ }
420
+ }
421
+ }
422
+ // Step 3: Start the containers
423
+ console.log(chalk_1.default.yellow(`šŸ“„ Using docker-compose.development.yml`));
424
+ const args = ["-f", composeDevFile, "up"];
425
+ if (options.build) {
426
+ console.log(chalk_1.default.yellow("šŸ”Ø Rebuilding containers..."));
427
+ args.splice(2, 0, "--build");
428
+ }
429
+ if (options.detach) {
430
+ args.push("-d");
431
+ }
432
+ console.log(chalk_1.default.yellow("šŸš€ Starting development containers...\n"));
433
+ // Print dev info
434
+ console.log(chalk_1.default.bold("Development Environment:"));
435
+ console.log(` 🌐 App: http://localhost:3000`);
436
+ console.log(` šŸ” Debug: localhost:9229`);
437
+ console.log();
438
+ console.log(chalk_1.default.bold("Commands:"));
439
+ console.log(` ${chalk_1.default.gray("expressots dev -c")} Start containers`);
440
+ console.log(` ${chalk_1.default.gray("expressots dev -c -b")} Rebuild & start`);
441
+ console.log(` ${chalk_1.default.gray("expressots dev -c -d")} Start in background`);
442
+ console.log(` ${chalk_1.default.gray("docker-compose -f docker-compose.development.yml down")} Stop`);
443
+ console.log();
444
+ console.log(chalk_1.default.green("šŸ”„ Hot reload enabled - edit files to see changes"));
445
+ if (!options.detach) {
446
+ console.log(chalk_1.default.gray("Press Ctrl+C to stop\n"));
447
+ }
448
+ // Run docker-compose
449
+ runDockerComposeCommand(args, cwd, options.detach);
450
+ if (options.detach) {
451
+ console.log(chalk_1.default.green("\nāœ… Containers started in background."));
452
+ console.log(chalk_1.default.gray(" View logs: docker-compose -f docker-compose.development.yml logs -f"));
453
+ }
454
+ }
455
+ /**
456
+ * Check if Docker is running
457
+ */
458
+ function isDockerRunning() {
459
+ const result = (0, safe_spawn_1.safeSpawnSync)("docker", ["info"], {
460
+ stdio: ["pipe", "pipe", "pipe"],
461
+ });
462
+ return !result.error && result.status === 0;
463
+ }
464
+ /**
465
+ * Check if directory is empty
466
+ */
467
+ function isDirEmpty(dir) {
468
+ try {
469
+ const files = (0, fs_1.readdirSync)(dir);
470
+ return files.length === 0;
471
+ }
472
+ catch {
473
+ return true;
474
+ }
475
+ }
476
+ /**
477
+ * Create an environment template file
478
+ */
479
+ async function createEnvTemplate(cwd, fileName, environment, requiredVariables) {
480
+ const filePath = (0, path_1.join)(cwd, fileName);
481
+ const commonVars = [
482
+ "PORT=3000",
483
+ `NODE_ENV=${environment}`,
484
+ "# Add your environment variables below",
485
+ ];
486
+ const requiredVars = requiredVariables.map((key) => `${key}=`);
487
+ const template = [...commonVars, ...requiredVars].join("\n");
488
+ await fs_1.promises.writeFile(filePath, template, "utf-8");
489
+ }
490
+ /**
491
+ * Run docker-compose command
492
+ */
493
+ function runDockerComposeCommand(args, cwd, detach) {
494
+ // Try docker compose (v2) first, fall back to docker-compose (v1).
495
+ // `safeSpawnSync` (cross-spawn) handles platform-specific binary
496
+ // resolution and cmd.exe-aware argv escaping, so compose file paths
497
+ // and service names are forwarded as discrete arguments rather than
498
+ // re-interpreted by the OS shell.
499
+ const v2 = (0, safe_spawn_1.safeSpawnSync)("docker", ["compose", ...args], {
500
+ cwd,
501
+ stdio: "inherit",
502
+ });
503
+ if (v2.error || (typeof v2.status === "number" && v2.status !== 0)) {
504
+ const v1 = (0, safe_spawn_1.safeSpawnSync)("docker-compose", args, {
505
+ cwd,
506
+ stdio: "inherit",
507
+ });
508
+ if (v1.error || (typeof v1.status === "number" && v1.status !== 0)) {
509
+ console.log(chalk_1.default.red("Error running docker-compose"));
510
+ }
511
+ }
512
+ // `detach` is honored implicitly by docker compose itself when "-d"
513
+ // is present in `args`. The previous branch tried to switch between
514
+ // execSync and spawnSync for that case, which served no real
515
+ // purpose and made shell injection harder to reason about.
516
+ void detach;
517
+ }
184
518
  /**
185
519
  * Helper function to run a command
186
- * @param command The command to run
520
+ * @param options The command options
187
521
  */
188
522
  const runCommand = async ({ command, }) => {
189
- const { opinionated, entryPoint } = await compiler_1.default.loadConfig();
523
+ const { opinionated, entryPoint, sourceRoot } = await compiler_1.default.loadConfig();
190
524
  const outDir = getOutDir();
191
525
  try {
192
526
  switch (command) {
193
527
  case "dev":
194
- execCmd("tsx", opinionated
195
- ? await opinionatedConfig()
196
- : await nonOpinionatedConfig());
528
+ await execCmd("tsx", await buildDevArgs(opinionated));
197
529
  break;
198
530
  case "build":
199
531
  if (!outDir) {
@@ -202,6 +534,10 @@ const runCommand = async ({ command, }) => {
202
534
  }
203
535
  await cleanDist(outDir);
204
536
  await compileTypescript();
537
+ // Transform path aliases to relative paths for production
538
+ if (opinionated) {
539
+ await transformPathAliases(outDir);
540
+ }
205
541
  await copyFiles(outDir);
206
542
  break;
207
543
  case "prod": {
@@ -209,19 +545,12 @@ const runCommand = async ({ command, }) => {
209
545
  (0, cli_ui_1.printError)("Cannot run in prod mode. Please provide an outDir in tsconfig.build.json", "prod-command");
210
546
  process.exit(1);
211
547
  }
212
- let config = [];
213
- if (opinionated) {
214
- config = [
215
- "-r",
216
- `./${outDir}/register-path.js`,
217
- `./${outDir}/src/${entryPoint}.js`,
218
- ];
219
- }
220
- else {
221
- config = [`./${outDir}/${entryPoint}.js`];
222
- }
548
+ // Honor `sourceRoot` so the compiled entry-point path
549
+ // matches whatever folder the user configured (the TS
550
+ // compiler preserves the source tree under `outDir`).
551
+ const config = [`./${outDir}/${sourceRoot}/${entryPoint}.js`];
223
552
  clearScreen();
224
- execCmd("node", config);
553
+ await execCmd("node", config);
225
554
  break;
226
555
  }
227
556
  default:
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Configuration exports
3
+ */
4
+ export { ConfigManager, getConfigManager, resetConfigManager } from "./manager";
5
+ export type { GlobalConfig, TemplateConfig, PricingConfig } from "./manager";
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * Configuration exports
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resetConfigManager = exports.getConfigManager = exports.ConfigManager = void 0;
7
+ var manager_1 = require("./manager");
8
+ Object.defineProperty(exports, "ConfigManager", { enumerable: true, get: function () { return manager_1.ConfigManager; } });
9
+ Object.defineProperty(exports, "getConfigManager", { enumerable: true, get: function () { return manager_1.getConfigManager; } });
10
+ Object.defineProperty(exports, "resetConfigManager", { enumerable: true, get: function () { return manager_1.resetConfigManager; } });