@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
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeImage = void 0;
4
+ const child_process_1 = require("child_process");
5
+ /**
6
+ * Analyze a Docker image for size, layers, and vulnerabilities
7
+ */
8
+ async function analyzeImage(imageName) {
9
+ // Get image inspect data
10
+ let size = "Unknown";
11
+ let layers = 0;
12
+ let created = "Unknown";
13
+ let os = "Unknown";
14
+ let architecture = "Unknown";
15
+ try {
16
+ // Use double quotes for cross-platform compatibility (Windows + Unix)
17
+ const inspectOutput = (0, child_process_1.execSync)(`docker inspect ${imageName} --format "{{.Size}} {{.Created}} {{.Os}} {{.Architecture}}"`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
18
+ const parts = inspectOutput.split(" ");
19
+ const sizeBytes = parseInt(parts[0], 10);
20
+ size = formatBytes(sizeBytes);
21
+ created = parts[1] || "Unknown";
22
+ os = parts[2] || "linux";
23
+ architecture = parts[3] || "amd64";
24
+ // Get layer count - cross-platform (count lines in Node.js instead of wc -l)
25
+ const historyOutput = (0, child_process_1.execSync)(`docker history ${imageName} --format "{{.ID}}"`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
26
+ layers = historyOutput.split("\n").filter((line) => line.trim()).length;
27
+ }
28
+ catch (error) {
29
+ // Docker command failed, image might not exist or Docker not running
30
+ throw new Error(`Failed to inspect image: ${imageName}`);
31
+ }
32
+ // Try to scan for vulnerabilities using Trivy if available
33
+ const vulnerabilities = [];
34
+ try {
35
+ // Check if Trivy is available
36
+ (0, child_process_1.execSync)("trivy --version", { stdio: ["pipe", "pipe", "pipe"] });
37
+ // Run Trivy scan
38
+ const trivyOutput = (0, child_process_1.execSync)(`trivy image --format json --severity HIGH,CRITICAL ${imageName}`, {
39
+ encoding: "utf-8",
40
+ stdio: ["pipe", "pipe", "pipe"],
41
+ maxBuffer: 50 * 1024 * 1024,
42
+ });
43
+ const trivyResult = JSON.parse(trivyOutput);
44
+ if (trivyResult.Results) {
45
+ for (const result of trivyResult.Results) {
46
+ if (result.Vulnerabilities) {
47
+ for (const vuln of result.Vulnerabilities) {
48
+ vulnerabilities.push({
49
+ id: vuln.VulnerabilityID,
50
+ severity: vuln.Severity?.toLowerCase() || "unknown",
51
+ description: vuln.Title ||
52
+ vuln.Description ||
53
+ "No description",
54
+ package: vuln.PkgName,
55
+ fixedVersion: vuln.FixedVersion,
56
+ });
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+ catch {
63
+ // Trivy not available or scan failed, skip vulnerability scanning
64
+ }
65
+ return {
66
+ size,
67
+ layers,
68
+ created,
69
+ os,
70
+ architecture,
71
+ vulnerabilities,
72
+ };
73
+ }
74
+ exports.analyzeImage = analyzeImage;
75
+ /**
76
+ * Format bytes to human readable string
77
+ */
78
+ function formatBytes(bytes) {
79
+ if (bytes === 0)
80
+ return "0 B";
81
+ const k = 1024;
82
+ const sizes = ["B", "KB", "MB", "GB"];
83
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
84
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
85
+ }
@@ -0,0 +1,4 @@
1
+ import { CommandModule } from "yargs";
2
+ type CommandModuleArgs = {};
3
+ declare const profileCommand: () => CommandModule<CommandModuleArgs, any>;
4
+ export { profileCommand };
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.profileCommand = void 0;
4
+ const form_1 = require("./form");
5
+ const profileCommand = () => {
6
+ return {
7
+ command: "profile <action> [target]",
8
+ describe: "Analyze and optimize container configurations.",
9
+ aliases: ["prof", "analyze"],
10
+ builder: (yargs) => {
11
+ yargs.positional("action", {
12
+ choices: ["container", "image", "optimize", "report"],
13
+ describe: "Action to perform",
14
+ type: "string",
15
+ demandOption: true,
16
+ });
17
+ yargs.positional("target", {
18
+ describe: "Target to analyze (Dockerfile path or image name)",
19
+ type: "string",
20
+ });
21
+ yargs.option("dockerfile", {
22
+ describe: "Path to Dockerfile",
23
+ type: "string",
24
+ alias: "f",
25
+ default: "Dockerfile",
26
+ });
27
+ yargs.option("format", {
28
+ choices: ["text", "json", "html"],
29
+ describe: "Output format",
30
+ type: "string",
31
+ default: "text",
32
+ });
33
+ yargs.option("severity", {
34
+ choices: ["low", "medium", "high", "critical"],
35
+ describe: "Minimum severity to report",
36
+ type: "string",
37
+ default: "low",
38
+ });
39
+ yargs.option("auto-fix", {
40
+ describe: "Automatically apply safe optimizations",
41
+ type: "boolean",
42
+ default: false,
43
+ });
44
+ yargs.option("output", {
45
+ describe: "Output file for report",
46
+ type: "string",
47
+ alias: "o",
48
+ });
49
+ yargs.option("include-security", {
50
+ describe: "Include security vulnerability scanning",
51
+ type: "boolean",
52
+ default: true,
53
+ });
54
+ yargs.option("include-size", {
55
+ describe: "Include size analysis",
56
+ type: "boolean",
57
+ default: true,
58
+ });
59
+ return yargs;
60
+ },
61
+ handler: async (argv) => {
62
+ const { action, target, dockerfile, format, severity, autoFix, output, includeSecurity, includeSize, } = argv;
63
+ const options = {
64
+ target: target,
65
+ dockerfile,
66
+ format: format,
67
+ severity: severity,
68
+ autoFix,
69
+ output,
70
+ includeSecurity,
71
+ includeSize,
72
+ };
73
+ switch (action) {
74
+ case "container":
75
+ await (0, form_1.profileContainer)(options);
76
+ break;
77
+ case "image":
78
+ await (0, form_1.profileImage)(options);
79
+ break;
80
+ case "optimize":
81
+ await (0, form_1.optimizeContainer)(options);
82
+ break;
83
+ case "report":
84
+ await (0, form_1.showProfileReport)(options);
85
+ break;
86
+ default:
87
+ console.log(`Unknown action: ${action}`);
88
+ }
89
+ },
90
+ };
91
+ };
92
+ exports.profileCommand = profileCommand;
@@ -0,0 +1,56 @@
1
+ export interface ProfileOptions {
2
+ target?: string;
3
+ dockerfile: string;
4
+ format: "text" | "json" | "html";
5
+ severity: "low" | "medium" | "high" | "critical";
6
+ autoFix: boolean;
7
+ output?: string;
8
+ includeSecurity: boolean;
9
+ includeSize: boolean;
10
+ }
11
+ export interface ProfileResult {
12
+ score: number;
13
+ issues: Issue[];
14
+ recommendations: Recommendation[];
15
+ metrics: Metrics;
16
+ }
17
+ export interface Issue {
18
+ id: string;
19
+ severity: "low" | "medium" | "high" | "critical";
20
+ category: "security" | "size" | "performance" | "best-practice";
21
+ message: string;
22
+ line?: number;
23
+ fix?: string;
24
+ }
25
+ export interface Recommendation {
26
+ priority: "low" | "medium" | "high";
27
+ category: string;
28
+ title: string;
29
+ description: string;
30
+ impact: string;
31
+ }
32
+ export interface Metrics {
33
+ estimatedSize?: string;
34
+ layers?: number;
35
+ baseImage?: string;
36
+ nodeVersion?: string;
37
+ hasMultiStage?: boolean;
38
+ hasHealthCheck?: boolean;
39
+ hasNonRootUser?: boolean;
40
+ }
41
+ /**
42
+ * Profile a Dockerfile for issues and recommendations
43
+ */
44
+ export declare function profileContainer(options: ProfileOptions): Promise<void>;
45
+ /**
46
+ * Profile a built Docker image
47
+ */
48
+ export declare function profileImage(options: ProfileOptions): Promise<void>;
49
+ /**
50
+ * Generate and optionally apply optimizations
51
+ */
52
+ export declare function optimizeContainer(options: ProfileOptions): Promise<void>;
53
+ /**
54
+ * Show a comprehensive profile report
55
+ */
56
+ export declare function showProfileReport(options: ProfileOptions): Promise<void>;
@@ -0,0 +1,400 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.showProfileReport = exports.optimizeContainer = exports.profileImage = exports.profileContainer = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const dockerfile_analyzer_1 = require("./analyzers/dockerfile-analyzer");
11
+ const image_analyzer_1 = require("./analyzers/image-analyzer");
12
+ const optimizers_1 = require("./optimizers");
13
+ /**
14
+ * Profile a Dockerfile for issues and recommendations
15
+ */
16
+ async function profileContainer(options) {
17
+ console.log(chalk_1.default.cyan("\nšŸ” ExpressoTS Container Profiler\n"));
18
+ const cwd = process.cwd();
19
+ const dockerfilePath = path_1.default.join(cwd, options.dockerfile);
20
+ if (!fs_1.default.existsSync(dockerfilePath)) {
21
+ console.log(chalk_1.default.red(`Error: Dockerfile not found at ${dockerfilePath}`));
22
+ console.log(chalk_1.default.gray("Run 'expressots containerize' to generate a Dockerfile first."));
23
+ return;
24
+ }
25
+ console.log(chalk_1.default.yellow(`šŸ“„ Analyzing ${options.dockerfile}...\n`));
26
+ const analysis = await (0, dockerfile_analyzer_1.analyzeDockerfile)(dockerfilePath);
27
+ const result = generateProfileResult(analysis, options);
28
+ outputResult(result, options);
29
+ }
30
+ exports.profileContainer = profileContainer;
31
+ /**
32
+ * Profile a built Docker image
33
+ */
34
+ async function profileImage(options) {
35
+ console.log(chalk_1.default.cyan("\nšŸ” ExpressoTS Image Profiler\n"));
36
+ if (!options.target) {
37
+ console.log(chalk_1.default.red("Error: Please specify an image name."));
38
+ console.log(chalk_1.default.gray("Usage: expressots profile image <image-name>"));
39
+ return;
40
+ }
41
+ console.log(chalk_1.default.yellow(`🐳 Analyzing image: ${options.target}...\n`));
42
+ try {
43
+ const analysis = await (0, image_analyzer_1.analyzeImage)(options.target);
44
+ console.log(chalk_1.default.bold("Image Analysis:"));
45
+ console.log(` Size: ${analysis.size}`);
46
+ console.log(` Layers: ${analysis.layers}`);
47
+ console.log(` Created: ${analysis.created}`);
48
+ console.log(` OS/Arch: ${analysis.os}/${analysis.architecture}`);
49
+ if (analysis.vulnerabilities && analysis.vulnerabilities.length > 0) {
50
+ console.log(chalk_1.default.bold("\nVulnerabilities:"));
51
+ for (const vuln of analysis.vulnerabilities) {
52
+ const color = vuln.severity === "critical"
53
+ ? chalk_1.default.red
54
+ : vuln.severity === "high"
55
+ ? chalk_1.default.yellow
56
+ : chalk_1.default.gray;
57
+ console.log(` ${color(`[${vuln.severity.toUpperCase()}]`)} ${vuln.id}: ${vuln.description}`);
58
+ }
59
+ }
60
+ else {
61
+ console.log(chalk_1.default.green("\nāœ“ No vulnerabilities found"));
62
+ }
63
+ }
64
+ catch (error) {
65
+ console.log(chalk_1.default.red(`Error analyzing image: ${error}`));
66
+ console.log(chalk_1.default.gray("Make sure Docker is running and the image exists."));
67
+ }
68
+ }
69
+ exports.profileImage = profileImage;
70
+ /**
71
+ * Generate and optionally apply optimizations
72
+ */
73
+ async function optimizeContainer(options) {
74
+ console.log(chalk_1.default.cyan("\n⚔ ExpressoTS Container Optimizer\n"));
75
+ const cwd = process.cwd();
76
+ const dockerfilePath = path_1.default.join(cwd, options.dockerfile);
77
+ if (!fs_1.default.existsSync(dockerfilePath)) {
78
+ console.log(chalk_1.default.red(`Error: Dockerfile not found at ${dockerfilePath}`));
79
+ return;
80
+ }
81
+ const analysis = await (0, dockerfile_analyzer_1.analyzeDockerfile)(dockerfilePath);
82
+ const optimizations = (0, optimizers_1.generateOptimizations)(analysis);
83
+ if (optimizations.length === 0) {
84
+ console.log(chalk_1.default.green("āœ“ No optimizations needed. Your Dockerfile looks great!"));
85
+ return;
86
+ }
87
+ console.log(chalk_1.default.bold(`Found ${optimizations.length} optimization(s):\n`));
88
+ for (const opt of optimizations) {
89
+ const priorityColor = opt.priority === "high"
90
+ ? chalk_1.default.red
91
+ : opt.priority === "medium"
92
+ ? chalk_1.default.yellow
93
+ : chalk_1.default.gray;
94
+ console.log(` ${priorityColor(`[${opt.priority.toUpperCase()}]`)} ${opt.title}`);
95
+ console.log(chalk_1.default.gray(` ${opt.description}`));
96
+ console.log(chalk_1.default.green(` Impact: ${opt.impact}`));
97
+ console.log();
98
+ }
99
+ if (options.autoFix) {
100
+ console.log(chalk_1.default.yellow("šŸ”§ Applying safe optimizations..."));
101
+ const applied = await (0, optimizers_1.applyOptimizations)(dockerfilePath, optimizations);
102
+ console.log(chalk_1.default.green(`āœ“ Applied ${applied} optimization(s)`));
103
+ }
104
+ else {
105
+ console.log(chalk_1.default.gray("Tip: Use --auto-fix to automatically apply safe optimizations"));
106
+ }
107
+ }
108
+ exports.optimizeContainer = optimizeContainer;
109
+ /**
110
+ * Show a comprehensive profile report
111
+ */
112
+ async function showProfileReport(options) {
113
+ console.log(chalk_1.default.cyan("\nšŸ“Š ExpressoTS Container Profile Report\n"));
114
+ const cwd = process.cwd();
115
+ const dockerfilePath = path_1.default.join(cwd, options.dockerfile);
116
+ if (!fs_1.default.existsSync(dockerfilePath)) {
117
+ console.log(chalk_1.default.red(`Error: Dockerfile not found at ${dockerfilePath}`));
118
+ return;
119
+ }
120
+ const analysis = await (0, dockerfile_analyzer_1.analyzeDockerfile)(dockerfilePath);
121
+ const result = generateProfileResult(analysis, options);
122
+ // Generate report based on format
123
+ if (options.format === "json") {
124
+ const json = JSON.stringify(result, null, 2);
125
+ if (options.output) {
126
+ fs_1.default.writeFileSync(options.output, json, "utf-8");
127
+ console.log(chalk_1.default.green(`āœ“ Report saved to ${options.output}`));
128
+ }
129
+ else {
130
+ console.log(json);
131
+ }
132
+ }
133
+ else if (options.format === "html") {
134
+ const html = generateHtmlReport(result);
135
+ const outputPath = options.output || "container-report.html";
136
+ fs_1.default.writeFileSync(outputPath, html, "utf-8");
137
+ console.log(chalk_1.default.green(`āœ“ HTML report saved to ${outputPath}`));
138
+ }
139
+ else {
140
+ outputResult(result, options);
141
+ }
142
+ }
143
+ exports.showProfileReport = showProfileReport;
144
+ /**
145
+ * Generate profile result from analysis
146
+ */
147
+ function generateProfileResult(analysis, options) {
148
+ const issues = [];
149
+ const recommendations = [];
150
+ // Check for security issues
151
+ if (!analysis.hasNonRootUser) {
152
+ issues.push({
153
+ id: "SEC001",
154
+ severity: "high",
155
+ category: "security",
156
+ message: "Container runs as root user",
157
+ fix: "Add 'USER node' or create a non-root user",
158
+ });
159
+ recommendations.push({
160
+ priority: "high",
161
+ category: "Security",
162
+ title: "Add non-root user",
163
+ description: "Running containers as root is a security risk",
164
+ impact: "Reduces attack surface if container is compromised",
165
+ });
166
+ }
167
+ if (!analysis.hasHealthCheck) {
168
+ issues.push({
169
+ id: "PERF001",
170
+ severity: "medium",
171
+ category: "performance",
172
+ message: "No HEALTHCHECK instruction found",
173
+ fix: "Add HEALTHCHECK instruction for orchestration support",
174
+ });
175
+ recommendations.push({
176
+ priority: "medium",
177
+ category: "Performance",
178
+ title: "Add health check",
179
+ description: "Health checks enable better orchestration",
180
+ impact: "Faster failure detection and recovery",
181
+ });
182
+ }
183
+ if (!analysis.hasMultiStage) {
184
+ issues.push({
185
+ id: "SIZE001",
186
+ severity: "medium",
187
+ category: "size",
188
+ message: "Not using multi-stage build",
189
+ fix: "Use multi-stage build to reduce final image size",
190
+ });
191
+ recommendations.push({
192
+ priority: "medium",
193
+ category: "Size",
194
+ title: "Use multi-stage build",
195
+ description: "Multi-stage builds reduce image size significantly",
196
+ impact: "Can reduce image size by 50-80%",
197
+ });
198
+ }
199
+ // Check for best practices
200
+ if (analysis.hasNpmInstallWithoutCi) {
201
+ issues.push({
202
+ id: "BP001",
203
+ severity: "low",
204
+ category: "best-practice",
205
+ message: "Using 'npm install' instead of 'npm ci'",
206
+ fix: "Replace 'npm install' with 'npm ci' for reproducible builds",
207
+ });
208
+ }
209
+ if (!analysis.hasDockerignore) {
210
+ issues.push({
211
+ id: "SIZE002",
212
+ severity: "low",
213
+ category: "size",
214
+ message: "No .dockerignore file found",
215
+ fix: "Create .dockerignore to exclude unnecessary files",
216
+ });
217
+ }
218
+ if (analysis.hasCurlOrWgetWithoutCleanup) {
219
+ issues.push({
220
+ id: "SIZE003",
221
+ severity: "low",
222
+ category: "size",
223
+ message: "Downloaded files may not be cleaned up",
224
+ fix: "Clean up downloaded files in the same RUN layer",
225
+ });
226
+ }
227
+ // Calculate score
228
+ let score = 100;
229
+ for (const issue of issues) {
230
+ switch (issue.severity) {
231
+ case "critical":
232
+ score -= 25;
233
+ break;
234
+ case "high":
235
+ score -= 15;
236
+ break;
237
+ case "medium":
238
+ score -= 10;
239
+ break;
240
+ case "low":
241
+ score -= 5;
242
+ break;
243
+ }
244
+ }
245
+ score = Math.max(0, score);
246
+ return {
247
+ score,
248
+ issues: issues.filter((i) => {
249
+ const severityOrder = { low: 0, medium: 1, high: 2, critical: 3 };
250
+ return severityOrder[i.severity] >= severityOrder[options.severity];
251
+ }),
252
+ recommendations,
253
+ metrics: {
254
+ baseImage: analysis.baseImage,
255
+ layers: analysis.layers,
256
+ hasMultiStage: analysis.hasMultiStage,
257
+ hasHealthCheck: analysis.hasHealthCheck,
258
+ hasNonRootUser: analysis.hasNonRootUser,
259
+ nodeVersion: analysis.nodeVersion,
260
+ },
261
+ };
262
+ }
263
+ /**
264
+ * Output result to console
265
+ */
266
+ function outputResult(result, options) {
267
+ // Score
268
+ const scoreColor = result.score >= 80
269
+ ? chalk_1.default.green
270
+ : result.score >= 60
271
+ ? chalk_1.default.yellow
272
+ : chalk_1.default.red;
273
+ console.log(chalk_1.default.bold(`Container Health Score: ${scoreColor(`${result.score}/100`)}\n`));
274
+ // Metrics
275
+ console.log(chalk_1.default.bold("Metrics:"));
276
+ console.log(` Base Image: ${result.metrics.baseImage || "Unknown"}`);
277
+ console.log(` Layers: ${result.metrics.layers || "Unknown"}`);
278
+ console.log(` Multi-stage: ${result.metrics.hasMultiStage ? chalk_1.default.green("āœ“") : chalk_1.default.red("āœ—")}`);
279
+ console.log(` Health Check: ${result.metrics.hasHealthCheck ? chalk_1.default.green("āœ“") : chalk_1.default.red("āœ—")}`);
280
+ console.log(` Non-root User: ${result.metrics.hasNonRootUser ? chalk_1.default.green("āœ“") : chalk_1.default.red("āœ—")}`);
281
+ // Issues
282
+ if (result.issues.length > 0) {
283
+ console.log(chalk_1.default.bold(`\nIssues (${result.issues.length}):`));
284
+ for (const issue of result.issues) {
285
+ const color = issue.severity === "critical"
286
+ ? chalk_1.default.red
287
+ : issue.severity === "high"
288
+ ? chalk_1.default.yellow
289
+ : issue.severity === "medium"
290
+ ? chalk_1.default.cyan
291
+ : chalk_1.default.gray;
292
+ console.log(` ${color(`[${issue.severity.toUpperCase()}]`)} ${issue.message}`);
293
+ if (issue.fix) {
294
+ console.log(chalk_1.default.gray(` Fix: ${issue.fix}`));
295
+ }
296
+ }
297
+ }
298
+ else {
299
+ console.log(chalk_1.default.green("\nāœ“ No issues found!"));
300
+ }
301
+ // Recommendations
302
+ if (result.recommendations.length > 0) {
303
+ console.log(chalk_1.default.bold(`\nRecommendations:`));
304
+ for (const rec of result.recommendations) {
305
+ const color = rec.priority === "high" ? chalk_1.default.yellow : chalk_1.default.gray;
306
+ console.log(` ${color(`[${rec.priority.toUpperCase()}]`)} ${rec.title}`);
307
+ console.log(chalk_1.default.gray(` ${rec.description}`));
308
+ }
309
+ }
310
+ console.log();
311
+ }
312
+ /**
313
+ * Generate HTML report
314
+ */
315
+ function generateHtmlReport(result) {
316
+ const scoreColor = result.score >= 80
317
+ ? "#22c55e"
318
+ : result.score >= 60
319
+ ? "#eab308"
320
+ : "#ef4444";
321
+ return `<!DOCTYPE html>
322
+ <html lang="en">
323
+ <head>
324
+ <meta charset="UTF-8">
325
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
326
+ <title>Container Profile Report - ExpressoTS</title>
327
+ <style>
328
+ * { margin: 0; padding: 0; box-sizing: border-box; }
329
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f3f4f6; padding: 2rem; }
330
+ .container { max-width: 800px; margin: 0 auto; }
331
+ .card { background: white; border-radius: 8px; padding: 1.5rem; margin-bottom: 1rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
332
+ h1 { color: #111; margin-bottom: 0.5rem; }
333
+ h2 { color: #333; margin-bottom: 1rem; font-size: 1.25rem; }
334
+ .score { font-size: 3rem; font-weight: bold; color: ${scoreColor}; }
335
+ .metric { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid #e5e7eb; }
336
+ .issue { padding: 0.75rem; margin: 0.5rem 0; border-radius: 4px; }
337
+ .issue.critical { background: #fef2f2; border-left: 4px solid #ef4444; }
338
+ .issue.high { background: #fffbeb; border-left: 4px solid #f59e0b; }
339
+ .issue.medium { background: #ecfeff; border-left: 4px solid #06b6d4; }
340
+ .issue.low { background: #f9fafb; border-left: 4px solid #9ca3af; }
341
+ .check { color: #22c55e; }
342
+ .cross { color: #ef4444; }
343
+ .footer { text-align: center; color: #6b7280; margin-top: 2rem; font-size: 0.875rem; }
344
+ </style>
345
+ </head>
346
+ <body>
347
+ <div class="container">
348
+ <div class="card">
349
+ <h1>🐳 Container Profile Report</h1>
350
+ <p>Generated by ExpressoTS CLI</p>
351
+ </div>
352
+
353
+ <div class="card">
354
+ <h2>Health Score</h2>
355
+ <div class="score">${result.score}/100</div>
356
+ </div>
357
+
358
+ <div class="card">
359
+ <h2>Metrics</h2>
360
+ <div class="metric"><span>Base Image</span><span>${result.metrics.baseImage || "Unknown"}</span></div>
361
+ <div class="metric"><span>Layers</span><span>${result.metrics.layers || "Unknown"}</span></div>
362
+ <div class="metric"><span>Multi-stage Build</span><span class="${result.metrics.hasMultiStage ? "check" : "cross"}">${result.metrics.hasMultiStage ? "āœ“" : "āœ—"}</span></div>
363
+ <div class="metric"><span>Health Check</span><span class="${result.metrics.hasHealthCheck ? "check" : "cross"}">${result.metrics.hasHealthCheck ? "āœ“" : "āœ—"}</span></div>
364
+ <div class="metric"><span>Non-root User</span><span class="${result.metrics.hasNonRootUser ? "check" : "cross"}">${result.metrics.hasNonRootUser ? "āœ“" : "āœ—"}</span></div>
365
+ </div>
366
+
367
+ <div class="card">
368
+ <h2>Issues (${result.issues.length})</h2>
369
+ ${result.issues.length === 0
370
+ ? '<p style="color: #22c55e;">āœ“ No issues found!</p>'
371
+ : result.issues
372
+ .map((i) => `
373
+ <div class="issue ${i.severity}">
374
+ <strong>[${i.severity.toUpperCase()}]</strong> ${i.message}
375
+ ${i.fix ? `<br><small style="color: #6b7280;">Fix: ${i.fix}</small>` : ""}
376
+ </div>
377
+ `)
378
+ .join("")}
379
+ </div>
380
+
381
+ <div class="card">
382
+ <h2>Recommendations (${result.recommendations.length})</h2>
383
+ ${result.recommendations
384
+ .map((r) => `
385
+ <div class="issue low">
386
+ <strong>${r.title}</strong><br>
387
+ ${r.description}<br>
388
+ <small style="color: #22c55e;">Impact: ${r.impact}</small>
389
+ </div>
390
+ `)
391
+ .join("")}
392
+ </div>
393
+
394
+ <div class="footer">
395
+ Generated by ExpressoTS CLI • ${new Date().toISOString()}
396
+ </div>
397
+ </div>
398
+ </body>
399
+ </html>`;
400
+ }
@@ -0,0 +1 @@
1
+ export { profileCommand } from "./cli";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.profileCommand = void 0;
4
+ var cli_1 = require("./cli");
5
+ Object.defineProperty(exports, "profileCommand", { enumerable: true, get: function () { return cli_1.profileCommand; } });
@@ -0,0 +1,19 @@
1
+ import type { DockerfileAnalysis } from "../analyzers/dockerfile-analyzer";
2
+ export interface Optimization {
3
+ id: string;
4
+ priority: "low" | "medium" | "high";
5
+ category: string;
6
+ title: string;
7
+ description: string;
8
+ impact: string;
9
+ autoFixable: boolean;
10
+ fix?: (content: string) => string;
11
+ }
12
+ /**
13
+ * Generate optimization recommendations based on Dockerfile analysis
14
+ */
15
+ export declare function generateOptimizations(analysis: DockerfileAnalysis): Optimization[];
16
+ /**
17
+ * Apply auto-fixable optimizations to Dockerfile
18
+ */
19
+ export declare function applyOptimizations(dockerfilePath: string, optimizations: Optimization[]): Promise<number>;