@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,342 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pricing Manager - orchestrates pricing fetching with cascading fallback
|
|
4
|
+
* Priority: API -> Remote JSON -> Local JSON -> Error
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.resetPricingManager = exports.getPricingManager = exports.PricingManager = void 0;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const os_1 = __importDefault(require("os"));
|
|
14
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
15
|
+
const api_source_1 = require("./sources/api-source");
|
|
16
|
+
const remote_json_source_1 = require("./sources/remote-json-source");
|
|
17
|
+
const local_json_source_1 = require("./sources/local-json-source");
|
|
18
|
+
const config_1 = require("../config");
|
|
19
|
+
const CACHE_DIR = path_1.default.join(os_1.default.homedir(), ".expressots", "cache");
|
|
20
|
+
const CACHE_FILE = path_1.default.join(CACHE_DIR, "pricing.json");
|
|
21
|
+
const DEFAULT_TTL = 21600; // 6 hours in seconds
|
|
22
|
+
class PricingManager {
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.sources = [];
|
|
25
|
+
this.cachedData = null;
|
|
26
|
+
this.lastSource = null;
|
|
27
|
+
const globalConfig = (0, config_1.getConfigManager)().getPricingConfig();
|
|
28
|
+
const sourcesToUse = config?.sources || globalConfig.sources;
|
|
29
|
+
this.cacheTTL =
|
|
30
|
+
config?.cacheTTL || globalConfig.cacheTTL || DEFAULT_TTL;
|
|
31
|
+
// Initialize sources based on configuration
|
|
32
|
+
for (const source of sourcesToUse) {
|
|
33
|
+
switch (source) {
|
|
34
|
+
case "api":
|
|
35
|
+
this.sources.push((0, api_source_1.createAPIPricingSource)());
|
|
36
|
+
break;
|
|
37
|
+
case "remote":
|
|
38
|
+
this.sources.push((0, remote_json_source_1.createRemoteJSONPricingSource)());
|
|
39
|
+
break;
|
|
40
|
+
case "local":
|
|
41
|
+
this.sources.push((0, local_json_source_1.createLocalJSONPricingSource)(config?.customLocalFile ||
|
|
42
|
+
globalConfig.customFile ||
|
|
43
|
+
undefined));
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Ensure cache directory exists
|
|
50
|
+
*/
|
|
51
|
+
ensureCacheDir() {
|
|
52
|
+
if (!fs_1.default.existsSync(CACHE_DIR)) {
|
|
53
|
+
fs_1.default.mkdirSync(CACHE_DIR, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Load cached pricing data
|
|
58
|
+
*/
|
|
59
|
+
loadCache() {
|
|
60
|
+
try {
|
|
61
|
+
if (!fs_1.default.existsSync(CACHE_FILE)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const content = fs_1.default.readFileSync(CACHE_FILE, "utf-8");
|
|
65
|
+
const entry = JSON.parse(content);
|
|
66
|
+
// Check if cache is still valid
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const expiresAt = entry.timestamp + this.cacheTTL * 1000;
|
|
69
|
+
if (now < expiresAt) {
|
|
70
|
+
return entry;
|
|
71
|
+
}
|
|
72
|
+
return null; // Cache expired
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Save pricing data to cache
|
|
80
|
+
*/
|
|
81
|
+
saveCache(data, source) {
|
|
82
|
+
this.ensureCacheDir();
|
|
83
|
+
const entry = {
|
|
84
|
+
data,
|
|
85
|
+
timestamp: Date.now(),
|
|
86
|
+
source,
|
|
87
|
+
};
|
|
88
|
+
try {
|
|
89
|
+
fs_1.default.writeFileSync(CACHE_FILE, JSON.stringify(entry, null, 2), "utf-8");
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Silently fail - cache is optional
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Fetch pricing data from sources with cascading fallback
|
|
97
|
+
*/
|
|
98
|
+
async fetchPricing(forceRefresh = false) {
|
|
99
|
+
// Check memory cache first
|
|
100
|
+
if (!forceRefresh && this.cachedData) {
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
const expiresAt = this.cachedData.timestamp + this.cacheTTL * 1000;
|
|
103
|
+
if (now < expiresAt) {
|
|
104
|
+
this.lastSource = this.cachedData.source + " (memory)";
|
|
105
|
+
return this.cachedData.data;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Check disk cache
|
|
109
|
+
if (!forceRefresh) {
|
|
110
|
+
const diskCache = this.loadCache();
|
|
111
|
+
if (diskCache) {
|
|
112
|
+
this.cachedData = diskCache;
|
|
113
|
+
this.lastSource = diskCache.source + " (disk cache)";
|
|
114
|
+
return diskCache.data;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Try each source in order
|
|
118
|
+
for (const source of this.sources) {
|
|
119
|
+
try {
|
|
120
|
+
const data = await source.fetch();
|
|
121
|
+
if (data && this.validatePricing(data)) {
|
|
122
|
+
this.lastSource = source.name;
|
|
123
|
+
this.cachedData = {
|
|
124
|
+
data,
|
|
125
|
+
timestamp: Date.now(),
|
|
126
|
+
source: source.name,
|
|
127
|
+
};
|
|
128
|
+
this.saveCache(data, source.name);
|
|
129
|
+
return data;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Continue to next source
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this.lastSource = null;
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Validate pricing data structure
|
|
141
|
+
*/
|
|
142
|
+
validatePricing(data) {
|
|
143
|
+
if (!data.version || !data.providers) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
// Check that at least one provider exists
|
|
147
|
+
if (Object.keys(data.providers).length === 0) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
// Validate each provider has required fields
|
|
151
|
+
for (const provider of Object.values(data.providers)) {
|
|
152
|
+
if (typeof provider.serviceName !== "string" ||
|
|
153
|
+
typeof provider.model !== "string" ||
|
|
154
|
+
typeof provider.cpuPerHour !== "number" ||
|
|
155
|
+
typeof provider.memoryPerGbHour !== "number") {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get pricing for a specific provider
|
|
163
|
+
*/
|
|
164
|
+
async getProviderPricing(provider) {
|
|
165
|
+
const pricing = await this.fetchPricing();
|
|
166
|
+
if (!pricing) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return pricing.providers[provider] || null;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get all provider pricing
|
|
173
|
+
*/
|
|
174
|
+
async getAllPricing() {
|
|
175
|
+
const pricing = await this.fetchPricing();
|
|
176
|
+
if (!pricing) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
return pricing.providers;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Calculate monthly cost for a provider
|
|
183
|
+
*/
|
|
184
|
+
async calculateMonthlyCost(provider, resources) {
|
|
185
|
+
const pricing = await this.getProviderPricing(provider);
|
|
186
|
+
if (!pricing) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const hoursRatio = resources.hours / 720;
|
|
190
|
+
let computeCost = 0;
|
|
191
|
+
let baseCost = 0;
|
|
192
|
+
switch (pricing.model) {
|
|
193
|
+
case "per-hour":
|
|
194
|
+
computeCost =
|
|
195
|
+
resources.instances *
|
|
196
|
+
(resources.cpu * pricing.cpuPerHour * resources.hours +
|
|
197
|
+
resources.memory *
|
|
198
|
+
pricing.memoryPerGbHour *
|
|
199
|
+
resources.hours);
|
|
200
|
+
break;
|
|
201
|
+
case "per-month":
|
|
202
|
+
baseCost = resources.instances * pricing.basePrice * hoursRatio;
|
|
203
|
+
break;
|
|
204
|
+
case "usage":
|
|
205
|
+
baseCost = pricing.basePrice;
|
|
206
|
+
computeCost =
|
|
207
|
+
resources.instances *
|
|
208
|
+
(resources.cpu * pricing.cpuPerHour * resources.hours +
|
|
209
|
+
resources.memory *
|
|
210
|
+
pricing.memoryPerGbHour *
|
|
211
|
+
resources.hours);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
// Storage cost
|
|
215
|
+
const storageCost = resources.storage * pricing.storagePerGb;
|
|
216
|
+
// Bandwidth cost (above free tier)
|
|
217
|
+
const billableBandwidth = Math.max(0, resources.bandwidth - pricing.freeBandwidth);
|
|
218
|
+
const bandwidthCost = billableBandwidth * pricing.bandwidthPerGb;
|
|
219
|
+
const totalCost = computeCost + storageCost + bandwidthCost + baseCost;
|
|
220
|
+
return {
|
|
221
|
+
provider,
|
|
222
|
+
monthlyCost: Math.round(totalCost * 100) / 100,
|
|
223
|
+
breakdown: {
|
|
224
|
+
compute: Math.round(computeCost * 100) / 100,
|
|
225
|
+
storage: Math.round(storageCost * 100) / 100,
|
|
226
|
+
bandwidth: Math.round(bandwidthCost * 100) / 100,
|
|
227
|
+
base: Math.round(baseCost * 100) / 100,
|
|
228
|
+
},
|
|
229
|
+
currency: "USD",
|
|
230
|
+
notes: pricing.notes,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Compare costs across all providers
|
|
235
|
+
*/
|
|
236
|
+
async compareCosts(resources) {
|
|
237
|
+
const pricing = await this.fetchPricing();
|
|
238
|
+
if (!pricing) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
const estimates = [];
|
|
242
|
+
for (const provider of Object.keys(pricing.providers)) {
|
|
243
|
+
const estimate = await this.calculateMonthlyCost(provider, resources);
|
|
244
|
+
if (estimate) {
|
|
245
|
+
estimates.push(estimate);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Sort by cost (ascending)
|
|
249
|
+
return estimates.sort((a, b) => a.monthlyCost - b.monthlyCost);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Update cached pricing
|
|
253
|
+
*/
|
|
254
|
+
async updateCache() {
|
|
255
|
+
const data = await this.fetchPricing(true);
|
|
256
|
+
return data !== null;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Clear pricing cache
|
|
260
|
+
*/
|
|
261
|
+
clearCache() {
|
|
262
|
+
this.cachedData = null;
|
|
263
|
+
try {
|
|
264
|
+
if (fs_1.default.existsSync(CACHE_FILE)) {
|
|
265
|
+
fs_1.default.unlinkSync(CACHE_FILE);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// Ignore errors
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get the source of the last fetch
|
|
274
|
+
*/
|
|
275
|
+
getLastSource() {
|
|
276
|
+
return this.lastSource;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get pricing info (version, last updated, source)
|
|
280
|
+
*/
|
|
281
|
+
async getInfo() {
|
|
282
|
+
const pricing = await this.fetchPricing();
|
|
283
|
+
if (!pricing) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
let cacheAge = null;
|
|
287
|
+
if (this.cachedData) {
|
|
288
|
+
cacheAge = Math.floor((Date.now() - this.cachedData.timestamp) / 1000);
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
version: pricing.version,
|
|
292
|
+
updated: pricing.updated,
|
|
293
|
+
source: this.lastSource,
|
|
294
|
+
cacheAge,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Print pricing status (for CLI output)
|
|
299
|
+
*/
|
|
300
|
+
async printStatus() {
|
|
301
|
+
const info = await this.getInfo();
|
|
302
|
+
console.log(chalk_1.default.bold("\nPricing System Status:\n"));
|
|
303
|
+
if (info) {
|
|
304
|
+
console.log(` Version: ${chalk_1.default.green(info.version)}`);
|
|
305
|
+
console.log(` Last Update: ${chalk_1.default.cyan(info.updated)}`);
|
|
306
|
+
console.log(` Source: ${chalk_1.default.yellow(info.source || "Unknown")}`);
|
|
307
|
+
if (info.cacheAge !== null) {
|
|
308
|
+
const hours = Math.floor(info.cacheAge / 3600);
|
|
309
|
+
const minutes = Math.floor((info.cacheAge % 3600) / 60);
|
|
310
|
+
console.log(` Cache Age: ${hours}h ${minutes}m`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
console.log(chalk_1.default.red(" Unable to fetch pricing data"));
|
|
315
|
+
console.log(chalk_1.default.gray(" Run 'expressots costs update' to refresh"));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Get list of available providers
|
|
320
|
+
*/
|
|
321
|
+
async getAvailableProviders() {
|
|
322
|
+
const pricing = await this.fetchPricing();
|
|
323
|
+
if (!pricing) {
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
return Object.keys(pricing.providers);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
exports.PricingManager = PricingManager;
|
|
330
|
+
// Singleton instance
|
|
331
|
+
let pricingManagerInstance = null;
|
|
332
|
+
function getPricingManager(config) {
|
|
333
|
+
if (!pricingManagerInstance) {
|
|
334
|
+
pricingManagerInstance = new PricingManager(config);
|
|
335
|
+
}
|
|
336
|
+
return pricingManagerInstance;
|
|
337
|
+
}
|
|
338
|
+
exports.getPricingManager = getPricingManager;
|
|
339
|
+
function resetPricingManager() {
|
|
340
|
+
pricingManagerInstance = null;
|
|
341
|
+
}
|
|
342
|
+
exports.resetPricingManager = resetPricingManager;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CloudProvider } from "../cli";
|
|
2
|
+
export interface ProviderPricing {
|
|
3
|
+
serviceName: string;
|
|
4
|
+
model: "per-hour" | "per-month" | "usage";
|
|
5
|
+
basePrice: number;
|
|
6
|
+
cpuPerHour: number;
|
|
7
|
+
memoryPerGbHour: number;
|
|
8
|
+
storagePerGb: number;
|
|
9
|
+
bandwidthPerGb: number;
|
|
10
|
+
freeBandwidth: number;
|
|
11
|
+
freeCredits?: number;
|
|
12
|
+
notes?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get pricing for a specific provider
|
|
16
|
+
*/
|
|
17
|
+
export declare function getPricing(provider: CloudProvider): ProviderPricing;
|
|
18
|
+
/**
|
|
19
|
+
* Get all provider pricing
|
|
20
|
+
*/
|
|
21
|
+
export declare function getAllPricing(): Record<CloudProvider, ProviderPricing>;
|
|
22
|
+
/**
|
|
23
|
+
* Calculate monthly cost for a provider
|
|
24
|
+
*/
|
|
25
|
+
export declare function calculateMonthlyCost(provider: CloudProvider, resources: {
|
|
26
|
+
instances: number;
|
|
27
|
+
cpu: number;
|
|
28
|
+
memory: number;
|
|
29
|
+
storage: number;
|
|
30
|
+
bandwidth: number;
|
|
31
|
+
hours: number;
|
|
32
|
+
}): number;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateMonthlyCost = exports.getAllPricing = exports.getPricing = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Pricing data for various cloud providers
|
|
6
|
+
* Note: These are approximate prices for estimation purposes.
|
|
7
|
+
* Actual prices vary by region, instance type, and commitment.
|
|
8
|
+
*/
|
|
9
|
+
const PROVIDER_PRICING = {
|
|
10
|
+
aws: {
|
|
11
|
+
serviceName: "ECS Fargate",
|
|
12
|
+
model: "per-hour",
|
|
13
|
+
basePrice: 0,
|
|
14
|
+
cpuPerHour: 0.04048,
|
|
15
|
+
memoryPerGbHour: 0.004445,
|
|
16
|
+
storagePerGb: 0.1,
|
|
17
|
+
bandwidthPerGb: 0.09,
|
|
18
|
+
freeBandwidth: 100,
|
|
19
|
+
notes: "Prices for us-east-1 region",
|
|
20
|
+
},
|
|
21
|
+
gcp: {
|
|
22
|
+
serviceName: "Cloud Run",
|
|
23
|
+
model: "per-hour",
|
|
24
|
+
basePrice: 0,
|
|
25
|
+
cpuPerHour: 0.024,
|
|
26
|
+
memoryPerGbHour: 0.0025,
|
|
27
|
+
storagePerGb: 0.1,
|
|
28
|
+
bandwidthPerGb: 0.12,
|
|
29
|
+
freeBandwidth: 200,
|
|
30
|
+
freeCredits: 300,
|
|
31
|
+
notes: "Pay only for what you use, scales to zero",
|
|
32
|
+
},
|
|
33
|
+
azure: {
|
|
34
|
+
serviceName: "Container Apps",
|
|
35
|
+
model: "per-hour",
|
|
36
|
+
basePrice: 0,
|
|
37
|
+
cpuPerHour: 0.024,
|
|
38
|
+
memoryPerGbHour: 0.003,
|
|
39
|
+
storagePerGb: 0.1,
|
|
40
|
+
bandwidthPerGb: 0.087,
|
|
41
|
+
freeBandwidth: 100,
|
|
42
|
+
freeCredits: 200,
|
|
43
|
+
notes: "First 180,000 vCPU-seconds free per month",
|
|
44
|
+
},
|
|
45
|
+
railway: {
|
|
46
|
+
serviceName: "Web Service",
|
|
47
|
+
model: "usage",
|
|
48
|
+
basePrice: 5,
|
|
49
|
+
cpuPerHour: 0.000463,
|
|
50
|
+
memoryPerGbHour: 0.000231,
|
|
51
|
+
storagePerGb: 0.25,
|
|
52
|
+
bandwidthPerGb: 0,
|
|
53
|
+
freeBandwidth: 1000,
|
|
54
|
+
freeCredits: 5,
|
|
55
|
+
notes: "Usage-based pricing, great DX",
|
|
56
|
+
},
|
|
57
|
+
render: {
|
|
58
|
+
serviceName: "Web Service",
|
|
59
|
+
model: "per-month",
|
|
60
|
+
basePrice: 7,
|
|
61
|
+
cpuPerHour: 0,
|
|
62
|
+
memoryPerGbHour: 0,
|
|
63
|
+
storagePerGb: 0.25,
|
|
64
|
+
bandwidthPerGb: 0.1,
|
|
65
|
+
freeBandwidth: 100,
|
|
66
|
+
notes: "Simple pricing, auto-scaling available",
|
|
67
|
+
},
|
|
68
|
+
fly: {
|
|
69
|
+
serviceName: "Machines",
|
|
70
|
+
model: "per-hour",
|
|
71
|
+
basePrice: 0,
|
|
72
|
+
cpuPerHour: 0.0000158,
|
|
73
|
+
memoryPerGbHour: 0.0000047,
|
|
74
|
+
storagePerGb: 0.15,
|
|
75
|
+
bandwidthPerGb: 0.02,
|
|
76
|
+
freeBandwidth: 100,
|
|
77
|
+
freeCredits: 0,
|
|
78
|
+
notes: "Pay for resources while running, scales to zero",
|
|
79
|
+
},
|
|
80
|
+
digitalocean: {
|
|
81
|
+
serviceName: "App Platform",
|
|
82
|
+
model: "per-month",
|
|
83
|
+
basePrice: 5,
|
|
84
|
+
cpuPerHour: 0,
|
|
85
|
+
memoryPerGbHour: 0,
|
|
86
|
+
storagePerGb: 0.1,
|
|
87
|
+
bandwidthPerGb: 0.01,
|
|
88
|
+
freeBandwidth: 500,
|
|
89
|
+
notes: "Simple pricing, good for small projects",
|
|
90
|
+
},
|
|
91
|
+
heroku: {
|
|
92
|
+
serviceName: "Eco Dyno",
|
|
93
|
+
model: "per-month",
|
|
94
|
+
basePrice: 5,
|
|
95
|
+
cpuPerHour: 0,
|
|
96
|
+
memoryPerGbHour: 0,
|
|
97
|
+
storagePerGb: 0,
|
|
98
|
+
bandwidthPerGb: 0,
|
|
99
|
+
freeBandwidth: 2000,
|
|
100
|
+
notes: "Basic: $7/mo, Standard: $25/mo, Performance: $250+/mo",
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Get pricing for a specific provider
|
|
105
|
+
*/
|
|
106
|
+
function getPricing(provider) {
|
|
107
|
+
return PROVIDER_PRICING[provider] || PROVIDER_PRICING.aws;
|
|
108
|
+
}
|
|
109
|
+
exports.getPricing = getPricing;
|
|
110
|
+
/**
|
|
111
|
+
* Get all provider pricing
|
|
112
|
+
*/
|
|
113
|
+
function getAllPricing() {
|
|
114
|
+
return PROVIDER_PRICING;
|
|
115
|
+
}
|
|
116
|
+
exports.getAllPricing = getAllPricing;
|
|
117
|
+
/**
|
|
118
|
+
* Calculate monthly cost for a provider
|
|
119
|
+
*/
|
|
120
|
+
function calculateMonthlyCost(provider, resources) {
|
|
121
|
+
const pricing = getPricing(provider);
|
|
122
|
+
const hoursRatio = resources.hours / 720;
|
|
123
|
+
let cost = 0;
|
|
124
|
+
switch (pricing.model) {
|
|
125
|
+
case "per-hour":
|
|
126
|
+
cost =
|
|
127
|
+
resources.instances *
|
|
128
|
+
(resources.cpu * pricing.cpuPerHour * resources.hours +
|
|
129
|
+
resources.memory *
|
|
130
|
+
pricing.memoryPerGbHour *
|
|
131
|
+
resources.hours);
|
|
132
|
+
break;
|
|
133
|
+
case "per-month":
|
|
134
|
+
cost = resources.instances * pricing.basePrice * hoursRatio;
|
|
135
|
+
break;
|
|
136
|
+
case "usage":
|
|
137
|
+
cost =
|
|
138
|
+
pricing.basePrice +
|
|
139
|
+
resources.instances *
|
|
140
|
+
(resources.cpu * pricing.cpuPerHour * resources.hours +
|
|
141
|
+
resources.memory *
|
|
142
|
+
pricing.memoryPerGbHour *
|
|
143
|
+
resources.hours);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
// Add storage cost
|
|
147
|
+
cost += resources.storage * pricing.storagePerGb;
|
|
148
|
+
// Add bandwidth cost (above free tier)
|
|
149
|
+
const billableBandwidth = Math.max(0, resources.bandwidth - pricing.freeBandwidth);
|
|
150
|
+
cost += billableBandwidth * pricing.bandwidthPerGb;
|
|
151
|
+
return Math.round(cost * 100) / 100;
|
|
152
|
+
}
|
|
153
|
+
exports.calculateMonthlyCost = calculateMonthlyCost;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API pricing source - fetches from ExpressoTS pricing API
|
|
3
|
+
* Currently a placeholder for future implementation
|
|
4
|
+
*/
|
|
5
|
+
import type { PricingData, PricingSource } from "../types";
|
|
6
|
+
export declare class APIPricingSource implements PricingSource {
|
|
7
|
+
name: string;
|
|
8
|
+
fetch(): Promise<PricingData | null>;
|
|
9
|
+
}
|
|
10
|
+
export declare function createAPIPricingSource(): PricingSource;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* API pricing source - fetches from ExpressoTS pricing API
|
|
4
|
+
* Currently a placeholder for future implementation
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.createAPIPricingSource = exports.APIPricingSource = void 0;
|
|
8
|
+
const API_URL = "https://api.expressots.com/pricing/v1";
|
|
9
|
+
class APIPricingSource {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.name = "api";
|
|
12
|
+
}
|
|
13
|
+
async fetch() {
|
|
14
|
+
// API not yet implemented - return null to fall through to next source
|
|
15
|
+
// In the future, this will fetch from the ExpressoTS pricing API
|
|
16
|
+
try {
|
|
17
|
+
// Placeholder for future API implementation
|
|
18
|
+
// const response = await fetch(API_URL);
|
|
19
|
+
// if (!response.ok) return null;
|
|
20
|
+
// return await response.json();
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.APIPricingSource = APIPricingSource;
|
|
29
|
+
function createAPIPricingSource() {
|
|
30
|
+
return new APIPricingSource();
|
|
31
|
+
}
|
|
32
|
+
exports.createAPIPricingSource = createAPIPricingSource;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pricing sources exports
|
|
3
|
+
*/
|
|
4
|
+
export { APIPricingSource, createAPIPricingSource } from "./api-source";
|
|
5
|
+
export { RemoteJSONPricingSource, createRemoteJSONPricingSource, } from "./remote-json-source";
|
|
6
|
+
export { LocalJSONPricingSource, createLocalJSONPricingSource, } from "./local-json-source";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pricing sources exports
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createLocalJSONPricingSource = exports.LocalJSONPricingSource = exports.createRemoteJSONPricingSource = exports.RemoteJSONPricingSource = exports.createAPIPricingSource = exports.APIPricingSource = void 0;
|
|
7
|
+
var api_source_1 = require("./api-source");
|
|
8
|
+
Object.defineProperty(exports, "APIPricingSource", { enumerable: true, get: function () { return api_source_1.APIPricingSource; } });
|
|
9
|
+
Object.defineProperty(exports, "createAPIPricingSource", { enumerable: true, get: function () { return api_source_1.createAPIPricingSource; } });
|
|
10
|
+
var remote_json_source_1 = require("./remote-json-source");
|
|
11
|
+
Object.defineProperty(exports, "RemoteJSONPricingSource", { enumerable: true, get: function () { return remote_json_source_1.RemoteJSONPricingSource; } });
|
|
12
|
+
Object.defineProperty(exports, "createRemoteJSONPricingSource", { enumerable: true, get: function () { return remote_json_source_1.createRemoteJSONPricingSource; } });
|
|
13
|
+
var local_json_source_1 = require("./local-json-source");
|
|
14
|
+
Object.defineProperty(exports, "LocalJSONPricingSource", { enumerable: true, get: function () { return local_json_source_1.LocalJSONPricingSource; } });
|
|
15
|
+
Object.defineProperty(exports, "createLocalJSONPricingSource", { enumerable: true, get: function () { return local_json_source_1.createLocalJSONPricingSource; } });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local JSON pricing source - reads from user's custom pricing file
|
|
3
|
+
*/
|
|
4
|
+
import type { PricingData, PricingSource } from "../types";
|
|
5
|
+
export declare class LocalJSONPricingSource implements PricingSource {
|
|
6
|
+
name: string;
|
|
7
|
+
private filePath;
|
|
8
|
+
constructor(filePath?: string);
|
|
9
|
+
fetch(): Promise<PricingData | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Check if custom pricing file exists
|
|
12
|
+
*/
|
|
13
|
+
exists(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Get the file path
|
|
16
|
+
*/
|
|
17
|
+
getPath(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Set custom file path
|
|
20
|
+
*/
|
|
21
|
+
setPath(filePath: string): void;
|
|
22
|
+
}
|
|
23
|
+
export declare function createLocalJSONPricingSource(filePath?: string): PricingSource;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Local JSON pricing source - reads from user's custom pricing file
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createLocalJSONPricingSource = exports.LocalJSONPricingSource = void 0;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const DEFAULT_PATH = path_1.default.join(os_1.default.homedir(), ".expressots", "pricing.json");
|
|
14
|
+
class LocalJSONPricingSource {
|
|
15
|
+
constructor(filePath) {
|
|
16
|
+
this.name = "local";
|
|
17
|
+
this.filePath = filePath || DEFAULT_PATH;
|
|
18
|
+
}
|
|
19
|
+
async fetch() {
|
|
20
|
+
try {
|
|
21
|
+
if (!fs_1.default.existsSync(this.filePath)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const content = fs_1.default.readFileSync(this.filePath, "utf-8");
|
|
25
|
+
const pricing = JSON.parse(content);
|
|
26
|
+
// Basic validation
|
|
27
|
+
if (!pricing.version || !pricing.providers) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return pricing;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if custom pricing file exists
|
|
38
|
+
*/
|
|
39
|
+
exists() {
|
|
40
|
+
return fs_1.default.existsSync(this.filePath);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get the file path
|
|
44
|
+
*/
|
|
45
|
+
getPath() {
|
|
46
|
+
return this.filePath;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Set custom file path
|
|
50
|
+
*/
|
|
51
|
+
setPath(filePath) {
|
|
52
|
+
this.filePath = filePath;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.LocalJSONPricingSource = LocalJSONPricingSource;
|
|
56
|
+
function createLocalJSONPricingSource(filePath) {
|
|
57
|
+
return new LocalJSONPricingSource(filePath);
|
|
58
|
+
}
|
|
59
|
+
exports.createLocalJSONPricingSource = createLocalJSONPricingSource;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote JSON pricing source - fetches from GitHub-hosted pricing file
|
|
3
|
+
*/
|
|
4
|
+
import type { PricingData, PricingSource } from "../types";
|
|
5
|
+
export declare class RemoteJSONPricingSource implements PricingSource {
|
|
6
|
+
name: string;
|
|
7
|
+
private url;
|
|
8
|
+
constructor(url?: string);
|
|
9
|
+
fetch(): Promise<PricingData | null>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createRemoteJSONPricingSource(url?: string): PricingSource;
|