@expressots/cli 3.0.0-beta.4 → 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.
- package/README.md +41 -95
- package/bin/cicd/cli.d.ts +6 -0
- package/bin/cicd/cli.js +126 -0
- package/bin/cicd/form.d.ts +29 -0
- package/bin/cicd/form.js +345 -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 -1
- package/bin/cli.js +18 -3
- package/bin/commands/project.commands.d.ts +19 -6
- package/bin/commands/project.commands.js +390 -61
- 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 +154 -0
- package/bin/containerize/generators/ci-generator.d.ts +31 -0
- package/bin/containerize/generators/ci-generator.js +936 -0
- package/bin/containerize/generators/docker-compose-generator.d.ts +8 -0
- package/bin/containerize/generators/docker-compose-generator.js +186 -0
- package/bin/containerize/generators/dockerfile-generator.d.ts +8 -0
- package/bin/containerize/generators/dockerfile-generator.js +635 -0
- package/bin/containerize/generators/kubernetes-generator.d.ts +8 -0
- package/bin/containerize/generators/kubernetes-generator.js +133 -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 +183 -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 +134 -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.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 +7 -3
- package/bin/generate/utils/command-utils.js +95 -31
- 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 +112 -7
- package/bin/generate/utils/string-utils.d.ts +6 -0
- package/bin/generate/utils/string-utils.js +13 -1
- package/bin/help/form.js +11 -3
- 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 +96 -0
- package/bin/migrate/form.d.ts +25 -0
- package/bin/migrate/form.js +347 -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.js +21 -6
- package/bin/new/form.d.ts +25 -4
- package/bin/new/form.js +285 -70
- 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 +92 -0
- package/bin/profile/form.d.ts +56 -0
- package/bin/profile/form.js +400 -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 +2 -1
- package/bin/scripts/form.js +27 -5
- package/bin/studio/cli.d.ts +15 -0
- package/bin/studio/cli.js +166 -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 +292 -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 +2 -2
- package/bin/utils/add-module-to-container.js +15 -5
- package/bin/utils/cli-ui.d.ts +30 -3
- package/bin/utils/cli-ui.js +95 -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 +286 -0
- package/package.json +154 -154
|
@@ -0,0 +1,412 @@
|
|
|
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.showPricing = exports.optimizeCosts = exports.compareCosts = exports.estimateCosts = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const project_analyzer_1 = require("../containerize/analyzers/project-analyzer");
|
|
10
|
+
const providers_1 = require("./providers");
|
|
11
|
+
const pricing_manager_1 = require("./pricing-manager");
|
|
12
|
+
const PROVIDERS = [
|
|
13
|
+
"aws",
|
|
14
|
+
"gcp",
|
|
15
|
+
"azure",
|
|
16
|
+
"railway",
|
|
17
|
+
"render",
|
|
18
|
+
"fly",
|
|
19
|
+
"digitalocean",
|
|
20
|
+
"heroku",
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Estimate costs for a specific provider
|
|
24
|
+
*/
|
|
25
|
+
async function estimateCosts(options) {
|
|
26
|
+
console.log(chalk_1.default.cyan("\n💰 ExpressoTS Cost Estimator\n"));
|
|
27
|
+
// Analyze project for resource estimates
|
|
28
|
+
const analysis = await (0, project_analyzer_1.analyzeProject)();
|
|
29
|
+
// Use project analysis to suggest resources if not specified
|
|
30
|
+
// Parse estimated values from analysis (e.g., "256Mi" -> 0.25)
|
|
31
|
+
const parseMemory = (mem) => {
|
|
32
|
+
if (!mem)
|
|
33
|
+
return 1;
|
|
34
|
+
const match = mem.match(/(\d+)/);
|
|
35
|
+
if (!match)
|
|
36
|
+
return 1;
|
|
37
|
+
const value = parseInt(match[1], 10);
|
|
38
|
+
if (mem.includes("Mi"))
|
|
39
|
+
return value / 1024;
|
|
40
|
+
if (mem.includes("Gi"))
|
|
41
|
+
return value;
|
|
42
|
+
return value / 1024; // Assume MiB
|
|
43
|
+
};
|
|
44
|
+
const parseCpu = (cpu) => {
|
|
45
|
+
if (!cpu)
|
|
46
|
+
return 1;
|
|
47
|
+
const match = cpu.match(/(\d+)/);
|
|
48
|
+
if (!match)
|
|
49
|
+
return 1;
|
|
50
|
+
const value = parseInt(match[1], 10);
|
|
51
|
+
if (cpu.includes("m"))
|
|
52
|
+
return value / 1000;
|
|
53
|
+
return value;
|
|
54
|
+
};
|
|
55
|
+
const resources = {
|
|
56
|
+
instances: options.instances,
|
|
57
|
+
cpu: options.cpu || parseCpu(analysis?.estimatedCpu) || 1,
|
|
58
|
+
memory: options.memory || parseMemory(analysis?.estimatedMemory) || 1,
|
|
59
|
+
storage: options.storage,
|
|
60
|
+
bandwidth: options.bandwidth,
|
|
61
|
+
hours: options.hours,
|
|
62
|
+
};
|
|
63
|
+
console.log(chalk_1.default.bold("Resource Configuration:"));
|
|
64
|
+
console.log(` Instances: ${resources.instances}`);
|
|
65
|
+
console.log(` CPU: ${resources.cpu} vCPU`);
|
|
66
|
+
console.log(` Memory: ${resources.memory} GB`);
|
|
67
|
+
console.log(` Storage: ${resources.storage} GB`);
|
|
68
|
+
console.log(` Bandwidth: ${resources.bandwidth} GB/month`);
|
|
69
|
+
console.log(` Running Hours: ${resources.hours}/month`);
|
|
70
|
+
console.log();
|
|
71
|
+
if (options.provider) {
|
|
72
|
+
// Estimate for specific provider
|
|
73
|
+
const estimate = calculateEstimate(options.provider, resources, options);
|
|
74
|
+
printEstimate(estimate, options);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// Show all providers for comparison
|
|
78
|
+
await compareCosts(options);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.estimateCosts = estimateCosts;
|
|
82
|
+
/**
|
|
83
|
+
* Compare costs across all providers
|
|
84
|
+
*/
|
|
85
|
+
async function compareCosts(options) {
|
|
86
|
+
console.log(chalk_1.default.cyan("\n📊 Cost Comparison Across Providers\n"));
|
|
87
|
+
const resources = {
|
|
88
|
+
instances: options.instances,
|
|
89
|
+
cpu: options.cpu,
|
|
90
|
+
memory: options.memory,
|
|
91
|
+
storage: options.storage,
|
|
92
|
+
bandwidth: options.bandwidth,
|
|
93
|
+
hours: options.hours,
|
|
94
|
+
};
|
|
95
|
+
const estimates = [];
|
|
96
|
+
for (const provider of PROVIDERS) {
|
|
97
|
+
const estimate = calculateEstimate(provider, resources, options);
|
|
98
|
+
estimates.push(estimate);
|
|
99
|
+
}
|
|
100
|
+
// Sort by cost
|
|
101
|
+
estimates.sort((a, b) => a.monthlyCost - b.monthlyCost);
|
|
102
|
+
if (options.format === "json") {
|
|
103
|
+
const json = JSON.stringify(estimates, null, 2);
|
|
104
|
+
if (options.output) {
|
|
105
|
+
fs_1.default.writeFileSync(options.output, json, "utf-8");
|
|
106
|
+
console.log(chalk_1.default.green(`✓ Saved to ${options.output}`));
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
console.log(json);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (options.format === "markdown") {
|
|
114
|
+
const markdown = generateMarkdownReport(estimates, resources);
|
|
115
|
+
if (options.output) {
|
|
116
|
+
fs_1.default.writeFileSync(options.output, markdown, "utf-8");
|
|
117
|
+
console.log(chalk_1.default.green(`✓ Saved to ${options.output}`));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(markdown);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Text format
|
|
125
|
+
console.log(chalk_1.default.bold("Monthly Cost Estimates:"));
|
|
126
|
+
console.log("-".repeat(60));
|
|
127
|
+
console.log("Provider".padEnd(20) +
|
|
128
|
+
"Service".padEnd(15) +
|
|
129
|
+
"Monthly Cost".padEnd(15) +
|
|
130
|
+
"Rank");
|
|
131
|
+
console.log("-".repeat(60));
|
|
132
|
+
let rank = 1;
|
|
133
|
+
const cheapest = estimates[0].monthlyCost;
|
|
134
|
+
for (const est of estimates) {
|
|
135
|
+
const costDiff = est.monthlyCost - cheapest;
|
|
136
|
+
const diffStr = costDiff > 0
|
|
137
|
+
? chalk_1.default.gray(`+$${costDiff.toFixed(2)}`)
|
|
138
|
+
: chalk_1.default.green("Best");
|
|
139
|
+
const providerColor = rank === 1 ? chalk_1.default.green : rank <= 3 ? chalk_1.default.yellow : chalk_1.default.white;
|
|
140
|
+
console.log(providerColor(est.provider.padEnd(20)) +
|
|
141
|
+
est.service.padEnd(15) +
|
|
142
|
+
chalk_1.default.cyan(`$${est.monthlyCost.toFixed(2)}`.padEnd(15)) +
|
|
143
|
+
diffStr);
|
|
144
|
+
rank++;
|
|
145
|
+
}
|
|
146
|
+
console.log("-".repeat(60));
|
|
147
|
+
console.log();
|
|
148
|
+
// Show recommendations
|
|
149
|
+
console.log(chalk_1.default.bold("💡 Recommendations:"));
|
|
150
|
+
console.log(` Cheapest: ${chalk_1.default.green(estimates[0].provider)} at $${estimates[0].monthlyCost.toFixed(2)}/month`);
|
|
151
|
+
if (estimates.length > 1) {
|
|
152
|
+
const savingsVsHeroku = estimates.find((e) => e.provider === "heroku");
|
|
153
|
+
if (savingsVsHeroku && savingsVsHeroku.monthlyCost > cheapest) {
|
|
154
|
+
const savings = savingsVsHeroku.monthlyCost - cheapest;
|
|
155
|
+
console.log(` Save ${chalk_1.default.yellow(`$${savings.toFixed(2)}/month`)} vs Heroku by switching to ${estimates[0].provider}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// PaaS vs Cloud recommendation
|
|
159
|
+
const paasProviders = ["railway", "render", "fly", "heroku"];
|
|
160
|
+
const cloudProviders = ["aws", "gcp", "azure"];
|
|
161
|
+
const cheapestPaaS = estimates.find((e) => paasProviders.includes(e.provider));
|
|
162
|
+
const cheapestCloud = estimates.find((e) => cloudProviders.includes(e.provider));
|
|
163
|
+
if (cheapestPaaS && cheapestCloud) {
|
|
164
|
+
if (cheapestPaaS.monthlyCost < cheapestCloud.monthlyCost) {
|
|
165
|
+
console.log(` For simplicity: Use ${chalk_1.default.cyan(cheapestPaaS.provider)} (PaaS)`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(` For control: Use ${chalk_1.default.cyan(cheapestCloud.provider)} (IaaS) - ${chalk_1.default.gray("requires more setup")}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
console.log();
|
|
172
|
+
}
|
|
173
|
+
exports.compareCosts = compareCosts;
|
|
174
|
+
/**
|
|
175
|
+
* Suggest cost optimizations
|
|
176
|
+
*/
|
|
177
|
+
async function optimizeCosts(options) {
|
|
178
|
+
console.log(chalk_1.default.cyan("\n⚡ Cost Optimization Suggestions\n"));
|
|
179
|
+
const analysis = await (0, project_analyzer_1.analyzeProject)();
|
|
180
|
+
const recommendations = [];
|
|
181
|
+
// Check current resource usage
|
|
182
|
+
if (options.cpu > 1 && options.memory <= 2) {
|
|
183
|
+
recommendations.push({
|
|
184
|
+
priority: "HIGH",
|
|
185
|
+
title: "Right-size CPU allocation",
|
|
186
|
+
savings: "20-40%",
|
|
187
|
+
description: "Your memory is low compared to CPU. Consider a smaller instance type.",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (options.instances > 1 && options.hours < 720) {
|
|
191
|
+
recommendations.push({
|
|
192
|
+
priority: "HIGH",
|
|
193
|
+
title: "Use auto-scaling",
|
|
194
|
+
savings: "30-50%",
|
|
195
|
+
description: "With variable hours, auto-scaling can reduce costs during low-traffic periods.",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (options.storage > 50) {
|
|
199
|
+
recommendations.push({
|
|
200
|
+
priority: "MEDIUM",
|
|
201
|
+
title: "Optimize storage",
|
|
202
|
+
savings: "10-20%",
|
|
203
|
+
description: "Consider using object storage (S3/GCS) for static files instead of block storage.",
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// Provider-specific recommendations
|
|
207
|
+
if (options.provider === "heroku") {
|
|
208
|
+
recommendations.push({
|
|
209
|
+
priority: "HIGH",
|
|
210
|
+
title: "Migrate from Heroku",
|
|
211
|
+
savings: "40-60%",
|
|
212
|
+
description: "Railway, Render, or Fly.io offer similar DX at lower prices.",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// General recommendations
|
|
216
|
+
recommendations.push({
|
|
217
|
+
priority: "MEDIUM",
|
|
218
|
+
title: "Use reserved/committed instances",
|
|
219
|
+
savings: "20-40%",
|
|
220
|
+
description: "If your workload is predictable, commit to 1-3 year reserved instances.",
|
|
221
|
+
});
|
|
222
|
+
recommendations.push({
|
|
223
|
+
priority: "LOW",
|
|
224
|
+
title: "Enable spot/preemptible instances",
|
|
225
|
+
savings: "50-80%",
|
|
226
|
+
description: "For fault-tolerant workloads, use spot instances for significant savings.",
|
|
227
|
+
});
|
|
228
|
+
// Print recommendations
|
|
229
|
+
for (const rec of recommendations) {
|
|
230
|
+
const color = rec.priority === "HIGH"
|
|
231
|
+
? chalk_1.default.red
|
|
232
|
+
: rec.priority === "MEDIUM"
|
|
233
|
+
? chalk_1.default.yellow
|
|
234
|
+
: chalk_1.default.gray;
|
|
235
|
+
console.log(`${color(`[${rec.priority}]`)} ${chalk_1.default.bold(rec.title)}`);
|
|
236
|
+
console.log(` Potential Savings: ${chalk_1.default.green(rec.savings)}`);
|
|
237
|
+
console.log(` ${chalk_1.default.gray(rec.description)}`);
|
|
238
|
+
console.log();
|
|
239
|
+
}
|
|
240
|
+
// Summary
|
|
241
|
+
console.log(chalk_1.default.bold("📊 Quick Wins:"));
|
|
242
|
+
console.log(" 1. Right-size your instances based on actual usage");
|
|
243
|
+
console.log(" 2. Use auto-scaling for variable workloads");
|
|
244
|
+
console.log(" 3. Consider PaaS alternatives for simplicity + cost savings");
|
|
245
|
+
console.log();
|
|
246
|
+
}
|
|
247
|
+
exports.optimizeCosts = optimizeCosts;
|
|
248
|
+
/**
|
|
249
|
+
* Show pricing information for a provider
|
|
250
|
+
*/
|
|
251
|
+
async function showPricing(options) {
|
|
252
|
+
console.log(chalk_1.default.cyan("\n📋 Cloud Provider Pricing Information\n"));
|
|
253
|
+
// Try to get dynamic pricing from PricingManager
|
|
254
|
+
const pricingManager = (0, pricing_manager_1.getPricingManager)();
|
|
255
|
+
const dynamicPricing = await pricingManager.fetchPricing();
|
|
256
|
+
const source = pricingManager.getLastSource();
|
|
257
|
+
if (dynamicPricing) {
|
|
258
|
+
console.log(chalk_1.default.gray(` Source: ${source || "dynamic"} (v${dynamicPricing.version})`));
|
|
259
|
+
console.log();
|
|
260
|
+
}
|
|
261
|
+
if (options.provider) {
|
|
262
|
+
// Try dynamic pricing first, fall back to hardcoded
|
|
263
|
+
const dynamicProvider = dynamicPricing?.providers[options.provider];
|
|
264
|
+
const pricing = dynamicProvider || (0, providers_1.getPricing)(options.provider);
|
|
265
|
+
printProviderPricing(options.provider, pricing);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
// Show all providers
|
|
269
|
+
for (const provider of PROVIDERS) {
|
|
270
|
+
const dynamicProvider = dynamicPricing?.providers[provider];
|
|
271
|
+
const pricing = dynamicProvider || (0, providers_1.getPricing)(provider);
|
|
272
|
+
printProviderPricing(provider, pricing);
|
|
273
|
+
console.log();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
console.log(chalk_1.default.gray("Note: Prices are estimates and may vary by region. Check provider websites for current pricing."));
|
|
277
|
+
if (!dynamicPricing) {
|
|
278
|
+
console.log(chalk_1.default.yellow(" Using embedded pricing data. Run 'expressots costs update' to fetch latest prices."));
|
|
279
|
+
}
|
|
280
|
+
console.log();
|
|
281
|
+
}
|
|
282
|
+
exports.showPricing = showPricing;
|
|
283
|
+
/**
|
|
284
|
+
* Get pricing for a provider (dynamic or fallback)
|
|
285
|
+
*/
|
|
286
|
+
async function getDynamicOrFallbackPricing(provider) {
|
|
287
|
+
const pricingManager = (0, pricing_manager_1.getPricingManager)();
|
|
288
|
+
const dynamicPricing = await pricingManager.fetchPricing();
|
|
289
|
+
if (dynamicPricing?.providers[provider]) {
|
|
290
|
+
return dynamicPricing.providers[provider];
|
|
291
|
+
}
|
|
292
|
+
return (0, providers_1.getPricing)(provider);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Calculate cost estimate for a provider
|
|
296
|
+
*/
|
|
297
|
+
function calculateEstimate(provider, resources, options) {
|
|
298
|
+
// Note: This function is sync for backward compat, uses cached pricing from PricingManager if available
|
|
299
|
+
const pricing = (0, providers_1.getPricing)(provider);
|
|
300
|
+
// Calculate based on provider pricing model
|
|
301
|
+
const hoursRatio = resources.hours / 720; // Ratio of running hours
|
|
302
|
+
let computeCost = 0;
|
|
303
|
+
let memoryCost = 0;
|
|
304
|
+
const storageCost = resources.storage * pricing.storagePerGb;
|
|
305
|
+
const bandwidthCost = Math.max(0, resources.bandwidth - pricing.freeBandwidth) *
|
|
306
|
+
pricing.bandwidthPerGb;
|
|
307
|
+
// Provider-specific calculations
|
|
308
|
+
if (pricing.model === "per-hour") {
|
|
309
|
+
computeCost =
|
|
310
|
+
resources.instances *
|
|
311
|
+
resources.cpu *
|
|
312
|
+
pricing.cpuPerHour *
|
|
313
|
+
resources.hours;
|
|
314
|
+
memoryCost =
|
|
315
|
+
resources.instances *
|
|
316
|
+
resources.memory *
|
|
317
|
+
pricing.memoryPerGbHour *
|
|
318
|
+
resources.hours;
|
|
319
|
+
}
|
|
320
|
+
else if (pricing.model === "per-month") {
|
|
321
|
+
computeCost = resources.instances * pricing.basePrice * hoursRatio;
|
|
322
|
+
}
|
|
323
|
+
else if (pricing.model === "usage") {
|
|
324
|
+
// Usage-based (like serverless)
|
|
325
|
+
computeCost = resources.instances * pricing.basePrice;
|
|
326
|
+
memoryCost =
|
|
327
|
+
resources.memory * pricing.memoryPerGbHour * resources.hours;
|
|
328
|
+
}
|
|
329
|
+
const totalCost = computeCost + memoryCost + storageCost + bandwidthCost;
|
|
330
|
+
return {
|
|
331
|
+
provider: provider.charAt(0).toUpperCase() + provider.slice(1),
|
|
332
|
+
service: pricing.serviceName,
|
|
333
|
+
monthlyCost: Math.round(totalCost * 100) / 100,
|
|
334
|
+
breakdown: {
|
|
335
|
+
compute: Math.round(computeCost * 100) / 100,
|
|
336
|
+
memory: Math.round(memoryCost * 100) / 100,
|
|
337
|
+
storage: Math.round(storageCost * 100) / 100,
|
|
338
|
+
bandwidth: Math.round(bandwidthCost * 100) / 100,
|
|
339
|
+
other: 0,
|
|
340
|
+
},
|
|
341
|
+
currency: "USD",
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Print single estimate
|
|
346
|
+
*/
|
|
347
|
+
function printEstimate(estimate, options) {
|
|
348
|
+
console.log(chalk_1.default.bold(`${estimate.provider} - ${estimate.service}`));
|
|
349
|
+
console.log("-".repeat(40));
|
|
350
|
+
console.log(` Compute: $${estimate.breakdown.compute.toFixed(2)}`);
|
|
351
|
+
if (estimate.breakdown.memory) {
|
|
352
|
+
console.log(` Memory: $${estimate.breakdown.memory.toFixed(2)}`);
|
|
353
|
+
}
|
|
354
|
+
console.log(` Storage: $${estimate.breakdown.storage.toFixed(2)}`);
|
|
355
|
+
console.log(` Bandwidth: $${estimate.breakdown.bandwidth.toFixed(2)}`);
|
|
356
|
+
console.log("-".repeat(40));
|
|
357
|
+
console.log(chalk_1.default.bold.cyan(` Total: $${estimate.monthlyCost.toFixed(2)}/month`));
|
|
358
|
+
console.log();
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Print provider pricing info
|
|
362
|
+
*/
|
|
363
|
+
function printProviderPricing(provider, pricing) {
|
|
364
|
+
console.log(chalk_1.default.bold(`${provider.toUpperCase()} - ${pricing.serviceName}`));
|
|
365
|
+
console.log(` Model: ${pricing.model}`);
|
|
366
|
+
console.log(` Base Price: $${pricing.basePrice}/month`);
|
|
367
|
+
console.log(` CPU: $${pricing.cpuPerHour}/hour`);
|
|
368
|
+
console.log(` Memory: $${pricing.memoryPerGbHour}/GB-hour`);
|
|
369
|
+
console.log(` Storage: $${pricing.storagePerGb}/GB-month`);
|
|
370
|
+
console.log(` Bandwidth: $${pricing.bandwidthPerGb}/GB (${pricing.freeBandwidth}GB free)`);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Generate markdown report
|
|
374
|
+
*/
|
|
375
|
+
function generateMarkdownReport(estimates, resources) {
|
|
376
|
+
let md = `# Cloud Cost Comparison Report
|
|
377
|
+
|
|
378
|
+
Generated by ExpressoTS CLI
|
|
379
|
+
|
|
380
|
+
## Resource Configuration
|
|
381
|
+
|
|
382
|
+
| Resource | Value |
|
|
383
|
+
|----------|-------|
|
|
384
|
+
| Instances | ${resources.instances} |
|
|
385
|
+
| CPU | ${resources.cpu} vCPU |
|
|
386
|
+
| Memory | ${resources.memory} GB |
|
|
387
|
+
| Storage | ${resources.storage} GB |
|
|
388
|
+
| Bandwidth | ${resources.bandwidth} GB/month |
|
|
389
|
+
| Hours | ${resources.hours}/month |
|
|
390
|
+
|
|
391
|
+
## Cost Comparison
|
|
392
|
+
|
|
393
|
+
| Provider | Service | Monthly Cost | Breakdown |
|
|
394
|
+
|----------|---------|--------------|-----------|
|
|
395
|
+
`;
|
|
396
|
+
for (const est of estimates) {
|
|
397
|
+
const breakdown = `Compute: $${est.breakdown.compute}, Storage: $${est.breakdown.storage}`;
|
|
398
|
+
md += `| ${est.provider} | ${est.service} | **$${est.monthlyCost.toFixed(2)}** | ${breakdown} |\n`;
|
|
399
|
+
}
|
|
400
|
+
md += `
|
|
401
|
+
## Recommendations
|
|
402
|
+
|
|
403
|
+
1. **Cheapest Option**: ${estimates[0].provider} at $${estimates[0].monthlyCost.toFixed(2)}/month
|
|
404
|
+
2. Consider PaaS for simplicity (Railway, Render, Fly.io)
|
|
405
|
+
3. Use auto-scaling for variable workloads
|
|
406
|
+
4. Right-size instances based on actual usage
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
*Prices are estimates. Check provider websites for current pricing.*
|
|
410
|
+
`;
|
|
411
|
+
return md;
|
|
412
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.resetPricingManager = exports.getPricingManager = exports.PricingManager = exports.costsCommand = void 0;
|
|
18
|
+
var cli_1 = require("./cli");
|
|
19
|
+
Object.defineProperty(exports, "costsCommand", { enumerable: true, get: function () { return cli_1.costsCommand; } });
|
|
20
|
+
__exportStar(require("./types"), exports);
|
|
21
|
+
var pricing_manager_1 = require("./pricing-manager");
|
|
22
|
+
Object.defineProperty(exports, "PricingManager", { enumerable: true, get: function () { return pricing_manager_1.PricingManager; } });
|
|
23
|
+
Object.defineProperty(exports, "getPricingManager", { enumerable: true, get: function () { return pricing_manager_1.getPricingManager; } });
|
|
24
|
+
Object.defineProperty(exports, "resetPricingManager", { enumerable: true, get: function () { return pricing_manager_1.resetPricingManager; } });
|
|
25
|
+
__exportStar(require("./sources"), exports);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pricing Manager - orchestrates pricing fetching with cascading fallback
|
|
3
|
+
* Priority: API -> Remote JSON -> Local JSON -> Error
|
|
4
|
+
*/
|
|
5
|
+
import type { PricingData, CloudProvider, ProviderPricing, ResourceEstimate, CostEstimate } from "./types";
|
|
6
|
+
export interface PricingManagerConfig {
|
|
7
|
+
sources?: ("api" | "remote" | "local")[];
|
|
8
|
+
cacheTTL?: number;
|
|
9
|
+
customLocalFile?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class PricingManager {
|
|
12
|
+
private sources;
|
|
13
|
+
private cacheTTL;
|
|
14
|
+
private cachedData;
|
|
15
|
+
private lastSource;
|
|
16
|
+
constructor(config?: PricingManagerConfig);
|
|
17
|
+
/**
|
|
18
|
+
* Ensure cache directory exists
|
|
19
|
+
*/
|
|
20
|
+
private ensureCacheDir;
|
|
21
|
+
/**
|
|
22
|
+
* Load cached pricing data
|
|
23
|
+
*/
|
|
24
|
+
private loadCache;
|
|
25
|
+
/**
|
|
26
|
+
* Save pricing data to cache
|
|
27
|
+
*/
|
|
28
|
+
private saveCache;
|
|
29
|
+
/**
|
|
30
|
+
* Fetch pricing data from sources with cascading fallback
|
|
31
|
+
*/
|
|
32
|
+
fetchPricing(forceRefresh?: boolean): Promise<PricingData | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Validate pricing data structure
|
|
35
|
+
*/
|
|
36
|
+
validatePricing(data: PricingData): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Get pricing for a specific provider
|
|
39
|
+
*/
|
|
40
|
+
getProviderPricing(provider: CloudProvider): Promise<ProviderPricing | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Get all provider pricing
|
|
43
|
+
*/
|
|
44
|
+
getAllPricing(): Promise<Record<CloudProvider, ProviderPricing> | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Calculate monthly cost for a provider
|
|
47
|
+
*/
|
|
48
|
+
calculateMonthlyCost(provider: CloudProvider, resources: ResourceEstimate): Promise<CostEstimate | null>;
|
|
49
|
+
/**
|
|
50
|
+
* Compare costs across all providers
|
|
51
|
+
*/
|
|
52
|
+
compareCosts(resources: ResourceEstimate): Promise<CostEstimate[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Update cached pricing
|
|
55
|
+
*/
|
|
56
|
+
updateCache(): Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Clear pricing cache
|
|
59
|
+
*/
|
|
60
|
+
clearCache(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Get the source of the last fetch
|
|
63
|
+
*/
|
|
64
|
+
getLastSource(): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Get pricing info (version, last updated, source)
|
|
67
|
+
*/
|
|
68
|
+
getInfo(): Promise<{
|
|
69
|
+
version: string;
|
|
70
|
+
updated: string;
|
|
71
|
+
source: string | null;
|
|
72
|
+
cacheAge: number | null;
|
|
73
|
+
} | null>;
|
|
74
|
+
/**
|
|
75
|
+
* Print pricing status (for CLI output)
|
|
76
|
+
*/
|
|
77
|
+
printStatus(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Get list of available providers
|
|
80
|
+
*/
|
|
81
|
+
getAvailableProviders(): Promise<CloudProvider[]>;
|
|
82
|
+
}
|
|
83
|
+
export declare function getPricingManager(config?: PricingManagerConfig): PricingManager;
|
|
84
|
+
export declare function resetPricingManager(): void;
|