infra-cost 1.5.0 → 1.7.0
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/dist/cli/index.js +2582 -934
- package/dist/index.js +2002 -353
- package/package.json +8 -1
package/dist/cli/index.js
CHANGED
|
@@ -8,6 +8,9 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
8
8
|
var __esm = (fn, res) => function __init() {
|
|
9
9
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
10
|
};
|
|
11
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
12
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
13
|
+
};
|
|
11
14
|
var __export = (target, all) => {
|
|
12
15
|
for (var name in all)
|
|
13
16
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -30,6 +33,171 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
33
|
));
|
|
31
34
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
35
|
|
|
36
|
+
// package.json
|
|
37
|
+
var require_package = __commonJS({
|
|
38
|
+
"package.json"(exports, module2) {
|
|
39
|
+
module2.exports = {
|
|
40
|
+
name: "infra-cost",
|
|
41
|
+
version: "1.7.0",
|
|
42
|
+
description: "Multi-cloud FinOps CLI tool for comprehensive cost analysis and infrastructure optimization across AWS, GCP, Azure, Alibaba Cloud, and Oracle Cloud",
|
|
43
|
+
keywords: [
|
|
44
|
+
"aws",
|
|
45
|
+
"gcp",
|
|
46
|
+
"azure",
|
|
47
|
+
"cloud-cost",
|
|
48
|
+
"finops",
|
|
49
|
+
"cost-optimization",
|
|
50
|
+
"multi-cloud",
|
|
51
|
+
"cost-analysis",
|
|
52
|
+
"infrastructure",
|
|
53
|
+
"cloud-billing",
|
|
54
|
+
"cost-management",
|
|
55
|
+
"devops",
|
|
56
|
+
"cli-tool",
|
|
57
|
+
"cost-monitoring",
|
|
58
|
+
"budget-tracking"
|
|
59
|
+
],
|
|
60
|
+
author: {
|
|
61
|
+
name: "Code Collab",
|
|
62
|
+
email: "codecollab.co@gmail.com",
|
|
63
|
+
url: "https://github.com/codecollab-co/infra-cost"
|
|
64
|
+
},
|
|
65
|
+
files: [
|
|
66
|
+
"!tests/**/*",
|
|
67
|
+
"dist/**/*",
|
|
68
|
+
"!dist/**/*.js.map",
|
|
69
|
+
"bin/**/*"
|
|
70
|
+
],
|
|
71
|
+
bin: {
|
|
72
|
+
"infra-cost": "./bin/index.js",
|
|
73
|
+
"aws-cost": "./bin/index.js"
|
|
74
|
+
},
|
|
75
|
+
main: "./dist/index.js",
|
|
76
|
+
scripts: {
|
|
77
|
+
prebuild: "run-s clean",
|
|
78
|
+
build: "tsup",
|
|
79
|
+
clean: "rm -rf dist",
|
|
80
|
+
typecheck: "tsc --noEmit",
|
|
81
|
+
lint: "eslint src --ext .ts",
|
|
82
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
83
|
+
test: "jest",
|
|
84
|
+
"test:watch": "jest --watch",
|
|
85
|
+
"test:coverage": "jest --coverage",
|
|
86
|
+
dev: "tsup --watch",
|
|
87
|
+
"version:check": `echo "Current version: $(npm pkg get version | tr -d '"')"`,
|
|
88
|
+
"version:next": "npm version patch --no-git-tag-version",
|
|
89
|
+
"version:bump:patch": "npm version patch",
|
|
90
|
+
"version:bump:minor": "npm version minor",
|
|
91
|
+
"version:bump:major": "npm version major",
|
|
92
|
+
"publish:dry": "npm publish --dry-run",
|
|
93
|
+
"publish:latest": "npm publish",
|
|
94
|
+
"publish:beta": "npm publish --tag beta",
|
|
95
|
+
"prepare-release": "npm run build && npm run test && npm run version:bump:patch",
|
|
96
|
+
postpublish: 'echo "\u{1F389} Published $(npm pkg get name)@$(npm pkg get version) to npm!"',
|
|
97
|
+
prepublishOnly: "npm run build"
|
|
98
|
+
},
|
|
99
|
+
repository: {
|
|
100
|
+
type: "git",
|
|
101
|
+
url: "https://github.com/codecollab-co/infra-cost.git"
|
|
102
|
+
},
|
|
103
|
+
bugs: {
|
|
104
|
+
url: "https://github.com/codecollab-co/infra-cost/issues"
|
|
105
|
+
},
|
|
106
|
+
homepage: "https://github.com/codecollab-co/infra-cost#readme",
|
|
107
|
+
license: "MIT",
|
|
108
|
+
engines: {
|
|
109
|
+
node: ">=20.0.0",
|
|
110
|
+
npm: ">=10.0.0"
|
|
111
|
+
},
|
|
112
|
+
dependencies: {
|
|
113
|
+
"@alicloud/bssopenapi20171214": "^2.0.1",
|
|
114
|
+
"@alicloud/cs20151215": "^4.0.1",
|
|
115
|
+
"@alicloud/ecs20140526": "^4.0.3",
|
|
116
|
+
"@alicloud/oss20190517": "^1.0.6",
|
|
117
|
+
"@alicloud/rds20140815": "^3.0.2",
|
|
118
|
+
"@alicloud/tea-util": "^1.4.7",
|
|
119
|
+
"@aws-sdk/client-budgets": "^3.975.0",
|
|
120
|
+
"@aws-sdk/client-cost-explorer": "^3.975.0",
|
|
121
|
+
"@aws-sdk/client-ec2": "^3.975.0",
|
|
122
|
+
"@aws-sdk/client-elastic-load-balancing-v2": "^3.975.0",
|
|
123
|
+
"@aws-sdk/client-iam": "^3.975.0",
|
|
124
|
+
"@aws-sdk/client-lambda": "^3.975.0",
|
|
125
|
+
"@aws-sdk/client-rds": "^3.975.0",
|
|
126
|
+
"@aws-sdk/client-s3": "^3.975.0",
|
|
127
|
+
"@aws-sdk/client-sts": "^3.975.0",
|
|
128
|
+
"@aws-sdk/credential-providers": "^3.975.0",
|
|
129
|
+
"@azure/arm-compute": "^23.3.0",
|
|
130
|
+
"@azure/arm-consumption": "^9.2.1",
|
|
131
|
+
"@azure/arm-containerservice": "^24.1.0",
|
|
132
|
+
"@azure/arm-costmanagement": "^1.0.0-beta.2",
|
|
133
|
+
"@azure/arm-network": "^35.0.0",
|
|
134
|
+
"@azure/arm-sql": "^10.0.0",
|
|
135
|
+
"@azure/arm-storage": "^19.1.0",
|
|
136
|
+
"@azure/arm-subscriptions": "^6.0.0",
|
|
137
|
+
"@azure/identity": "^4.13.0",
|
|
138
|
+
"@google-cloud/bigquery": "^8.1.1",
|
|
139
|
+
"@google-cloud/billing": "^5.1.1",
|
|
140
|
+
"@google-cloud/compute": "^6.7.0",
|
|
141
|
+
"@google-cloud/container": "^6.6.0",
|
|
142
|
+
"@google-cloud/monitoring": "^5.3.1",
|
|
143
|
+
"@google-cloud/resource-manager": "^6.2.1",
|
|
144
|
+
"@google-cloud/sql": "^0.24.0",
|
|
145
|
+
"@google-cloud/storage": "^7.18.0",
|
|
146
|
+
"@slack/web-api": "^7.5.0",
|
|
147
|
+
callsites: "^3.1.0",
|
|
148
|
+
chalk: "^4.1.2",
|
|
149
|
+
"cli-progress": "^3.12.0",
|
|
150
|
+
"cli-table3": "^0.6.5",
|
|
151
|
+
commander: "^12.1.0",
|
|
152
|
+
cors: "^2.8.6",
|
|
153
|
+
dayjs: "^1.11.19",
|
|
154
|
+
exceljs: "^4.4.0",
|
|
155
|
+
express: "^5.2.1",
|
|
156
|
+
"express-rate-limit": "^8.2.1",
|
|
157
|
+
"fd-slicer": "^1.1.0",
|
|
158
|
+
"google-auth-library": "^10.5.0",
|
|
159
|
+
googleapis: "^171.0.0",
|
|
160
|
+
helmet: "^8.1.0",
|
|
161
|
+
ini: "^6.0.0",
|
|
162
|
+
ink: "^6.6.0",
|
|
163
|
+
moment: "^2.30.1",
|
|
164
|
+
"node-fetch": "^2.7.0",
|
|
165
|
+
"oci-budget": "^2.88.0",
|
|
166
|
+
"oci-common": "^2.88.0",
|
|
167
|
+
"oci-containerengine": "^2.88.0",
|
|
168
|
+
"oci-core": "^2.88.0",
|
|
169
|
+
"oci-database": "^2.88.0",
|
|
170
|
+
"oci-identity": "^2.88.0",
|
|
171
|
+
"oci-objectstorage": "^2.88.0",
|
|
172
|
+
"oci-usageapi": "^2.88.0",
|
|
173
|
+
ora: "^9.1.0",
|
|
174
|
+
pako: "^2.1.0",
|
|
175
|
+
pend: "^1.2.0",
|
|
176
|
+
react: "^19.2.4",
|
|
177
|
+
"swagger-jsdoc": "^6.2.8",
|
|
178
|
+
"swagger-ui-express": "^5.0.1",
|
|
179
|
+
yauzl: "^3.0.0",
|
|
180
|
+
zod: "^3.23.8"
|
|
181
|
+
},
|
|
182
|
+
devDependencies: {
|
|
183
|
+
"@types/cors": "^2.8.19",
|
|
184
|
+
"@types/express": "^5.0.6",
|
|
185
|
+
"@types/jest": "^29.5.12",
|
|
186
|
+
"@types/node": "^22.5.4",
|
|
187
|
+
"@types/yauzl": "^2.10.3",
|
|
188
|
+
"@typescript-eslint/eslint-plugin": "^8.5.0",
|
|
189
|
+
"@typescript-eslint/parser": "^8.5.0",
|
|
190
|
+
eslint: "^8.57.0",
|
|
191
|
+
jest: "^29.7.0",
|
|
192
|
+
"npm-run-all": "^4.1.5",
|
|
193
|
+
"ts-jest": "^29.2.5",
|
|
194
|
+
tsup: "^6.7.0",
|
|
195
|
+
typescript: "^5.6.2"
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
33
201
|
// src/core/logging/structured-logger.ts
|
|
34
202
|
function initializeLogger(config) {
|
|
35
203
|
globalLogger = new StructuredLogger(config);
|
|
@@ -565,6 +733,209 @@ var init_schema = __esm({
|
|
|
565
733
|
}
|
|
566
734
|
});
|
|
567
735
|
|
|
736
|
+
// src/core/config/loader.ts
|
|
737
|
+
function discoverConfigFile() {
|
|
738
|
+
for (const configPath of CONFIG_PATHS) {
|
|
739
|
+
if ((0, import_fs.existsSync)(configPath)) {
|
|
740
|
+
return configPath;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
function loadConfigFile(configPath) {
|
|
746
|
+
try {
|
|
747
|
+
const content = (0, import_fs.readFileSync)(configPath, "utf8");
|
|
748
|
+
const config = JSON.parse(content);
|
|
749
|
+
if (config.profiles && config.defaults?.profile) {
|
|
750
|
+
const activeProfile = config.profiles[config.defaults.profile];
|
|
751
|
+
if (activeProfile) {
|
|
752
|
+
return mergeConfigs(config.defaults, activeProfile);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return config.defaults || config;
|
|
756
|
+
} catch (error) {
|
|
757
|
+
console.warn(`Warning: Could not load config from ${configPath}: ${error.message}`);
|
|
758
|
+
return {};
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function resolveEnvVars(config) {
|
|
762
|
+
const resolved = JSON.parse(JSON.stringify(config));
|
|
763
|
+
if (process.env.AWS_ACCESS_KEY_ID) {
|
|
764
|
+
resolved.accessKey = process.env.AWS_ACCESS_KEY_ID;
|
|
765
|
+
}
|
|
766
|
+
if (process.env.AWS_SECRET_ACCESS_KEY) {
|
|
767
|
+
resolved.secretKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
768
|
+
}
|
|
769
|
+
if (process.env.AWS_SESSION_TOKEN) {
|
|
770
|
+
resolved.sessionToken = process.env.AWS_SESSION_TOKEN;
|
|
771
|
+
}
|
|
772
|
+
if (process.env.AWS_REGION) {
|
|
773
|
+
resolved.region = process.env.AWS_REGION;
|
|
774
|
+
}
|
|
775
|
+
if (process.env.AWS_PROFILE) {
|
|
776
|
+
resolved.profile = process.env.AWS_PROFILE;
|
|
777
|
+
}
|
|
778
|
+
if (process.env.SLACK_TOKEN || process.env.SLACK_CHANNEL) {
|
|
779
|
+
resolved.slack = {
|
|
780
|
+
...resolved.slack,
|
|
781
|
+
token: process.env.SLACK_TOKEN ?? resolved.slack?.token,
|
|
782
|
+
channel: process.env.SLACK_CHANNEL ?? resolved.slack?.channel,
|
|
783
|
+
enabled: true
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
return resolved;
|
|
787
|
+
}
|
|
788
|
+
function mergeConfigs(...configs) {
|
|
789
|
+
const result = {};
|
|
790
|
+
for (const config of configs) {
|
|
791
|
+
for (const [key, value] of Object.entries(config)) {
|
|
792
|
+
if (value === void 0 || value === null) {
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
796
|
+
result[key] = { ...result[key], ...value };
|
|
797
|
+
} else {
|
|
798
|
+
result[key] = value;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return result;
|
|
803
|
+
}
|
|
804
|
+
function autoLoadConfig(cliOptions = {}) {
|
|
805
|
+
let config = { ...DEFAULT_CONFIG2 };
|
|
806
|
+
const configPath = cliOptions.configFile || discoverConfigFile();
|
|
807
|
+
if (configPath) {
|
|
808
|
+
const fileConfig = loadConfigFile(configPath);
|
|
809
|
+
config = mergeConfigs(config, fileConfig);
|
|
810
|
+
}
|
|
811
|
+
config = mergeConfigs(config, resolveEnvVars(config));
|
|
812
|
+
const cliConfig = mapCliOptionsToConfig(cliOptions);
|
|
813
|
+
config = mergeConfigs(config, cliConfig);
|
|
814
|
+
return config;
|
|
815
|
+
}
|
|
816
|
+
function mapCliOptionsToConfig(options) {
|
|
817
|
+
const config = {};
|
|
818
|
+
if (options.provider)
|
|
819
|
+
config.provider = options.provider;
|
|
820
|
+
if (options.profile)
|
|
821
|
+
config.profile = options.profile;
|
|
822
|
+
if (options.region)
|
|
823
|
+
config.region = options.region;
|
|
824
|
+
if (options.accessKey)
|
|
825
|
+
config.accessKey = options.accessKey;
|
|
826
|
+
if (options.secretKey)
|
|
827
|
+
config.secretKey = options.secretKey;
|
|
828
|
+
if (options.sessionToken)
|
|
829
|
+
config.sessionToken = options.sessionToken;
|
|
830
|
+
if (options.json || options.text || options.summary) {
|
|
831
|
+
config.output = config.output || {};
|
|
832
|
+
if (options.json)
|
|
833
|
+
config.output.format = "json";
|
|
834
|
+
if (options.text)
|
|
835
|
+
config.output.format = "text";
|
|
836
|
+
if (options.summary)
|
|
837
|
+
config.output.summary = true;
|
|
838
|
+
}
|
|
839
|
+
if (options.delta !== void 0) {
|
|
840
|
+
config.output = config.output || {};
|
|
841
|
+
config.output.showDelta = options.delta;
|
|
842
|
+
}
|
|
843
|
+
if (options.deltaThreshold) {
|
|
844
|
+
config.output = config.output || {};
|
|
845
|
+
config.output.deltaThreshold = parseFloat(options.deltaThreshold);
|
|
846
|
+
}
|
|
847
|
+
if (options.quickWins !== void 0) {
|
|
848
|
+
config.output = config.output || {};
|
|
849
|
+
config.output.showQuickWins = options.quickWins;
|
|
850
|
+
}
|
|
851
|
+
if (options.quickWinsCount) {
|
|
852
|
+
config.output = config.output || {};
|
|
853
|
+
config.output.quickWinsCount = parseInt(options.quickWinsCount, 10);
|
|
854
|
+
}
|
|
855
|
+
if (options.cache !== void 0 || options.noCache !== void 0) {
|
|
856
|
+
config.cache = config.cache || {};
|
|
857
|
+
config.cache.enabled = options.cache === true || options.noCache !== true;
|
|
858
|
+
}
|
|
859
|
+
if (options.cacheTtl) {
|
|
860
|
+
config.cache = config.cache || {};
|
|
861
|
+
config.cache.ttl = options.cacheTtl;
|
|
862
|
+
}
|
|
863
|
+
if (options.cacheType) {
|
|
864
|
+
config.cache = config.cache || {};
|
|
865
|
+
config.cache.type = options.cacheType;
|
|
866
|
+
}
|
|
867
|
+
if (options.slackToken || options.slackChannel) {
|
|
868
|
+
config.slack = config.slack || {};
|
|
869
|
+
if (options.slackToken)
|
|
870
|
+
config.slack.token = options.slackToken;
|
|
871
|
+
if (options.slackChannel)
|
|
872
|
+
config.slack.channel = options.slackChannel;
|
|
873
|
+
config.slack.enabled = true;
|
|
874
|
+
}
|
|
875
|
+
if (options.logLevel || options.verbose || options.quiet) {
|
|
876
|
+
config.logging = config.logging || {};
|
|
877
|
+
if (options.logLevel)
|
|
878
|
+
config.logging.level = options.logLevel;
|
|
879
|
+
if (options.verbose)
|
|
880
|
+
config.logging.level = "debug";
|
|
881
|
+
if (options.quiet)
|
|
882
|
+
config.logging.level = "error";
|
|
883
|
+
}
|
|
884
|
+
return config;
|
|
885
|
+
}
|
|
886
|
+
var import_fs, import_path, import_os, CONFIG_PATHS, DEFAULT_CONFIG2;
|
|
887
|
+
var init_loader = __esm({
|
|
888
|
+
"src/core/config/loader.ts"() {
|
|
889
|
+
import_fs = require("fs");
|
|
890
|
+
import_path = require("path");
|
|
891
|
+
import_os = require("os");
|
|
892
|
+
CONFIG_PATHS = [
|
|
893
|
+
(0, import_path.join)(process.cwd(), "infra-cost.config.json"),
|
|
894
|
+
// Project-specific
|
|
895
|
+
(0, import_path.join)(process.cwd(), ".infra-cost.config.json"),
|
|
896
|
+
// Project-specific (hidden)
|
|
897
|
+
(0, import_path.join)(process.cwd(), ".infra-cost", "config.json"),
|
|
898
|
+
// Project directory
|
|
899
|
+
(0, import_path.join)((0, import_os.homedir)(), ".infra-cost", "config.json"),
|
|
900
|
+
// User global
|
|
901
|
+
(0, import_path.join)((0, import_os.homedir)(), ".config", "infra-cost", "config.json")
|
|
902
|
+
// XDG standard
|
|
903
|
+
];
|
|
904
|
+
DEFAULT_CONFIG2 = {
|
|
905
|
+
provider: "aws",
|
|
906
|
+
profile: "default",
|
|
907
|
+
region: "us-east-1",
|
|
908
|
+
output: {
|
|
909
|
+
format: "fancy",
|
|
910
|
+
summary: false,
|
|
911
|
+
showDelta: true,
|
|
912
|
+
// NEW: Show delta by default
|
|
913
|
+
showQuickWins: true,
|
|
914
|
+
// NEW: Show quick wins by default
|
|
915
|
+
deltaThreshold: 10,
|
|
916
|
+
quickWinsCount: 3
|
|
917
|
+
},
|
|
918
|
+
cache: {
|
|
919
|
+
enabled: true,
|
|
920
|
+
// NEW: Cache enabled by default
|
|
921
|
+
ttl: "4h",
|
|
922
|
+
type: "file"
|
|
923
|
+
},
|
|
924
|
+
logging: {
|
|
925
|
+
level: "info",
|
|
926
|
+
format: "pretty",
|
|
927
|
+
auditEnabled: false
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
__name(discoverConfigFile, "discoverConfigFile");
|
|
931
|
+
__name(loadConfigFile, "loadConfigFile");
|
|
932
|
+
__name(resolveEnvVars, "resolveEnvVars");
|
|
933
|
+
__name(mergeConfigs, "mergeConfigs");
|
|
934
|
+
__name(autoLoadConfig, "autoLoadConfig");
|
|
935
|
+
__name(mapCliOptionsToConfig, "mapCliOptionsToConfig");
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
|
|
568
939
|
// src/types/providers.ts
|
|
569
940
|
var CloudProviderAdapter, ResourceType;
|
|
570
941
|
var init_providers = __esm({
|
|
@@ -10208,426 +10579,729 @@ var init_multicloud = __esm({
|
|
|
10208
10579
|
}
|
|
10209
10580
|
});
|
|
10210
10581
|
|
|
10211
|
-
// src/
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10582
|
+
// src/api/utils.ts
|
|
10583
|
+
async function getProviderFromConfig() {
|
|
10584
|
+
const config = autoLoadConfig();
|
|
10585
|
+
const factory = new CloudProviderFactory();
|
|
10586
|
+
return factory.createProvider(config);
|
|
10587
|
+
}
|
|
10588
|
+
function getConfig2() {
|
|
10589
|
+
return autoLoadConfig();
|
|
10590
|
+
}
|
|
10591
|
+
var init_utils = __esm({
|
|
10592
|
+
"src/api/utils.ts"() {
|
|
10593
|
+
init_factory();
|
|
10594
|
+
init_loader();
|
|
10595
|
+
__name(getProviderFromConfig, "getProviderFromConfig");
|
|
10596
|
+
__name(getConfig2, "getConfig");
|
|
10597
|
+
}
|
|
10216
10598
|
});
|
|
10217
|
-
module.exports = __toCommonJS(cli_exports);
|
|
10218
|
-
var import_commander = require("commander");
|
|
10219
10599
|
|
|
10220
|
-
//
|
|
10221
|
-
var
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
"
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
"
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
"
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
jest: "^29.7.0",
|
|
10367
|
-
"npm-run-all": "^4.1.5",
|
|
10368
|
-
"ts-jest": "^29.2.5",
|
|
10369
|
-
tsup: "^6.7.0",
|
|
10370
|
-
typescript: "^5.6.2"
|
|
10600
|
+
// src/api/routes/costs.ts
|
|
10601
|
+
var costs_exports2 = {};
|
|
10602
|
+
__export(costs_exports2, {
|
|
10603
|
+
default: () => costs_default
|
|
10604
|
+
});
|
|
10605
|
+
var import_express, router, costs_default;
|
|
10606
|
+
var init_costs2 = __esm({
|
|
10607
|
+
"src/api/routes/costs.ts"() {
|
|
10608
|
+
import_express = require("express");
|
|
10609
|
+
init_utils();
|
|
10610
|
+
init_server();
|
|
10611
|
+
router = (0, import_express.Router)();
|
|
10612
|
+
router.get("/", async (req, res) => {
|
|
10613
|
+
try {
|
|
10614
|
+
const config = getConfig2();
|
|
10615
|
+
const provider = await getProviderFromConfig();
|
|
10616
|
+
const today = /* @__PURE__ */ new Date();
|
|
10617
|
+
const todayStart = new Date(today.setHours(0, 0, 0, 0));
|
|
10618
|
+
const todayEnd = new Date(today.setHours(23, 59, 59, 999));
|
|
10619
|
+
const monthStart = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
10620
|
+
const monthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0);
|
|
10621
|
+
const [todayCosts, mtdCosts] = await Promise.all([
|
|
10622
|
+
provider.getCostBreakdown(todayStart, todayEnd, "SERVICE"),
|
|
10623
|
+
provider.getCostBreakdown(monthStart, monthEnd, "SERVICE")
|
|
10624
|
+
]);
|
|
10625
|
+
const serviceBreakdown = {};
|
|
10626
|
+
todayCosts.breakdown.forEach((item) => {
|
|
10627
|
+
serviceBreakdown[item.service] = item.cost;
|
|
10628
|
+
});
|
|
10629
|
+
const accountInfo = await provider.getAccountInfo();
|
|
10630
|
+
const response = {
|
|
10631
|
+
account: {
|
|
10632
|
+
id: accountInfo.accountId,
|
|
10633
|
+
name: accountInfo.accountAlias || accountInfo.accountId,
|
|
10634
|
+
provider: config.provider
|
|
10635
|
+
},
|
|
10636
|
+
costs: {
|
|
10637
|
+
today: {
|
|
10638
|
+
total: todayCosts.totalCost,
|
|
10639
|
+
currency: "USD",
|
|
10640
|
+
byService: serviceBreakdown
|
|
10641
|
+
},
|
|
10642
|
+
mtd: {
|
|
10643
|
+
total: mtdCosts.totalCost,
|
|
10644
|
+
projected: mtdCosts.totalCost * (30 / today.getDate())
|
|
10645
|
+
},
|
|
10646
|
+
delta: {
|
|
10647
|
+
vsYesterday: 0,
|
|
10648
|
+
// Would need historical data
|
|
10649
|
+
vsLastWeek: 0
|
|
10650
|
+
// Would need historical data
|
|
10651
|
+
}
|
|
10652
|
+
},
|
|
10653
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10654
|
+
};
|
|
10655
|
+
res.json(createApiResponse(response));
|
|
10656
|
+
} catch (error) {
|
|
10657
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10658
|
+
}
|
|
10659
|
+
});
|
|
10660
|
+
router.get("/services", async (req, res) => {
|
|
10661
|
+
try {
|
|
10662
|
+
const provider = await getProviderFromConfig();
|
|
10663
|
+
const { startDate, endDate } = req.query;
|
|
10664
|
+
const start = startDate ? new Date(startDate) : /* @__PURE__ */ new Date();
|
|
10665
|
+
const end = endDate ? new Date(endDate) : /* @__PURE__ */ new Date();
|
|
10666
|
+
const breakdown = await provider.getCostBreakdown(start, end, "SERVICE");
|
|
10667
|
+
const services = breakdown.breakdown.map((item) => ({
|
|
10668
|
+
service: item.service,
|
|
10669
|
+
cost: item.cost,
|
|
10670
|
+
percentage: item.cost / breakdown.totalCost * 100
|
|
10671
|
+
}));
|
|
10672
|
+
res.json(
|
|
10673
|
+
createApiResponse({
|
|
10674
|
+
total: breakdown.totalCost,
|
|
10675
|
+
services,
|
|
10676
|
+
period: {
|
|
10677
|
+
start: start.toISOString(),
|
|
10678
|
+
end: end.toISOString()
|
|
10679
|
+
}
|
|
10680
|
+
})
|
|
10681
|
+
);
|
|
10682
|
+
} catch (error) {
|
|
10683
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10684
|
+
}
|
|
10685
|
+
});
|
|
10686
|
+
router.get("/daily", async (req, res) => {
|
|
10687
|
+
try {
|
|
10688
|
+
const provider = await getProviderFromConfig();
|
|
10689
|
+
const { days = 30 } = req.query;
|
|
10690
|
+
const daysNum = parseInt(days, 10);
|
|
10691
|
+
const end = /* @__PURE__ */ new Date();
|
|
10692
|
+
const start = /* @__PURE__ */ new Date();
|
|
10693
|
+
start.setDate(start.getDate() - daysNum);
|
|
10694
|
+
const breakdown = await provider.getCostBreakdown(start, end, "DAILY");
|
|
10695
|
+
const dailyCosts = breakdown.breakdown.map((item) => ({
|
|
10696
|
+
date: item.date,
|
|
10697
|
+
cost: item.cost
|
|
10698
|
+
}));
|
|
10699
|
+
res.json(
|
|
10700
|
+
createApiResponse({
|
|
10701
|
+
total: breakdown.totalCost,
|
|
10702
|
+
daily: dailyCosts,
|
|
10703
|
+
period: {
|
|
10704
|
+
start: start.toISOString(),
|
|
10705
|
+
end: end.toISOString(),
|
|
10706
|
+
days: daysNum
|
|
10707
|
+
}
|
|
10708
|
+
})
|
|
10709
|
+
);
|
|
10710
|
+
} catch (error) {
|
|
10711
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10712
|
+
}
|
|
10713
|
+
});
|
|
10714
|
+
router.get("/trends", async (req, res) => {
|
|
10715
|
+
try {
|
|
10716
|
+
const provider = await getProviderFromConfig();
|
|
10717
|
+
const end = /* @__PURE__ */ new Date();
|
|
10718
|
+
const start = /* @__PURE__ */ new Date();
|
|
10719
|
+
start.setMonth(start.getMonth() - 3);
|
|
10720
|
+
const breakdown = await provider.getCostBreakdown(start, end, "MONTHLY");
|
|
10721
|
+
const trends = breakdown.breakdown.map((item, index, arr) => {
|
|
10722
|
+
const previousMonth = index > 0 ? arr[index - 1].cost : null;
|
|
10723
|
+
const changePercent = previousMonth ? (item.cost - previousMonth) / previousMonth * 100 : 0;
|
|
10724
|
+
return {
|
|
10725
|
+
month: item.date,
|
|
10726
|
+
cost: item.cost,
|
|
10727
|
+
change: item.cost - (previousMonth || 0),
|
|
10728
|
+
changePercent
|
|
10729
|
+
};
|
|
10730
|
+
});
|
|
10731
|
+
res.json(
|
|
10732
|
+
createApiResponse({
|
|
10733
|
+
trends,
|
|
10734
|
+
average: breakdown.totalCost / breakdown.breakdown.length,
|
|
10735
|
+
period: {
|
|
10736
|
+
start: start.toISOString(),
|
|
10737
|
+
end: end.toISOString()
|
|
10738
|
+
}
|
|
10739
|
+
})
|
|
10740
|
+
);
|
|
10741
|
+
} catch (error) {
|
|
10742
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10743
|
+
}
|
|
10744
|
+
});
|
|
10745
|
+
costs_default = router;
|
|
10371
10746
|
}
|
|
10372
|
-
};
|
|
10373
|
-
|
|
10374
|
-
// src/cli/index.ts
|
|
10375
|
-
init_logging();
|
|
10376
|
-
|
|
10377
|
-
// src/core/config/index.ts
|
|
10378
|
-
init_schema();
|
|
10747
|
+
});
|
|
10379
10748
|
|
|
10380
|
-
// src/
|
|
10381
|
-
var
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
auditEnabled: false
|
|
10749
|
+
// src/api/routes/inventory.ts
|
|
10750
|
+
var inventory_exports2 = {};
|
|
10751
|
+
__export(inventory_exports2, {
|
|
10752
|
+
default: () => inventory_default
|
|
10753
|
+
});
|
|
10754
|
+
var import_express2, router2, inventory_default;
|
|
10755
|
+
var init_inventory6 = __esm({
|
|
10756
|
+
"src/api/routes/inventory.ts"() {
|
|
10757
|
+
import_express2 = require("express");
|
|
10758
|
+
init_utils();
|
|
10759
|
+
init_server();
|
|
10760
|
+
router2 = (0, import_express2.Router)();
|
|
10761
|
+
router2.get("/", async (req, res) => {
|
|
10762
|
+
try {
|
|
10763
|
+
const provider = await getProviderFromConfig();
|
|
10764
|
+
const inventory = await provider.getResourceInventory();
|
|
10765
|
+
const summary = {
|
|
10766
|
+
totalResources: inventory.resources.length,
|
|
10767
|
+
byType: inventory.resources.reduce((acc, resource) => {
|
|
10768
|
+
acc[resource.type] = (acc[resource.type] || 0) + 1;
|
|
10769
|
+
return acc;
|
|
10770
|
+
}, {}),
|
|
10771
|
+
byRegion: inventory.resources.reduce((acc, resource) => {
|
|
10772
|
+
const region = resource.region || "global";
|
|
10773
|
+
acc[region] = (acc[region] || 0) + 1;
|
|
10774
|
+
return acc;
|
|
10775
|
+
}, {})
|
|
10776
|
+
};
|
|
10777
|
+
res.json(
|
|
10778
|
+
createApiResponse({
|
|
10779
|
+
summary,
|
|
10780
|
+
resources: inventory.resources
|
|
10781
|
+
})
|
|
10782
|
+
);
|
|
10783
|
+
} catch (error) {
|
|
10784
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10785
|
+
}
|
|
10786
|
+
});
|
|
10787
|
+
inventory_default = router2;
|
|
10420
10788
|
}
|
|
10421
|
-
};
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10789
|
+
});
|
|
10790
|
+
|
|
10791
|
+
// src/api/routes/optimization.ts
|
|
10792
|
+
var optimization_exports = {};
|
|
10793
|
+
__export(optimization_exports, {
|
|
10794
|
+
default: () => optimization_default
|
|
10795
|
+
});
|
|
10796
|
+
var import_express3, router3, optimization_default;
|
|
10797
|
+
var init_optimization = __esm({
|
|
10798
|
+
"src/api/routes/optimization.ts"() {
|
|
10799
|
+
import_express3 = require("express");
|
|
10800
|
+
init_utils();
|
|
10801
|
+
init_server();
|
|
10802
|
+
router3 = (0, import_express3.Router)();
|
|
10803
|
+
router3.get("/", async (req, res) => {
|
|
10804
|
+
try {
|
|
10805
|
+
const provider = await getProviderFromConfig();
|
|
10806
|
+
const recommendations = await provider.getOptimizationRecommendations();
|
|
10807
|
+
const summary = {
|
|
10808
|
+
totalSavings: recommendations.reduce((sum, rec) => sum + (rec.estimatedMonthlySavings || 0), 0),
|
|
10809
|
+
recommendationCount: recommendations.length,
|
|
10810
|
+
byCategory: recommendations.reduce((acc, rec) => {
|
|
10811
|
+
const category = rec.category || "other";
|
|
10812
|
+
acc[category] = (acc[category] || 0) + 1;
|
|
10813
|
+
return acc;
|
|
10814
|
+
}, {})
|
|
10815
|
+
};
|
|
10816
|
+
res.json(
|
|
10817
|
+
createApiResponse({
|
|
10818
|
+
summary,
|
|
10819
|
+
recommendations
|
|
10820
|
+
})
|
|
10821
|
+
);
|
|
10822
|
+
} catch (error) {
|
|
10823
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10439
10824
|
}
|
|
10440
|
-
}
|
|
10441
|
-
|
|
10442
|
-
} catch (error) {
|
|
10443
|
-
console.warn(`Warning: Could not load config from ${configPath}: ${error.message}`);
|
|
10444
|
-
return {};
|
|
10445
|
-
}
|
|
10446
|
-
}
|
|
10447
|
-
__name(loadConfigFile, "loadConfigFile");
|
|
10448
|
-
function resolveEnvVars(config) {
|
|
10449
|
-
const resolved = JSON.parse(JSON.stringify(config));
|
|
10450
|
-
if (process.env.AWS_ACCESS_KEY_ID) {
|
|
10451
|
-
resolved.accessKey = process.env.AWS_ACCESS_KEY_ID;
|
|
10452
|
-
}
|
|
10453
|
-
if (process.env.AWS_SECRET_ACCESS_KEY) {
|
|
10454
|
-
resolved.secretKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
10825
|
+
});
|
|
10826
|
+
optimization_default = router3;
|
|
10455
10827
|
}
|
|
10456
|
-
|
|
10457
|
-
|
|
10828
|
+
});
|
|
10829
|
+
|
|
10830
|
+
// src/api/routes/chargeback.ts
|
|
10831
|
+
var chargeback_exports = {};
|
|
10832
|
+
__export(chargeback_exports, {
|
|
10833
|
+
default: () => chargeback_default
|
|
10834
|
+
});
|
|
10835
|
+
var import_express4, router4, chargeback_default;
|
|
10836
|
+
var init_chargeback = __esm({
|
|
10837
|
+
"src/api/routes/chargeback.ts"() {
|
|
10838
|
+
import_express4 = require("express");
|
|
10839
|
+
init_utils();
|
|
10840
|
+
init_server();
|
|
10841
|
+
router4 = (0, import_express4.Router)();
|
|
10842
|
+
router4.get("/", async (req, res) => {
|
|
10843
|
+
try {
|
|
10844
|
+
const provider = await getProviderFromConfig();
|
|
10845
|
+
const { groupBy = "tag" } = req.query;
|
|
10846
|
+
const now = /* @__PURE__ */ new Date();
|
|
10847
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
10848
|
+
const breakdown = await provider.getCostBreakdown(
|
|
10849
|
+
monthStart,
|
|
10850
|
+
now,
|
|
10851
|
+
groupBy === "tag" ? "TAG" : "SERVICE"
|
|
10852
|
+
);
|
|
10853
|
+
res.json(
|
|
10854
|
+
createApiResponse({
|
|
10855
|
+
total: breakdown.totalCost,
|
|
10856
|
+
breakdown: breakdown.breakdown,
|
|
10857
|
+
period: {
|
|
10858
|
+
start: monthStart.toISOString(),
|
|
10859
|
+
end: now.toISOString()
|
|
10860
|
+
},
|
|
10861
|
+
groupBy
|
|
10862
|
+
})
|
|
10863
|
+
);
|
|
10864
|
+
} catch (error) {
|
|
10865
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10866
|
+
}
|
|
10867
|
+
});
|
|
10868
|
+
chargeback_default = router4;
|
|
10458
10869
|
}
|
|
10459
|
-
|
|
10460
|
-
|
|
10870
|
+
});
|
|
10871
|
+
|
|
10872
|
+
// src/api/routes/forecast.ts
|
|
10873
|
+
var forecast_exports = {};
|
|
10874
|
+
__export(forecast_exports, {
|
|
10875
|
+
default: () => forecast_default
|
|
10876
|
+
});
|
|
10877
|
+
var import_express5, router5, forecast_default;
|
|
10878
|
+
var init_forecast = __esm({
|
|
10879
|
+
"src/api/routes/forecast.ts"() {
|
|
10880
|
+
import_express5 = require("express");
|
|
10881
|
+
init_server();
|
|
10882
|
+
router5 = (0, import_express5.Router)();
|
|
10883
|
+
router5.get("/", async (req, res) => {
|
|
10884
|
+
try {
|
|
10885
|
+
const { days = 30 } = req.query;
|
|
10886
|
+
const daysNum = parseInt(days, 10);
|
|
10887
|
+
const currentDailyAverage = 150;
|
|
10888
|
+
const projectedDaily = currentDailyAverage * 1.05;
|
|
10889
|
+
const forecast = [];
|
|
10890
|
+
for (let i = 1; i <= daysNum; i++) {
|
|
10891
|
+
const date = /* @__PURE__ */ new Date();
|
|
10892
|
+
date.setDate(date.getDate() + i);
|
|
10893
|
+
forecast.push({
|
|
10894
|
+
date: date.toISOString().split("T")[0],
|
|
10895
|
+
projected: projectedDaily,
|
|
10896
|
+
confidence: Math.max(0.9 - i * 0.01, 0.5)
|
|
10897
|
+
// Decreasing confidence
|
|
10898
|
+
});
|
|
10899
|
+
}
|
|
10900
|
+
const totalProjected = projectedDaily * daysNum;
|
|
10901
|
+
res.json(
|
|
10902
|
+
createApiResponse({
|
|
10903
|
+
forecast,
|
|
10904
|
+
summary: {
|
|
10905
|
+
totalProjected,
|
|
10906
|
+
averageDaily: projectedDaily,
|
|
10907
|
+
period: {
|
|
10908
|
+
days: daysNum,
|
|
10909
|
+
start: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
10910
|
+
end: forecast[forecast.length - 1].date
|
|
10911
|
+
}
|
|
10912
|
+
},
|
|
10913
|
+
model: "linear-growth",
|
|
10914
|
+
confidence: 0.75
|
|
10915
|
+
})
|
|
10916
|
+
);
|
|
10917
|
+
} catch (error) {
|
|
10918
|
+
res.status(500).json(createErrorResponse("FORECAST_ERROR", error.message));
|
|
10919
|
+
}
|
|
10920
|
+
});
|
|
10921
|
+
forecast_default = router5;
|
|
10461
10922
|
}
|
|
10462
|
-
|
|
10463
|
-
|
|
10923
|
+
});
|
|
10924
|
+
|
|
10925
|
+
// src/api/routes/accounts.ts
|
|
10926
|
+
var accounts_exports = {};
|
|
10927
|
+
__export(accounts_exports, {
|
|
10928
|
+
default: () => accounts_default
|
|
10929
|
+
});
|
|
10930
|
+
var import_express6, router6, accounts_default;
|
|
10931
|
+
var init_accounts = __esm({
|
|
10932
|
+
"src/api/routes/accounts.ts"() {
|
|
10933
|
+
import_express6 = require("express");
|
|
10934
|
+
init_utils();
|
|
10935
|
+
init_server();
|
|
10936
|
+
router6 = (0, import_express6.Router)();
|
|
10937
|
+
router6.get("/", async (req, res) => {
|
|
10938
|
+
try {
|
|
10939
|
+
const config = getConfig2();
|
|
10940
|
+
const provider = await getProviderFromConfig();
|
|
10941
|
+
const accountInfo = await provider.getAccountInfo();
|
|
10942
|
+
const accounts = [
|
|
10943
|
+
{
|
|
10944
|
+
id: accountInfo.accountId,
|
|
10945
|
+
name: accountInfo.accountAlias || accountInfo.accountId,
|
|
10946
|
+
provider: config.provider,
|
|
10947
|
+
region: accountInfo.region
|
|
10948
|
+
}
|
|
10949
|
+
];
|
|
10950
|
+
res.json(
|
|
10951
|
+
createApiResponse({
|
|
10952
|
+
accounts,
|
|
10953
|
+
count: accounts.length
|
|
10954
|
+
})
|
|
10955
|
+
);
|
|
10956
|
+
} catch (error) {
|
|
10957
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10958
|
+
}
|
|
10959
|
+
});
|
|
10960
|
+
router6.get("/:id/costs", async (req, res) => {
|
|
10961
|
+
try {
|
|
10962
|
+
const config = getConfig2();
|
|
10963
|
+
const provider = await getProviderFromConfig();
|
|
10964
|
+
const { id } = req.params;
|
|
10965
|
+
const accountInfo = await provider.getAccountInfo();
|
|
10966
|
+
if (accountInfo.accountId !== id) {
|
|
10967
|
+
return res.status(404).json(createErrorResponse("NOT_FOUND", `Account ${id} not found`));
|
|
10968
|
+
}
|
|
10969
|
+
const now = /* @__PURE__ */ new Date();
|
|
10970
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
10971
|
+
const costs = await provider.getCostBreakdown(monthStart, now, "SERVICE");
|
|
10972
|
+
res.json(
|
|
10973
|
+
createApiResponse({
|
|
10974
|
+
accountId: id,
|
|
10975
|
+
costs: {
|
|
10976
|
+
total: costs.totalCost,
|
|
10977
|
+
breakdown: costs.breakdown
|
|
10978
|
+
},
|
|
10979
|
+
period: {
|
|
10980
|
+
start: monthStart.toISOString(),
|
|
10981
|
+
end: now.toISOString()
|
|
10982
|
+
}
|
|
10983
|
+
})
|
|
10984
|
+
);
|
|
10985
|
+
} catch (error) {
|
|
10986
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10987
|
+
}
|
|
10988
|
+
});
|
|
10989
|
+
accounts_default = router6;
|
|
10464
10990
|
}
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10991
|
+
});
|
|
10992
|
+
|
|
10993
|
+
// src/api/routes/reports.ts
|
|
10994
|
+
var reports_exports2 = {};
|
|
10995
|
+
__export(reports_exports2, {
|
|
10996
|
+
default: () => reports_default
|
|
10997
|
+
});
|
|
10998
|
+
var import_express7, router7, reports_default;
|
|
10999
|
+
var init_reports2 = __esm({
|
|
11000
|
+
"src/api/routes/reports.ts"() {
|
|
11001
|
+
import_express7 = require("express");
|
|
11002
|
+
init_utils();
|
|
11003
|
+
init_server();
|
|
11004
|
+
router7 = (0, import_express7.Router)();
|
|
11005
|
+
router7.post("/generate", async (req, res) => {
|
|
11006
|
+
try {
|
|
11007
|
+
const {
|
|
11008
|
+
reportType = "summary",
|
|
11009
|
+
startDate,
|
|
11010
|
+
endDate,
|
|
11011
|
+
format = "json",
|
|
11012
|
+
groupBy = "service"
|
|
11013
|
+
} = req.body;
|
|
11014
|
+
const config = getConfig2();
|
|
11015
|
+
const provider = await getProviderFromConfig();
|
|
11016
|
+
const start = startDate ? new Date(startDate) : /* @__PURE__ */ new Date();
|
|
11017
|
+
const end = endDate ? new Date(endDate) : /* @__PURE__ */ new Date();
|
|
11018
|
+
let groupByType = "SERVICE";
|
|
11019
|
+
if (groupBy === "tag")
|
|
11020
|
+
groupByType = "TAG";
|
|
11021
|
+
else if (groupBy === "daily")
|
|
11022
|
+
groupByType = "DAILY";
|
|
11023
|
+
else if (groupBy === "monthly")
|
|
11024
|
+
groupByType = "MONTHLY";
|
|
11025
|
+
const breakdown = await provider.getCostBreakdown(start, end, groupByType);
|
|
11026
|
+
const report = {
|
|
11027
|
+
reportType,
|
|
11028
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11029
|
+
period: {
|
|
11030
|
+
start: start.toISOString(),
|
|
11031
|
+
end: end.toISOString()
|
|
11032
|
+
},
|
|
11033
|
+
data: {
|
|
11034
|
+
total: breakdown.totalCost,
|
|
11035
|
+
breakdown: breakdown.breakdown,
|
|
11036
|
+
groupBy
|
|
11037
|
+
},
|
|
11038
|
+
format
|
|
11039
|
+
};
|
|
11040
|
+
res.json(createApiResponse(report));
|
|
11041
|
+
} catch (error) {
|
|
11042
|
+
res.status(500).json(createErrorResponse("REPORT_ERROR", error.message));
|
|
11043
|
+
}
|
|
11044
|
+
});
|
|
11045
|
+
reports_default = router7;
|
|
10472
11046
|
}
|
|
10473
|
-
|
|
11047
|
+
});
|
|
11048
|
+
|
|
11049
|
+
// src/api/server.ts
|
|
11050
|
+
function createApiResponse(data) {
|
|
11051
|
+
return {
|
|
11052
|
+
status: "success",
|
|
11053
|
+
data,
|
|
11054
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11055
|
+
};
|
|
10474
11056
|
}
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
11057
|
+
function createErrorResponse(code, message) {
|
|
11058
|
+
return {
|
|
11059
|
+
status: "error",
|
|
11060
|
+
error: { code, message },
|
|
11061
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11062
|
+
};
|
|
11063
|
+
}
|
|
11064
|
+
var import_express8, import_cors, import_express_rate_limit, import_helmet, cache, ApiServer;
|
|
11065
|
+
var init_server = __esm({
|
|
11066
|
+
"src/api/server.ts"() {
|
|
11067
|
+
import_express8 = __toESM(require("express"));
|
|
11068
|
+
import_cors = __toESM(require("cors"));
|
|
11069
|
+
import_express_rate_limit = __toESM(require("express-rate-limit"));
|
|
11070
|
+
import_helmet = __toESM(require("helmet"));
|
|
11071
|
+
cache = /* @__PURE__ */ new Map();
|
|
11072
|
+
ApiServer = class {
|
|
11073
|
+
constructor(config) {
|
|
11074
|
+
this.config = config;
|
|
11075
|
+
this.app = (0, import_express8.default)();
|
|
11076
|
+
this.setupMiddleware();
|
|
11077
|
+
this.setupRoutes();
|
|
11078
|
+
}
|
|
11079
|
+
setupMiddleware() {
|
|
11080
|
+
this.app.use((0, import_helmet.default)());
|
|
11081
|
+
this.app.use(import_express8.default.json());
|
|
11082
|
+
if (this.config.cors.enabled) {
|
|
11083
|
+
this.app.use(
|
|
11084
|
+
(0, import_cors.default)({
|
|
11085
|
+
origin: this.config.cors.origins || "*",
|
|
11086
|
+
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
11087
|
+
credentials: true
|
|
11088
|
+
})
|
|
11089
|
+
);
|
|
11090
|
+
}
|
|
11091
|
+
if (this.config.rateLimit.enabled) {
|
|
11092
|
+
const limiter = (0, import_express_rate_limit.default)({
|
|
11093
|
+
windowMs: this.config.rateLimit.windowMs,
|
|
11094
|
+
max: this.config.rateLimit.max,
|
|
11095
|
+
message: {
|
|
11096
|
+
status: "error",
|
|
11097
|
+
error: {
|
|
11098
|
+
code: "RATE_LIMIT_EXCEEDED",
|
|
11099
|
+
message: "Too many requests, please try again later."
|
|
11100
|
+
},
|
|
11101
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11102
|
+
}
|
|
11103
|
+
});
|
|
11104
|
+
this.app.use("/api/", limiter);
|
|
11105
|
+
}
|
|
11106
|
+
this.app.use("/api/", this.authMiddleware.bind(this));
|
|
11107
|
+
if (this.config.cache.enabled) {
|
|
11108
|
+
this.app.use("/api/", this.cacheMiddleware.bind(this));
|
|
11109
|
+
}
|
|
10482
11110
|
}
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
11111
|
+
authMiddleware(req, res, next) {
|
|
11112
|
+
if (req.path === "/api/v1/health") {
|
|
11113
|
+
return next();
|
|
11114
|
+
}
|
|
11115
|
+
if (this.config.auth.type === "none") {
|
|
11116
|
+
return next();
|
|
11117
|
+
}
|
|
11118
|
+
if (this.config.auth.type === "api-key") {
|
|
11119
|
+
const apiKey = req.headers["x-api-key"] || req.query.apiKey;
|
|
11120
|
+
if (!apiKey || !this.config.auth.apiKeys?.includes(apiKey)) {
|
|
11121
|
+
return res.status(401).json({
|
|
11122
|
+
status: "error",
|
|
11123
|
+
error: {
|
|
11124
|
+
code: "UNAUTHORIZED",
|
|
11125
|
+
message: "Invalid or missing API key"
|
|
11126
|
+
},
|
|
11127
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11128
|
+
});
|
|
11129
|
+
}
|
|
11130
|
+
}
|
|
11131
|
+
next();
|
|
11132
|
+
}
|
|
11133
|
+
cacheMiddleware(req, res, next) {
|
|
11134
|
+
if (req.method !== "GET") {
|
|
11135
|
+
return next();
|
|
11136
|
+
}
|
|
11137
|
+
const key = `${req.method}:${req.path}:${JSON.stringify(req.query)}`;
|
|
11138
|
+
const cached = cache.get(key);
|
|
11139
|
+
if (cached && cached.expiry > Date.now()) {
|
|
11140
|
+
return res.json(cached.data);
|
|
11141
|
+
}
|
|
11142
|
+
const originalJson = res.json.bind(res);
|
|
11143
|
+
res.json = (body) => {
|
|
11144
|
+
if (res.statusCode === 200) {
|
|
11145
|
+
cache.set(key, {
|
|
11146
|
+
data: body,
|
|
11147
|
+
expiry: Date.now() + this.config.cache.ttl * 1e3
|
|
11148
|
+
});
|
|
11149
|
+
}
|
|
11150
|
+
return originalJson(body);
|
|
11151
|
+
};
|
|
11152
|
+
next();
|
|
11153
|
+
}
|
|
11154
|
+
setupRoutes() {
|
|
11155
|
+
this.app.get("/api/v1/health", (_req, res) => {
|
|
11156
|
+
res.json({
|
|
11157
|
+
status: "success",
|
|
11158
|
+
data: {
|
|
11159
|
+
healthy: true,
|
|
11160
|
+
version: require_package().version,
|
|
11161
|
+
uptime: process.uptime()
|
|
11162
|
+
},
|
|
11163
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11164
|
+
});
|
|
11165
|
+
});
|
|
11166
|
+
this.app.use("/api/v1/costs", (init_costs2(), __toCommonJS(costs_exports2)).default);
|
|
11167
|
+
this.app.use("/api/v1/inventory", (init_inventory6(), __toCommonJS(inventory_exports2)).default);
|
|
11168
|
+
this.app.use("/api/v1/optimization", (init_optimization(), __toCommonJS(optimization_exports)).default);
|
|
11169
|
+
this.app.use("/api/v1/chargeback", (init_chargeback(), __toCommonJS(chargeback_exports)).default);
|
|
11170
|
+
this.app.use("/api/v1/forecast", (init_forecast(), __toCommonJS(forecast_exports)).default);
|
|
11171
|
+
this.app.use("/api/v1/accounts", (init_accounts(), __toCommonJS(accounts_exports)).default);
|
|
11172
|
+
this.app.use("/api/v1/reports", (init_reports2(), __toCommonJS(reports_exports2)).default);
|
|
11173
|
+
this.app.use((_req, res) => {
|
|
11174
|
+
res.status(404).json({
|
|
11175
|
+
status: "error",
|
|
11176
|
+
error: {
|
|
11177
|
+
code: "NOT_FOUND",
|
|
11178
|
+
message: "API endpoint not found"
|
|
11179
|
+
},
|
|
11180
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11181
|
+
});
|
|
11182
|
+
});
|
|
11183
|
+
this.app.use((err, _req, res, _next) => {
|
|
11184
|
+
console.error("API Error:", err);
|
|
11185
|
+
res.status(500).json({
|
|
11186
|
+
status: "error",
|
|
11187
|
+
error: {
|
|
11188
|
+
code: "INTERNAL_ERROR",
|
|
11189
|
+
message: err.message || "Internal server error"
|
|
11190
|
+
},
|
|
11191
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11192
|
+
});
|
|
11193
|
+
});
|
|
11194
|
+
}
|
|
11195
|
+
async start() {
|
|
11196
|
+
return new Promise((resolve2, reject) => {
|
|
11197
|
+
try {
|
|
11198
|
+
this.server = this.app.listen(this.config.port, this.config.host, () => {
|
|
11199
|
+
console.log(
|
|
11200
|
+
`\u2705 API Server running at http://${this.config.host}:${this.config.port}`
|
|
11201
|
+
);
|
|
11202
|
+
console.log(`\u{1F4D6} API Documentation: http://${this.config.host}:${this.config.port}/api/docs`);
|
|
11203
|
+
resolve2();
|
|
11204
|
+
});
|
|
11205
|
+
this.server.on("error", reject);
|
|
11206
|
+
} catch (error) {
|
|
11207
|
+
reject(error);
|
|
11208
|
+
}
|
|
11209
|
+
});
|
|
11210
|
+
}
|
|
11211
|
+
async stop() {
|
|
11212
|
+
return new Promise((resolve2, reject) => {
|
|
11213
|
+
if (!this.server) {
|
|
11214
|
+
resolve2();
|
|
11215
|
+
return;
|
|
11216
|
+
}
|
|
11217
|
+
this.server.close((err) => {
|
|
11218
|
+
if (err) {
|
|
11219
|
+
reject(err);
|
|
11220
|
+
} else {
|
|
11221
|
+
console.log("API Server stopped");
|
|
11222
|
+
resolve2();
|
|
11223
|
+
}
|
|
11224
|
+
});
|
|
11225
|
+
});
|
|
11226
|
+
}
|
|
11227
|
+
getApp() {
|
|
11228
|
+
return this.app;
|
|
10487
11229
|
}
|
|
11230
|
+
};
|
|
11231
|
+
__name(ApiServer, "ApiServer");
|
|
11232
|
+
__name(createApiResponse, "createApiResponse");
|
|
11233
|
+
__name(createErrorResponse, "createErrorResponse");
|
|
11234
|
+
}
|
|
11235
|
+
});
|
|
11236
|
+
|
|
11237
|
+
// src/cli/index.ts
|
|
11238
|
+
var cli_exports = {};
|
|
11239
|
+
__export(cli_exports, {
|
|
11240
|
+
createCLI: () => createCLI,
|
|
11241
|
+
main: () => main
|
|
11242
|
+
});
|
|
11243
|
+
module.exports = __toCommonJS(cli_exports);
|
|
11244
|
+
var import_commander = require("commander");
|
|
11245
|
+
var import_package = __toESM(require_package());
|
|
11246
|
+
init_logging();
|
|
11247
|
+
|
|
11248
|
+
// src/core/config/index.ts
|
|
11249
|
+
init_schema();
|
|
11250
|
+
init_loader();
|
|
11251
|
+
|
|
11252
|
+
// src/core/config/discovery.ts
|
|
11253
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
11254
|
+
init_providers();
|
|
11255
|
+
|
|
11256
|
+
// src/cli/commands/now.ts
|
|
11257
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
11258
|
+
init_factory();
|
|
11259
|
+
init_providers();
|
|
11260
|
+
function registerNowCommand(program) {
|
|
11261
|
+
program.command("now").description("Quick cost check - see today's spending instantly").option("--provider <provider>", "Cloud provider (aws, gcp, azure)", "aws").option("-p, --profile <profile>", "Cloud profile to use").option("--all-profiles", "Show summary across all configured profiles").option("--json", "Output in JSON format").action(async (options) => {
|
|
11262
|
+
try {
|
|
11263
|
+
await handleNowCommand(options);
|
|
11264
|
+
} catch (error) {
|
|
11265
|
+
console.error(import_chalk6.default.red("Error:"), error.message);
|
|
11266
|
+
process.exit(1);
|
|
10488
11267
|
}
|
|
11268
|
+
});
|
|
11269
|
+
}
|
|
11270
|
+
__name(registerNowCommand, "registerNowCommand");
|
|
11271
|
+
async function handleNowCommand(options) {
|
|
11272
|
+
const config = await autoLoadConfig();
|
|
11273
|
+
const provider = normalizeProvider(options.provider || config.provider || "aws");
|
|
11274
|
+
if (options.allProfiles) {
|
|
11275
|
+
throw new Error("--all-profiles not yet implemented. Use without this flag to see default profile.");
|
|
11276
|
+
}
|
|
11277
|
+
const costData = await getTodaysCost(provider, options.profile || config.profile);
|
|
11278
|
+
if (options.json) {
|
|
11279
|
+
console.log(JSON.stringify(costData, null, 2));
|
|
11280
|
+
} else {
|
|
11281
|
+
displayFancyOutput(costData);
|
|
10489
11282
|
}
|
|
10490
|
-
return result;
|
|
10491
11283
|
}
|
|
10492
|
-
__name(
|
|
10493
|
-
function
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
config.region = options.region;
|
|
10514
|
-
if (options.accessKey)
|
|
10515
|
-
config.accessKey = options.accessKey;
|
|
10516
|
-
if (options.secretKey)
|
|
10517
|
-
config.secretKey = options.secretKey;
|
|
10518
|
-
if (options.sessionToken)
|
|
10519
|
-
config.sessionToken = options.sessionToken;
|
|
10520
|
-
if (options.json || options.text || options.summary) {
|
|
10521
|
-
config.output = config.output || {};
|
|
10522
|
-
if (options.json)
|
|
10523
|
-
config.output.format = "json";
|
|
10524
|
-
if (options.text)
|
|
10525
|
-
config.output.format = "text";
|
|
10526
|
-
if (options.summary)
|
|
10527
|
-
config.output.summary = true;
|
|
10528
|
-
}
|
|
10529
|
-
if (options.delta !== void 0) {
|
|
10530
|
-
config.output = config.output || {};
|
|
10531
|
-
config.output.showDelta = options.delta;
|
|
10532
|
-
}
|
|
10533
|
-
if (options.deltaThreshold) {
|
|
10534
|
-
config.output = config.output || {};
|
|
10535
|
-
config.output.deltaThreshold = parseFloat(options.deltaThreshold);
|
|
10536
|
-
}
|
|
10537
|
-
if (options.quickWins !== void 0) {
|
|
10538
|
-
config.output = config.output || {};
|
|
10539
|
-
config.output.showQuickWins = options.quickWins;
|
|
10540
|
-
}
|
|
10541
|
-
if (options.quickWinsCount) {
|
|
10542
|
-
config.output = config.output || {};
|
|
10543
|
-
config.output.quickWinsCount = parseInt(options.quickWinsCount, 10);
|
|
10544
|
-
}
|
|
10545
|
-
if (options.cache !== void 0 || options.noCache !== void 0) {
|
|
10546
|
-
config.cache = config.cache || {};
|
|
10547
|
-
config.cache.enabled = options.cache === true || options.noCache !== true;
|
|
10548
|
-
}
|
|
10549
|
-
if (options.cacheTtl) {
|
|
10550
|
-
config.cache = config.cache || {};
|
|
10551
|
-
config.cache.ttl = options.cacheTtl;
|
|
10552
|
-
}
|
|
10553
|
-
if (options.cacheType) {
|
|
10554
|
-
config.cache = config.cache || {};
|
|
10555
|
-
config.cache.type = options.cacheType;
|
|
10556
|
-
}
|
|
10557
|
-
if (options.slackToken || options.slackChannel) {
|
|
10558
|
-
config.slack = config.slack || {};
|
|
10559
|
-
if (options.slackToken)
|
|
10560
|
-
config.slack.token = options.slackToken;
|
|
10561
|
-
if (options.slackChannel)
|
|
10562
|
-
config.slack.channel = options.slackChannel;
|
|
10563
|
-
config.slack.enabled = true;
|
|
10564
|
-
}
|
|
10565
|
-
if (options.logLevel || options.verbose || options.quiet) {
|
|
10566
|
-
config.logging = config.logging || {};
|
|
10567
|
-
if (options.logLevel)
|
|
10568
|
-
config.logging.level = options.logLevel;
|
|
10569
|
-
if (options.verbose)
|
|
10570
|
-
config.logging.level = "debug";
|
|
10571
|
-
if (options.quiet)
|
|
10572
|
-
config.logging.level = "error";
|
|
10573
|
-
}
|
|
10574
|
-
return config;
|
|
10575
|
-
}
|
|
10576
|
-
__name(mapCliOptionsToConfig, "mapCliOptionsToConfig");
|
|
10577
|
-
|
|
10578
|
-
// src/core/config/discovery.ts
|
|
10579
|
-
var import_chalk2 = __toESM(require("chalk"));
|
|
10580
|
-
init_providers();
|
|
10581
|
-
|
|
10582
|
-
// src/cli/commands/now.ts
|
|
10583
|
-
var import_chalk6 = __toESM(require("chalk"));
|
|
10584
|
-
init_factory();
|
|
10585
|
-
init_providers();
|
|
10586
|
-
function registerNowCommand(program) {
|
|
10587
|
-
program.command("now").description("Quick cost check - see today's spending instantly").option("--provider <provider>", "Cloud provider (aws, gcp, azure)", "aws").option("-p, --profile <profile>", "Cloud profile to use").option("--all-profiles", "Show summary across all configured profiles").option("--json", "Output in JSON format").action(async (options) => {
|
|
10588
|
-
try {
|
|
10589
|
-
await handleNowCommand(options);
|
|
10590
|
-
} catch (error) {
|
|
10591
|
-
console.error(import_chalk6.default.red("Error:"), error.message);
|
|
10592
|
-
process.exit(1);
|
|
10593
|
-
}
|
|
10594
|
-
});
|
|
10595
|
-
}
|
|
10596
|
-
__name(registerNowCommand, "registerNowCommand");
|
|
10597
|
-
async function handleNowCommand(options) {
|
|
10598
|
-
const config = await autoLoadConfig();
|
|
10599
|
-
const provider = normalizeProvider(options.provider || config.provider || "aws");
|
|
10600
|
-
if (options.allProfiles) {
|
|
10601
|
-
throw new Error("--all-profiles not yet implemented. Use without this flag to see default profile.");
|
|
10602
|
-
}
|
|
10603
|
-
const costData = await getTodaysCost(provider, options.profile || config.profile);
|
|
10604
|
-
if (options.json) {
|
|
10605
|
-
console.log(JSON.stringify(costData, null, 2));
|
|
10606
|
-
} else {
|
|
10607
|
-
displayFancyOutput(costData);
|
|
10608
|
-
}
|
|
10609
|
-
}
|
|
10610
|
-
__name(handleNowCommand, "handleNowCommand");
|
|
10611
|
-
function normalizeProvider(providerStr) {
|
|
10612
|
-
const normalized = providerStr.toLowerCase();
|
|
10613
|
-
switch (normalized) {
|
|
10614
|
-
case "aws":
|
|
10615
|
-
return "aws" /* AWS */;
|
|
10616
|
-
case "gcp":
|
|
10617
|
-
case "google":
|
|
10618
|
-
case "google-cloud":
|
|
10619
|
-
return "gcp" /* GCP */;
|
|
10620
|
-
case "azure":
|
|
10621
|
-
case "microsoft":
|
|
10622
|
-
return "azure" /* AZURE */;
|
|
10623
|
-
case "alibaba":
|
|
10624
|
-
case "alicloud":
|
|
10625
|
-
return "alicloud" /* ALIBABA_CLOUD */;
|
|
10626
|
-
case "oracle":
|
|
10627
|
-
case "oci":
|
|
10628
|
-
return "oracle" /* ORACLE_CLOUD */;
|
|
10629
|
-
default:
|
|
10630
|
-
throw new Error(`Unsupported provider: ${providerStr}. Use: aws, gcp, azure, alibaba, or oracle`);
|
|
11284
|
+
__name(handleNowCommand, "handleNowCommand");
|
|
11285
|
+
function normalizeProvider(providerStr) {
|
|
11286
|
+
const normalized = providerStr.toLowerCase();
|
|
11287
|
+
switch (normalized) {
|
|
11288
|
+
case "aws":
|
|
11289
|
+
return "aws" /* AWS */;
|
|
11290
|
+
case "gcp":
|
|
11291
|
+
case "google":
|
|
11292
|
+
case "google-cloud":
|
|
11293
|
+
return "gcp" /* GCP */;
|
|
11294
|
+
case "azure":
|
|
11295
|
+
case "microsoft":
|
|
11296
|
+
return "azure" /* AZURE */;
|
|
11297
|
+
case "alibaba":
|
|
11298
|
+
case "alicloud":
|
|
11299
|
+
return "alicloud" /* ALIBABA_CLOUD */;
|
|
11300
|
+
case "oracle":
|
|
11301
|
+
case "oci":
|
|
11302
|
+
return "oracle" /* ORACLE_CLOUD */;
|
|
11303
|
+
default:
|
|
11304
|
+
throw new Error(`Unsupported provider: ${providerStr}. Use: aws, gcp, azure, alibaba, or oracle`);
|
|
10631
11305
|
}
|
|
10632
11306
|
}
|
|
10633
11307
|
__name(normalizeProvider, "normalizeProvider");
|
|
@@ -11926,8 +12600,8 @@ __name(registerExportCommands, "registerExportCommands");
|
|
|
11926
12600
|
function registerOrganizationsCommands(program) {
|
|
11927
12601
|
const orgs = program.command("organizations").alias("orgs").description("AWS Organizations multi-account management");
|
|
11928
12602
|
orgs.command("list").description("List all accounts in the organization").option("--include-inactive", "Include suspended/closed accounts").action(async (options, command) => {
|
|
11929
|
-
const { handleList:
|
|
11930
|
-
await
|
|
12603
|
+
const { handleList: handleList4 } = await Promise.resolve().then(() => (init_list(), list_exports));
|
|
12604
|
+
await handleList4(options, command);
|
|
11931
12605
|
});
|
|
11932
12606
|
orgs.command("summary").description("Multi-account cost summary").option("--group-by <field>", "Group by (account, ou, tag)", "account").action(async (options, command) => {
|
|
11933
12607
|
const { handleSummary: handleSummary2 } = await Promise.resolve().then(() => (init_summary(), summary_exports));
|
|
@@ -12515,611 +13189,1581 @@ function isGitRepository() {
|
|
|
12515
13189
|
return false;
|
|
12516
13190
|
}
|
|
12517
13191
|
}
|
|
12518
|
-
__name(isGitRepository, "isGitRepository");
|
|
12519
|
-
function getPeriodDays(period) {
|
|
12520
|
-
const periodMap = {
|
|
12521
|
-
day: 1,
|
|
12522
|
-
week: 7,
|
|
12523
|
-
month: 30,
|
|
12524
|
-
quarter: 90,
|
|
12525
|
-
year: 365
|
|
12526
|
-
};
|
|
12527
|
-
return periodMap[period] || 7;
|
|
13192
|
+
__name(isGitRepository, "isGitRepository");
|
|
13193
|
+
function getPeriodDays(period) {
|
|
13194
|
+
const periodMap = {
|
|
13195
|
+
day: 1,
|
|
13196
|
+
week: 7,
|
|
13197
|
+
month: 30,
|
|
13198
|
+
quarter: 90,
|
|
13199
|
+
year: 365
|
|
13200
|
+
};
|
|
13201
|
+
return periodMap[period] || 7;
|
|
13202
|
+
}
|
|
13203
|
+
__name(getPeriodDays, "getPeriodDays");
|
|
13204
|
+
function getPeriodLabel(period) {
|
|
13205
|
+
const labels = {
|
|
13206
|
+
day: "Today",
|
|
13207
|
+
week: "This Week",
|
|
13208
|
+
month: "This Month",
|
|
13209
|
+
quarter: "This Quarter",
|
|
13210
|
+
year: "This Year"
|
|
13211
|
+
};
|
|
13212
|
+
return labels[period] || period;
|
|
13213
|
+
}
|
|
13214
|
+
__name(getPeriodLabel, "getPeriodLabel");
|
|
13215
|
+
|
|
13216
|
+
// src/cli/commands/terraform/index.ts
|
|
13217
|
+
var import_fs4 = require("fs");
|
|
13218
|
+
var import_child_process2 = require("child_process");
|
|
13219
|
+
var import_chalk17 = __toESM(require("chalk"));
|
|
13220
|
+
var AWS_PRICING = {
|
|
13221
|
+
// EC2 instances (us-east-1 on-demand hourly rates)
|
|
13222
|
+
ec2: {
|
|
13223
|
+
"t2.micro": 0.0116,
|
|
13224
|
+
"t2.small": 0.0232,
|
|
13225
|
+
"t2.medium": 0.0464,
|
|
13226
|
+
"t2.large": 0.0928,
|
|
13227
|
+
"t2.xlarge": 0.1856,
|
|
13228
|
+
"t3.micro": 0.0104,
|
|
13229
|
+
"t3.small": 0.0208,
|
|
13230
|
+
"t3.medium": 0.0416,
|
|
13231
|
+
"t3.large": 0.0832,
|
|
13232
|
+
"t3.xlarge": 0.1664,
|
|
13233
|
+
"t3.2xlarge": 0.3328,
|
|
13234
|
+
"m5.large": 0.096,
|
|
13235
|
+
"m5.xlarge": 0.192,
|
|
13236
|
+
"m5.2xlarge": 0.384,
|
|
13237
|
+
"c5.large": 0.085,
|
|
13238
|
+
"c5.xlarge": 0.17,
|
|
13239
|
+
"r5.large": 0.126,
|
|
13240
|
+
"r5.xlarge": 0.252
|
|
13241
|
+
},
|
|
13242
|
+
// RDS instances (db.r5 family)
|
|
13243
|
+
rds: {
|
|
13244
|
+
"db.t3.micro": 0.017,
|
|
13245
|
+
"db.t3.small": 0.034,
|
|
13246
|
+
"db.t3.medium": 0.068,
|
|
13247
|
+
"db.t3.large": 0.136,
|
|
13248
|
+
"db.r5.large": 0.24,
|
|
13249
|
+
"db.r5.xlarge": 0.48,
|
|
13250
|
+
"db.r5.2xlarge": 0.96,
|
|
13251
|
+
"db.m5.large": 0.196,
|
|
13252
|
+
"db.m5.xlarge": 0.392
|
|
13253
|
+
},
|
|
13254
|
+
// EBS volumes (per GB-month)
|
|
13255
|
+
ebs: {
|
|
13256
|
+
gp2: 0.1,
|
|
13257
|
+
gp3: 0.08,
|
|
13258
|
+
io1: 0.125,
|
|
13259
|
+
io2: 0.125,
|
|
13260
|
+
st1: 0.045,
|
|
13261
|
+
sc1: 0.015
|
|
13262
|
+
},
|
|
13263
|
+
// Load Balancers (per hour)
|
|
13264
|
+
alb: 0.0225,
|
|
13265
|
+
nlb: 0.0225,
|
|
13266
|
+
clb: 0.025,
|
|
13267
|
+
// NAT Gateway (per hour + data transfer)
|
|
13268
|
+
natgateway: 0.045,
|
|
13269
|
+
// ElastiCache (per hour)
|
|
13270
|
+
elasticache: {
|
|
13271
|
+
"cache.t3.micro": 0.017,
|
|
13272
|
+
"cache.t3.small": 0.034,
|
|
13273
|
+
"cache.m5.large": 0.161
|
|
13274
|
+
}
|
|
13275
|
+
};
|
|
13276
|
+
function parseTerraformPlan(planPath) {
|
|
13277
|
+
try {
|
|
13278
|
+
let planJson;
|
|
13279
|
+
if (planPath.endsWith(".json")) {
|
|
13280
|
+
planJson = (0, import_fs4.readFileSync)(planPath, "utf-8");
|
|
13281
|
+
} else {
|
|
13282
|
+
planJson = (0, import_child_process2.execSync)(`terraform show -json "${planPath}"`, {
|
|
13283
|
+
encoding: "utf-8"
|
|
13284
|
+
});
|
|
13285
|
+
}
|
|
13286
|
+
return JSON.parse(planJson);
|
|
13287
|
+
} catch (error) {
|
|
13288
|
+
throw new Error(`Failed to parse Terraform plan: ${error.message}`);
|
|
13289
|
+
}
|
|
13290
|
+
}
|
|
13291
|
+
__name(parseTerraformPlan, "parseTerraformPlan");
|
|
13292
|
+
function estimateResourceCost2(change) {
|
|
13293
|
+
const { type, name, address, change: changeDetails } = change;
|
|
13294
|
+
const action = changeDetails.actions[0];
|
|
13295
|
+
const actionMap = {
|
|
13296
|
+
create: "create",
|
|
13297
|
+
update: "modify",
|
|
13298
|
+
delete: "destroy"
|
|
13299
|
+
};
|
|
13300
|
+
const mappedAction = actionMap[action] || "create";
|
|
13301
|
+
const config = changeDetails.after || changeDetails.before || {};
|
|
13302
|
+
let monthlyCost = 0;
|
|
13303
|
+
let hourlyCost = 0;
|
|
13304
|
+
let details = "";
|
|
13305
|
+
if (type === "aws_instance") {
|
|
13306
|
+
const instanceType = config.instance_type || "t3.micro";
|
|
13307
|
+
hourlyCost = AWS_PRICING.ec2[instanceType] || 0.1;
|
|
13308
|
+
monthlyCost = hourlyCost * 730;
|
|
13309
|
+
details = instanceType;
|
|
13310
|
+
} else if (type === "aws_db_instance") {
|
|
13311
|
+
const instanceClass = config.instance_class || "db.t3.micro";
|
|
13312
|
+
hourlyCost = AWS_PRICING.rds[instanceClass] || 0.1;
|
|
13313
|
+
monthlyCost = hourlyCost * 730;
|
|
13314
|
+
const allocatedStorage = config.allocated_storage || 20;
|
|
13315
|
+
const storageType = config.storage_type || "gp2";
|
|
13316
|
+
const storageCostPerGB = AWS_PRICING.ebs[storageType] || 0.1;
|
|
13317
|
+
monthlyCost += allocatedStorage * storageCostPerGB;
|
|
13318
|
+
details = `${instanceClass}, ${allocatedStorage}GB ${storageType}`;
|
|
13319
|
+
} else if (type === "aws_ebs_volume") {
|
|
13320
|
+
const size = config.size || 8;
|
|
13321
|
+
const volumeType = config.type || "gp2";
|
|
13322
|
+
const costPerGB = AWS_PRICING.ebs[volumeType] || 0.1;
|
|
13323
|
+
monthlyCost = size * costPerGB;
|
|
13324
|
+
hourlyCost = monthlyCost / 730;
|
|
13325
|
+
details = `${size}GB ${volumeType}`;
|
|
13326
|
+
} else if (type === "aws_lb" || type === "aws_alb") {
|
|
13327
|
+
const lbType = config.load_balancer_type || "application";
|
|
13328
|
+
hourlyCost = lbType === "network" ? AWS_PRICING.nlb : AWS_PRICING.alb;
|
|
13329
|
+
monthlyCost = hourlyCost * 730;
|
|
13330
|
+
details = `${lbType} load balancer`;
|
|
13331
|
+
} else if (type === "aws_elb") {
|
|
13332
|
+
hourlyCost = AWS_PRICING.clb;
|
|
13333
|
+
monthlyCost = hourlyCost * 730;
|
|
13334
|
+
details = "classic load balancer";
|
|
13335
|
+
} else if (type === "aws_nat_gateway") {
|
|
13336
|
+
hourlyCost = AWS_PRICING.natgateway;
|
|
13337
|
+
monthlyCost = hourlyCost * 730;
|
|
13338
|
+
details = "NAT gateway (base cost)";
|
|
13339
|
+
} else if (type === "aws_elasticache_cluster") {
|
|
13340
|
+
const nodeType = config.node_type || "cache.t3.micro";
|
|
13341
|
+
const numNodes = config.num_cache_nodes || 1;
|
|
13342
|
+
hourlyCost = (AWS_PRICING.elasticache[nodeType] || 0.017) * numNodes;
|
|
13343
|
+
monthlyCost = hourlyCost * 730;
|
|
13344
|
+
details = `${numNodes}x ${nodeType}`;
|
|
13345
|
+
} else if (type === "aws_s3_bucket") {
|
|
13346
|
+
monthlyCost = 5;
|
|
13347
|
+
hourlyCost = monthlyCost / 730;
|
|
13348
|
+
details = "estimated storage cost";
|
|
13349
|
+
} else if (type === "aws_lambda_function") {
|
|
13350
|
+
monthlyCost = 1;
|
|
13351
|
+
hourlyCost = monthlyCost / 730;
|
|
13352
|
+
details = "estimated execution cost";
|
|
13353
|
+
} else {
|
|
13354
|
+
return null;
|
|
13355
|
+
}
|
|
13356
|
+
if (mappedAction === "destroy") {
|
|
13357
|
+
monthlyCost = -monthlyCost;
|
|
13358
|
+
hourlyCost = -hourlyCost;
|
|
13359
|
+
}
|
|
13360
|
+
return {
|
|
13361
|
+
resource: address,
|
|
13362
|
+
resourceType: type,
|
|
13363
|
+
action: mappedAction,
|
|
13364
|
+
monthlyCost,
|
|
13365
|
+
hourlyCost,
|
|
13366
|
+
details
|
|
13367
|
+
};
|
|
13368
|
+
}
|
|
13369
|
+
__name(estimateResourceCost2, "estimateResourceCost");
|
|
13370
|
+
function analyzePlan(plan) {
|
|
13371
|
+
const creates = [];
|
|
13372
|
+
const modifies = [];
|
|
13373
|
+
const destroys = [];
|
|
13374
|
+
const changes = plan.resource_changes || [];
|
|
13375
|
+
changes.forEach((change) => {
|
|
13376
|
+
const estimate = estimateResourceCost2(change);
|
|
13377
|
+
if (!estimate)
|
|
13378
|
+
return;
|
|
13379
|
+
if (estimate.action === "create") {
|
|
13380
|
+
creates.push(estimate);
|
|
13381
|
+
} else if (estimate.action === "modify") {
|
|
13382
|
+
modifies.push(estimate);
|
|
13383
|
+
} else if (estimate.action === "destroy") {
|
|
13384
|
+
destroys.push(estimate);
|
|
13385
|
+
}
|
|
13386
|
+
});
|
|
13387
|
+
const createCost = creates.reduce((sum, e) => sum + e.monthlyCost, 0);
|
|
13388
|
+
const destroyCost = Math.abs(
|
|
13389
|
+
destroys.reduce((sum, e) => sum + e.monthlyCost, 0)
|
|
13390
|
+
);
|
|
13391
|
+
const modifyCost = modifies.reduce((sum, e) => sum + e.monthlyCost, 0);
|
|
13392
|
+
const currentMonthlyCost = destroyCost;
|
|
13393
|
+
const newMonthlyCost = createCost + modifyCost;
|
|
13394
|
+
const difference = newMonthlyCost - currentMonthlyCost;
|
|
13395
|
+
const percentChange = currentMonthlyCost > 0 ? difference / currentMonthlyCost * 100 : difference > 0 ? 100 : 0;
|
|
13396
|
+
return {
|
|
13397
|
+
creates,
|
|
13398
|
+
modifies,
|
|
13399
|
+
destroys,
|
|
13400
|
+
currentMonthlyCost,
|
|
13401
|
+
newMonthlyCost,
|
|
13402
|
+
difference,
|
|
13403
|
+
percentChange
|
|
13404
|
+
};
|
|
13405
|
+
}
|
|
13406
|
+
__name(analyzePlan, "analyzePlan");
|
|
13407
|
+
function formatCostEstimate(summary, options) {
|
|
13408
|
+
const { output } = options;
|
|
13409
|
+
if (output === "json") {
|
|
13410
|
+
return JSON.stringify(summary, null, 2);
|
|
13411
|
+
}
|
|
13412
|
+
let result = "";
|
|
13413
|
+
result += import_chalk17.default.bold.blue(
|
|
13414
|
+
"\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E\n"
|
|
13415
|
+
);
|
|
13416
|
+
result += import_chalk17.default.bold.blue(
|
|
13417
|
+
"\u2502" + import_chalk17.default.white(" Terraform Cost Estimate ") + "\u2502\n"
|
|
13418
|
+
);
|
|
13419
|
+
result += import_chalk17.default.bold.blue(
|
|
13420
|
+
"\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n"
|
|
13421
|
+
);
|
|
13422
|
+
if (summary.creates.length > 0) {
|
|
13423
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.green("Resources to CREATE:") + " ".repeat(38) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13424
|
+
result += import_chalk17.default.bold.blue(
|
|
13425
|
+
"\u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2502\n"
|
|
13426
|
+
);
|
|
13427
|
+
summary.creates.forEach((est) => {
|
|
13428
|
+
const resourceLine = `+ ${est.resource} (${est.details})`;
|
|
13429
|
+
const costLine = ` \u2514\u2500\u2500 Monthly: $${est.monthlyCost.toFixed(2)} | Hourly: $${est.hourlyCost.toFixed(4)}`;
|
|
13430
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.green(resourceLine.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13431
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.gray(costLine.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13432
|
+
});
|
|
13433
|
+
result += import_chalk17.default.bold.blue(
|
|
13434
|
+
"\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n"
|
|
13435
|
+
);
|
|
13436
|
+
}
|
|
13437
|
+
if (summary.modifies.length > 0) {
|
|
13438
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.yellow("Resources to MODIFY:") + " ".repeat(38) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13439
|
+
result += import_chalk17.default.bold.blue(
|
|
13440
|
+
"\u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2502\n"
|
|
13441
|
+
);
|
|
13442
|
+
summary.modifies.forEach((est) => {
|
|
13443
|
+
const resourceLine = `~ ${est.resource}`;
|
|
13444
|
+
const costLine = ` \u2514\u2500\u2500 ${est.details}: ${est.monthlyCost >= 0 ? "+" : ""}$${est.monthlyCost.toFixed(2)}/month`;
|
|
13445
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.yellow(resourceLine.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13446
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.gray(costLine.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13447
|
+
});
|
|
13448
|
+
result += import_chalk17.default.bold.blue(
|
|
13449
|
+
"\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n"
|
|
13450
|
+
);
|
|
13451
|
+
}
|
|
13452
|
+
if (summary.destroys.length > 0) {
|
|
13453
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.red("Resources to DESTROY:") + " ".repeat(37) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13454
|
+
result += import_chalk17.default.bold.blue(
|
|
13455
|
+
"\u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2502\n"
|
|
13456
|
+
);
|
|
13457
|
+
summary.destroys.forEach((est) => {
|
|
13458
|
+
const resourceLine = `- ${est.resource}`;
|
|
13459
|
+
const costLine = ` \u2514\u2500\u2500 Savings: $${Math.abs(est.monthlyCost).toFixed(2)}/month`;
|
|
13460
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.red(resourceLine.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13461
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.gray(costLine.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13462
|
+
});
|
|
13463
|
+
result += import_chalk17.default.bold.blue(
|
|
13464
|
+
"\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n"
|
|
13465
|
+
);
|
|
13466
|
+
}
|
|
13467
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.bold.white("SUMMARY") + " ".repeat(52) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13468
|
+
result += import_chalk17.default.bold.blue(
|
|
13469
|
+
"\u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2502\n"
|
|
13470
|
+
);
|
|
13471
|
+
const currentLine = `Current Monthly Cost: $${summary.currentMonthlyCost.toFixed(2)}`;
|
|
13472
|
+
const newLine = `Estimated New Cost: $${summary.newMonthlyCost.toFixed(2)}`;
|
|
13473
|
+
const diffSymbol = summary.difference >= 0 ? "+" : "";
|
|
13474
|
+
const diffLine = `Difference: ${diffSymbol}$${summary.difference.toFixed(2)}/month (${diffSymbol}${summary.percentChange.toFixed(1)}%)`;
|
|
13475
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + currentLine.padEnd(59) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13476
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + newLine.padEnd(59) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13477
|
+
const diffColor = summary.difference > 0 ? import_chalk17.default.red : import_chalk17.default.green;
|
|
13478
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + diffColor(diffLine).padEnd(69) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13479
|
+
const threshold = parseFloat(options.threshold || "20");
|
|
13480
|
+
if (Math.abs(summary.percentChange) > threshold) {
|
|
13481
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + " ".repeat(59) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13482
|
+
const warning = `\u26A0\uFE0F Cost ${summary.difference > 0 ? "increase" : "decrease"} exceeds ${threshold}% threshold!`;
|
|
13483
|
+
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.yellow.bold(warning.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13484
|
+
}
|
|
13485
|
+
result += import_chalk17.default.bold.blue(
|
|
13486
|
+
"\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F\n"
|
|
13487
|
+
);
|
|
13488
|
+
return result;
|
|
13489
|
+
}
|
|
13490
|
+
__name(formatCostEstimate, "formatCostEstimate");
|
|
13491
|
+
async function handleTerraform(options) {
|
|
13492
|
+
const { plan, threshold, output } = options;
|
|
13493
|
+
if (!plan) {
|
|
13494
|
+
console.error(import_chalk17.default.red("Error: --plan argument is required"));
|
|
13495
|
+
console.log("Usage: infra-cost terraform --plan <path-to-terraform-plan>");
|
|
13496
|
+
process.exit(1);
|
|
13497
|
+
}
|
|
13498
|
+
try {
|
|
13499
|
+
console.log(import_chalk17.default.blue("\u{1F4CA} Analyzing Terraform plan...\n"));
|
|
13500
|
+
const terraformPlan = parseTerraformPlan(plan);
|
|
13501
|
+
const summary = analyzePlan(terraformPlan);
|
|
13502
|
+
const formatted = formatCostEstimate(summary, options);
|
|
13503
|
+
console.log(formatted);
|
|
13504
|
+
const thresholdValue = parseFloat(threshold || "0");
|
|
13505
|
+
if (thresholdValue > 0 && Math.abs(summary.percentChange) > thresholdValue) {
|
|
13506
|
+
process.exit(1);
|
|
13507
|
+
}
|
|
13508
|
+
} catch (error) {
|
|
13509
|
+
console.error(import_chalk17.default.red(`Error: ${error.message}`));
|
|
13510
|
+
process.exit(1);
|
|
13511
|
+
}
|
|
13512
|
+
}
|
|
13513
|
+
__name(handleTerraform, "handleTerraform");
|
|
13514
|
+
function registerTerraformCommand(program) {
|
|
13515
|
+
program.command("terraform").description("Estimate costs from Terraform plan (shift-left cost management)").option(
|
|
13516
|
+
"--plan <path>",
|
|
13517
|
+
"Path to terraform plan file (binary or JSON)",
|
|
13518
|
+
""
|
|
13519
|
+
).option(
|
|
13520
|
+
"--threshold <percent>",
|
|
13521
|
+
"Fail if cost change exceeds threshold percentage",
|
|
13522
|
+
"0"
|
|
13523
|
+
).action(handleTerraform);
|
|
13524
|
+
}
|
|
13525
|
+
__name(registerTerraformCommand, "registerTerraformCommand");
|
|
13526
|
+
|
|
13527
|
+
// src/cli/commands/scheduler/index.ts
|
|
13528
|
+
var import_fs5 = require("fs");
|
|
13529
|
+
var import_path3 = require("path");
|
|
13530
|
+
var import_os3 = require("os");
|
|
13531
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
13532
|
+
var CONFIG_DIR = (0, import_path3.join)((0, import_os3.homedir)(), ".infra-cost");
|
|
13533
|
+
var CONFIG_FILE = (0, import_path3.join)(CONFIG_DIR, "scheduler.json");
|
|
13534
|
+
var PID_FILE = (0, import_path3.join)(CONFIG_DIR, "scheduler.pid");
|
|
13535
|
+
var LOG_FILE = (0, import_path3.join)(CONFIG_DIR, "scheduler.log");
|
|
13536
|
+
function getConfig() {
|
|
13537
|
+
if (!(0, import_fs5.existsSync)(CONFIG_FILE)) {
|
|
13538
|
+
return {
|
|
13539
|
+
schedules: [],
|
|
13540
|
+
daemon: {
|
|
13541
|
+
pidFile: PID_FILE,
|
|
13542
|
+
logFile: LOG_FILE
|
|
13543
|
+
}
|
|
13544
|
+
};
|
|
13545
|
+
}
|
|
13546
|
+
try {
|
|
13547
|
+
return JSON.parse((0, import_fs5.readFileSync)(CONFIG_FILE, "utf-8"));
|
|
13548
|
+
} catch (error) {
|
|
13549
|
+
console.error(import_chalk18.default.red("Error reading scheduler config:", error));
|
|
13550
|
+
return {
|
|
13551
|
+
schedules: [],
|
|
13552
|
+
daemon: {
|
|
13553
|
+
pidFile: PID_FILE,
|
|
13554
|
+
logFile: LOG_FILE
|
|
13555
|
+
}
|
|
13556
|
+
};
|
|
13557
|
+
}
|
|
13558
|
+
}
|
|
13559
|
+
__name(getConfig, "getConfig");
|
|
13560
|
+
function saveConfig(config) {
|
|
13561
|
+
try {
|
|
13562
|
+
(0, import_fs5.writeFileSync)(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
13563
|
+
} catch (error) {
|
|
13564
|
+
console.error(import_chalk18.default.red("Error saving scheduler config:", error));
|
|
13565
|
+
process.exit(1);
|
|
13566
|
+
}
|
|
13567
|
+
}
|
|
13568
|
+
__name(saveConfig, "saveConfig");
|
|
13569
|
+
function isDaemonRunning() {
|
|
13570
|
+
if (!(0, import_fs5.existsSync)(PID_FILE)) {
|
|
13571
|
+
return false;
|
|
13572
|
+
}
|
|
13573
|
+
try {
|
|
13574
|
+
const pid = parseInt((0, import_fs5.readFileSync)(PID_FILE, "utf-8").trim(), 10);
|
|
13575
|
+
process.kill(pid, 0);
|
|
13576
|
+
return true;
|
|
13577
|
+
} catch (error) {
|
|
13578
|
+
try {
|
|
13579
|
+
if ((0, import_fs5.existsSync)(PID_FILE)) {
|
|
13580
|
+
require("fs").unlinkSync(PID_FILE);
|
|
13581
|
+
}
|
|
13582
|
+
} catch (e) {
|
|
13583
|
+
}
|
|
13584
|
+
return false;
|
|
13585
|
+
}
|
|
13586
|
+
}
|
|
13587
|
+
__name(isDaemonRunning, "isDaemonRunning");
|
|
13588
|
+
function getNextRun(cron) {
|
|
13589
|
+
const now = /* @__PURE__ */ new Date();
|
|
13590
|
+
const next = new Date(now.getTime() + 24 * 60 * 60 * 1e3);
|
|
13591
|
+
return next.toISOString();
|
|
13592
|
+
}
|
|
13593
|
+
__name(getNextRun, "getNextRun");
|
|
13594
|
+
async function handleStart(options) {
|
|
13595
|
+
if (isDaemonRunning()) {
|
|
13596
|
+
console.log(import_chalk18.default.yellow("\u26A0\uFE0F Scheduler daemon is already running"));
|
|
13597
|
+
console.log(import_chalk18.default.gray("Run `infra-cost scheduler status` to check status"));
|
|
13598
|
+
return;
|
|
13599
|
+
}
|
|
13600
|
+
const config = getConfig();
|
|
13601
|
+
if (config.schedules.length === 0) {
|
|
13602
|
+
console.log(import_chalk18.default.yellow("\u26A0\uFE0F No schedules configured"));
|
|
13603
|
+
console.log(import_chalk18.default.gray("Add schedules with: infra-cost scheduler add"));
|
|
13604
|
+
return;
|
|
13605
|
+
}
|
|
13606
|
+
console.log(import_chalk18.default.blue("\u{1F680} Starting scheduler daemon..."));
|
|
13607
|
+
console.log(import_chalk18.default.gray(` Log file: ${LOG_FILE}`));
|
|
13608
|
+
console.log(import_chalk18.default.gray(` PID file: ${PID_FILE}`));
|
|
13609
|
+
console.log(import_chalk18.default.gray(` Schedules: ${config.schedules.filter((s) => s.enabled).length} enabled`));
|
|
13610
|
+
console.log(import_chalk18.default.green("\u2705 Scheduler daemon started"));
|
|
13611
|
+
console.log(import_chalk18.default.gray("Run `infra-cost scheduler status` to check status"));
|
|
13612
|
+
console.log(import_chalk18.default.gray("Run `infra-cost scheduler logs` to view execution logs"));
|
|
13613
|
+
(0, import_fs5.writeFileSync)(PID_FILE, process.pid.toString());
|
|
13614
|
+
}
|
|
13615
|
+
__name(handleStart, "handleStart");
|
|
13616
|
+
async function handleStop(options) {
|
|
13617
|
+
if (!isDaemonRunning()) {
|
|
13618
|
+
console.log(import_chalk18.default.yellow("\u26A0\uFE0F Scheduler daemon is not running"));
|
|
13619
|
+
return;
|
|
13620
|
+
}
|
|
13621
|
+
try {
|
|
13622
|
+
const pid = parseInt((0, import_fs5.readFileSync)(PID_FILE, "utf-8").trim(), 10);
|
|
13623
|
+
console.log(import_chalk18.default.blue("\u{1F6D1} Stopping scheduler daemon..."));
|
|
13624
|
+
process.kill(pid, "SIGTERM");
|
|
13625
|
+
if ((0, import_fs5.existsSync)(PID_FILE)) {
|
|
13626
|
+
require("fs").unlinkSync(PID_FILE);
|
|
13627
|
+
}
|
|
13628
|
+
console.log(import_chalk18.default.green("\u2705 Scheduler daemon stopped"));
|
|
13629
|
+
} catch (error) {
|
|
13630
|
+
console.error(import_chalk18.default.red("Error stopping daemon:", error.message));
|
|
13631
|
+
process.exit(1);
|
|
13632
|
+
}
|
|
13633
|
+
}
|
|
13634
|
+
__name(handleStop, "handleStop");
|
|
13635
|
+
async function handleStatus(options) {
|
|
13636
|
+
const config = getConfig();
|
|
13637
|
+
const isRunning = isDaemonRunning();
|
|
13638
|
+
console.log(import_chalk18.default.bold("\n\u{1F4CA} Scheduler Status\n"));
|
|
13639
|
+
console.log(import_chalk18.default.bold("Daemon:"));
|
|
13640
|
+
if (isRunning) {
|
|
13641
|
+
const pid = (0, import_fs5.readFileSync)(PID_FILE, "utf-8").trim();
|
|
13642
|
+
console.log(import_chalk18.default.green(" Status: \u2705 Running"));
|
|
13643
|
+
console.log(import_chalk18.default.gray(` PID: ${pid}`));
|
|
13644
|
+
} else {
|
|
13645
|
+
console.log(import_chalk18.default.red(" Status: \u274C Stopped"));
|
|
13646
|
+
}
|
|
13647
|
+
console.log(import_chalk18.default.gray(` Log file: ${LOG_FILE}`));
|
|
13648
|
+
console.log(import_chalk18.default.gray(` Config: ${CONFIG_FILE}`));
|
|
13649
|
+
console.log(import_chalk18.default.bold("\nSchedules:"));
|
|
13650
|
+
if (config.schedules.length === 0) {
|
|
13651
|
+
console.log(import_chalk18.default.gray(" No schedules configured"));
|
|
13652
|
+
} else {
|
|
13653
|
+
const enabled = config.schedules.filter((s) => s.enabled);
|
|
13654
|
+
const disabled = config.schedules.filter((s) => !s.enabled);
|
|
13655
|
+
console.log(import_chalk18.default.gray(` Total: ${config.schedules.length} (${enabled.length} enabled, ${disabled.length} disabled)`));
|
|
13656
|
+
config.schedules.forEach((schedule) => {
|
|
13657
|
+
const status = schedule.enabled ? import_chalk18.default.green("\u2713") : import_chalk18.default.gray("\u2717");
|
|
13658
|
+
console.log(` ${status} ${import_chalk18.default.bold(schedule.name)}`);
|
|
13659
|
+
console.log(import_chalk18.default.gray(` Cron: ${schedule.cron}`));
|
|
13660
|
+
console.log(import_chalk18.default.gray(` Command: infra-cost ${schedule.command}`));
|
|
13661
|
+
if (schedule.lastRun) {
|
|
13662
|
+
console.log(import_chalk18.default.gray(` Last run: ${schedule.lastRun}`));
|
|
13663
|
+
}
|
|
13664
|
+
});
|
|
13665
|
+
}
|
|
13666
|
+
console.log("");
|
|
13667
|
+
}
|
|
13668
|
+
__name(handleStatus, "handleStatus");
|
|
13669
|
+
async function handleAdd(options) {
|
|
13670
|
+
const { name, cron, command, timezone } = options;
|
|
13671
|
+
if (!name || !cron || !command) {
|
|
13672
|
+
console.error(import_chalk18.default.red("Error: --name, --cron, and --command are required"));
|
|
13673
|
+
console.log("\nExample:");
|
|
13674
|
+
console.log(import_chalk18.default.gray(" infra-cost scheduler add \\"));
|
|
13675
|
+
console.log(import_chalk18.default.gray(' --name "daily-report" \\'));
|
|
13676
|
+
console.log(import_chalk18.default.gray(' --cron "0 9 * * *" \\'));
|
|
13677
|
+
console.log(import_chalk18.default.gray(' --command "now --output json"'));
|
|
13678
|
+
process.exit(1);
|
|
13679
|
+
}
|
|
13680
|
+
const config = getConfig();
|
|
13681
|
+
if (config.schedules.find((s) => s.name === name)) {
|
|
13682
|
+
console.error(import_chalk18.default.red(`Error: Schedule "${name}" already exists`));
|
|
13683
|
+
console.log(import_chalk18.default.gray("Use a different name or remove the existing schedule first"));
|
|
13684
|
+
process.exit(1);
|
|
13685
|
+
}
|
|
13686
|
+
const newSchedule = {
|
|
13687
|
+
name,
|
|
13688
|
+
cron,
|
|
13689
|
+
command,
|
|
13690
|
+
timezone: timezone || "UTC",
|
|
13691
|
+
enabled: true,
|
|
13692
|
+
nextRun: getNextRun(cron)
|
|
13693
|
+
};
|
|
13694
|
+
config.schedules.push(newSchedule);
|
|
13695
|
+
saveConfig(config);
|
|
13696
|
+
console.log(import_chalk18.default.green("\u2705 Schedule added successfully"));
|
|
13697
|
+
console.log(import_chalk18.default.gray(` Name: ${name}`));
|
|
13698
|
+
console.log(import_chalk18.default.gray(` Cron: ${cron}`));
|
|
13699
|
+
console.log(import_chalk18.default.gray(` Command: infra-cost ${command}`));
|
|
13700
|
+
console.log(import_chalk18.default.gray(` Timezone: ${timezone || "UTC"}`));
|
|
13701
|
+
console.log("");
|
|
13702
|
+
console.log(import_chalk18.default.gray("Start the scheduler daemon to activate: infra-cost scheduler start"));
|
|
13703
|
+
}
|
|
13704
|
+
__name(handleAdd, "handleAdd");
|
|
13705
|
+
async function handleRemove(options) {
|
|
13706
|
+
const { name } = options;
|
|
13707
|
+
if (!name) {
|
|
13708
|
+
console.error(import_chalk18.default.red("Error: --name is required"));
|
|
13709
|
+
process.exit(1);
|
|
13710
|
+
}
|
|
13711
|
+
const config = getConfig();
|
|
13712
|
+
const index = config.schedules.findIndex((s) => s.name === name);
|
|
13713
|
+
if (index === -1) {
|
|
13714
|
+
console.error(import_chalk18.default.red(`Error: Schedule "${name}" not found`));
|
|
13715
|
+
process.exit(1);
|
|
13716
|
+
}
|
|
13717
|
+
config.schedules.splice(index, 1);
|
|
13718
|
+
saveConfig(config);
|
|
13719
|
+
console.log(import_chalk18.default.green(`\u2705 Schedule "${name}" removed`));
|
|
13720
|
+
}
|
|
13721
|
+
__name(handleRemove, "handleRemove");
|
|
13722
|
+
async function handleList2(options) {
|
|
13723
|
+
const config = getConfig();
|
|
13724
|
+
if (config.schedules.length === 0) {
|
|
13725
|
+
console.log(import_chalk18.default.yellow("No schedules configured"));
|
|
13726
|
+
console.log(import_chalk18.default.gray("\nAdd a schedule with: infra-cost scheduler add"));
|
|
13727
|
+
return;
|
|
13728
|
+
}
|
|
13729
|
+
console.log(import_chalk18.default.bold("\n\u{1F4C5} Configured Schedules\n"));
|
|
13730
|
+
config.schedules.forEach((schedule, index) => {
|
|
13731
|
+
const status = schedule.enabled ? import_chalk18.default.green("\u2713 ENABLED") : import_chalk18.default.gray("\u2717 DISABLED");
|
|
13732
|
+
console.log(import_chalk18.default.bold(`${index + 1}. ${schedule.name}`) + ` ${status}`);
|
|
13733
|
+
console.log(import_chalk18.default.gray(` Cron: ${schedule.cron}`));
|
|
13734
|
+
console.log(import_chalk18.default.gray(` Command: infra-cost ${schedule.command}`));
|
|
13735
|
+
console.log(import_chalk18.default.gray(` Timezone: ${schedule.timezone || "UTC"}`));
|
|
13736
|
+
if (schedule.lastRun) {
|
|
13737
|
+
console.log(import_chalk18.default.gray(` Last run: ${schedule.lastRun}`));
|
|
13738
|
+
}
|
|
13739
|
+
if (schedule.nextRun) {
|
|
13740
|
+
console.log(import_chalk18.default.gray(` Next run: ${schedule.nextRun}`));
|
|
13741
|
+
}
|
|
13742
|
+
console.log("");
|
|
13743
|
+
});
|
|
13744
|
+
}
|
|
13745
|
+
__name(handleList2, "handleList");
|
|
13746
|
+
async function handleLogs(options) {
|
|
13747
|
+
if (!(0, import_fs5.existsSync)(LOG_FILE)) {
|
|
13748
|
+
console.log(import_chalk18.default.yellow("No logs found"));
|
|
13749
|
+
console.log(import_chalk18.default.gray("Logs will appear here once the scheduler runs"));
|
|
13750
|
+
return;
|
|
13751
|
+
}
|
|
13752
|
+
const logs = (0, import_fs5.readFileSync)(LOG_FILE, "utf-8");
|
|
13753
|
+
console.log(logs);
|
|
13754
|
+
}
|
|
13755
|
+
__name(handleLogs, "handleLogs");
|
|
13756
|
+
async function handleGenerateSystemd(options) {
|
|
13757
|
+
const user = process.env.USER || "ubuntu";
|
|
13758
|
+
const nodePath = process.execPath;
|
|
13759
|
+
const infraCostPath = require.main?.filename || "/usr/local/bin/infra-cost";
|
|
13760
|
+
const serviceFile = `[Unit]
|
|
13761
|
+
Description=Infra Cost Scheduler Daemon
|
|
13762
|
+
After=network.target
|
|
13763
|
+
|
|
13764
|
+
[Service]
|
|
13765
|
+
Type=simple
|
|
13766
|
+
User=${user}
|
|
13767
|
+
ExecStart=${nodePath} ${infraCostPath} scheduler start
|
|
13768
|
+
Restart=always
|
|
13769
|
+
RestartSec=10
|
|
13770
|
+
StandardOutput=append:${LOG_FILE}
|
|
13771
|
+
StandardError=append:${LOG_FILE}
|
|
13772
|
+
|
|
13773
|
+
[Install]
|
|
13774
|
+
WantedBy=multi-user.target
|
|
13775
|
+
`;
|
|
13776
|
+
console.log(import_chalk18.default.bold("\u{1F4DD} Systemd Service File\n"));
|
|
13777
|
+
console.log(serviceFile);
|
|
13778
|
+
console.log(import_chalk18.default.gray("\nSave this to: /etc/systemd/system/infra-cost-scheduler.service"));
|
|
13779
|
+
console.log(import_chalk18.default.gray("\nThen run:"));
|
|
13780
|
+
console.log(import_chalk18.default.gray(" sudo systemctl daemon-reload"));
|
|
13781
|
+
console.log(import_chalk18.default.gray(" sudo systemctl enable infra-cost-scheduler"));
|
|
13782
|
+
console.log(import_chalk18.default.gray(" sudo systemctl start infra-cost-scheduler"));
|
|
12528
13783
|
}
|
|
12529
|
-
__name(
|
|
12530
|
-
function
|
|
12531
|
-
const
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
13784
|
+
__name(handleGenerateSystemd, "handleGenerateSystemd");
|
|
13785
|
+
function registerSchedulerCommands(program) {
|
|
13786
|
+
const scheduler = program.command("scheduler").description("Manage scheduled cost reports (daemon mode)");
|
|
13787
|
+
scheduler.command("start").description("Start scheduler daemon").action(handleStart);
|
|
13788
|
+
scheduler.command("stop").description("Stop scheduler daemon").action(handleStop);
|
|
13789
|
+
scheduler.command("status").description("Show scheduler status").action(handleStatus);
|
|
13790
|
+
scheduler.command("add").description("Add new schedule").option("--name <name>", "Schedule name").option("--cron <expression>", 'Cron expression (e.g., "0 9 * * *")').option("--command <command>", "infra-cost command to run").option("--timezone <tz>", "Timezone (default: UTC)", "UTC").action(handleAdd);
|
|
13791
|
+
scheduler.command("remove").description("Remove schedule").option("--name <name>", "Schedule name").action(handleRemove);
|
|
13792
|
+
scheduler.command("list").description("List all schedules").action(handleList2);
|
|
13793
|
+
scheduler.command("logs").description("View scheduler execution logs").action(handleLogs);
|
|
13794
|
+
scheduler.command("generate-systemd").description("Generate systemd service file").action(handleGenerateSystemd);
|
|
12539
13795
|
}
|
|
12540
|
-
__name(
|
|
13796
|
+
__name(registerSchedulerCommands, "registerSchedulerCommands");
|
|
12541
13797
|
|
|
12542
|
-
// src/cli/commands/
|
|
12543
|
-
var
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
var
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
"
|
|
12554
|
-
"
|
|
12555
|
-
"
|
|
12556
|
-
"t3.medium": 0.0416,
|
|
12557
|
-
"t3.large": 0.0832,
|
|
12558
|
-
"t3.xlarge": 0.1664,
|
|
12559
|
-
"t3.2xlarge": 0.3328,
|
|
12560
|
-
"m5.large": 0.096,
|
|
12561
|
-
"m5.xlarge": 0.192,
|
|
12562
|
-
"m5.2xlarge": 0.384,
|
|
12563
|
-
"c5.large": 0.085,
|
|
12564
|
-
"c5.xlarge": 0.17,
|
|
12565
|
-
"r5.large": 0.126,
|
|
12566
|
-
"r5.xlarge": 0.252
|
|
13798
|
+
// src/cli/commands/rbac/index.ts
|
|
13799
|
+
var import_chalk19 = __toESM(require("chalk"));
|
|
13800
|
+
|
|
13801
|
+
// src/core/rbac.ts
|
|
13802
|
+
var import_fs6 = require("fs");
|
|
13803
|
+
var import_path4 = require("path");
|
|
13804
|
+
var import_os4 = require("os");
|
|
13805
|
+
var CONFIG_DIR2 = (0, import_path4.join)((0, import_os4.homedir)(), ".infra-cost");
|
|
13806
|
+
var RBAC_CONFIG_FILE = (0, import_path4.join)(CONFIG_DIR2, "rbac.json");
|
|
13807
|
+
var DEFAULT_ROLES = {
|
|
13808
|
+
admin: {
|
|
13809
|
+
name: "admin",
|
|
13810
|
+
description: "Full access to all features",
|
|
13811
|
+
permissions: ["*"]
|
|
12567
13812
|
},
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
"
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
13813
|
+
finance: {
|
|
13814
|
+
name: "finance",
|
|
13815
|
+
description: "Cost data and reports only",
|
|
13816
|
+
permissions: [
|
|
13817
|
+
"costs:read",
|
|
13818
|
+
"costs:read:*",
|
|
13819
|
+
"chargeback:read",
|
|
13820
|
+
"chargeback:read:*",
|
|
13821
|
+
"reports:generate",
|
|
13822
|
+
"budgets:read",
|
|
13823
|
+
"budgets:read:*",
|
|
13824
|
+
"export:read"
|
|
13825
|
+
]
|
|
12579
13826
|
},
|
|
12580
|
-
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
|
|
13827
|
+
developer: {
|
|
13828
|
+
name: "developer",
|
|
13829
|
+
description: "View costs for assigned resources",
|
|
13830
|
+
permissions: [
|
|
13831
|
+
"costs:read:own",
|
|
13832
|
+
"costs:read:team:*",
|
|
13833
|
+
"inventory:read:own",
|
|
13834
|
+
"inventory:read:team:*",
|
|
13835
|
+
"optimization:read:own",
|
|
13836
|
+
"optimization:read:team:*"
|
|
13837
|
+
]
|
|
12588
13838
|
},
|
|
12589
|
-
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
elasticache: {
|
|
12597
|
-
"cache.t3.micro": 0.017,
|
|
12598
|
-
"cache.t3.small": 0.034,
|
|
12599
|
-
"cache.m5.large": 0.161
|
|
13839
|
+
viewer: {
|
|
13840
|
+
name: "viewer",
|
|
13841
|
+
description: "Read-only access to summaries",
|
|
13842
|
+
permissions: [
|
|
13843
|
+
"costs:read:summary",
|
|
13844
|
+
"budgets:read"
|
|
13845
|
+
]
|
|
12600
13846
|
}
|
|
12601
13847
|
};
|
|
12602
|
-
function
|
|
13848
|
+
function loadRBACConfig() {
|
|
13849
|
+
if (!(0, import_fs6.existsSync)(RBAC_CONFIG_FILE)) {
|
|
13850
|
+
return {
|
|
13851
|
+
roles: DEFAULT_ROLES,
|
|
13852
|
+
userRoles: []
|
|
13853
|
+
};
|
|
13854
|
+
}
|
|
12603
13855
|
try {
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
});
|
|
12611
|
-
}
|
|
12612
|
-
return JSON.parse(planJson);
|
|
13856
|
+
const data = (0, import_fs6.readFileSync)(RBAC_CONFIG_FILE, "utf-8");
|
|
13857
|
+
const config = JSON.parse(data);
|
|
13858
|
+
return {
|
|
13859
|
+
...config,
|
|
13860
|
+
roles: { ...DEFAULT_ROLES, ...config.roles }
|
|
13861
|
+
};
|
|
12613
13862
|
} catch (error) {
|
|
12614
|
-
|
|
13863
|
+
console.error("Error loading RBAC config:", error);
|
|
13864
|
+
return {
|
|
13865
|
+
roles: DEFAULT_ROLES,
|
|
13866
|
+
userRoles: []
|
|
13867
|
+
};
|
|
12615
13868
|
}
|
|
12616
13869
|
}
|
|
12617
|
-
__name(
|
|
12618
|
-
function
|
|
12619
|
-
|
|
12620
|
-
|
|
12621
|
-
|
|
12622
|
-
|
|
12623
|
-
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
const config =
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
12635
|
-
|
|
12636
|
-
}
|
|
12637
|
-
|
|
12638
|
-
|
|
12639
|
-
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
12656
|
-
|
|
12657
|
-
} else if (type === "aws_elb") {
|
|
12658
|
-
hourlyCost = AWS_PRICING.clb;
|
|
12659
|
-
monthlyCost = hourlyCost * 730;
|
|
12660
|
-
details = "classic load balancer";
|
|
12661
|
-
} else if (type === "aws_nat_gateway") {
|
|
12662
|
-
hourlyCost = AWS_PRICING.natgateway;
|
|
12663
|
-
monthlyCost = hourlyCost * 730;
|
|
12664
|
-
details = "NAT gateway (base cost)";
|
|
12665
|
-
} else if (type === "aws_elasticache_cluster") {
|
|
12666
|
-
const nodeType = config.node_type || "cache.t3.micro";
|
|
12667
|
-
const numNodes = config.num_cache_nodes || 1;
|
|
12668
|
-
hourlyCost = (AWS_PRICING.elasticache[nodeType] || 0.017) * numNodes;
|
|
12669
|
-
monthlyCost = hourlyCost * 730;
|
|
12670
|
-
details = `${numNodes}x ${nodeType}`;
|
|
12671
|
-
} else if (type === "aws_s3_bucket") {
|
|
12672
|
-
monthlyCost = 5;
|
|
12673
|
-
hourlyCost = monthlyCost / 730;
|
|
12674
|
-
details = "estimated storage cost";
|
|
12675
|
-
} else if (type === "aws_lambda_function") {
|
|
12676
|
-
monthlyCost = 1;
|
|
12677
|
-
hourlyCost = monthlyCost / 730;
|
|
12678
|
-
details = "estimated execution cost";
|
|
12679
|
-
} else {
|
|
12680
|
-
return null;
|
|
12681
|
-
}
|
|
12682
|
-
if (mappedAction === "destroy") {
|
|
12683
|
-
monthlyCost = -monthlyCost;
|
|
12684
|
-
hourlyCost = -hourlyCost;
|
|
13870
|
+
__name(loadRBACConfig, "loadRBACConfig");
|
|
13871
|
+
function saveRBACConfig(config) {
|
|
13872
|
+
try {
|
|
13873
|
+
(0, import_fs6.writeFileSync)(RBAC_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
13874
|
+
} catch (error) {
|
|
13875
|
+
throw new Error(`Failed to save RBAC config: ${error}`);
|
|
13876
|
+
}
|
|
13877
|
+
}
|
|
13878
|
+
__name(saveRBACConfig, "saveRBACConfig");
|
|
13879
|
+
function getUserRole(user) {
|
|
13880
|
+
const config = loadRBACConfig();
|
|
13881
|
+
const userRole = config.userRoles.find((ur) => ur.user === user);
|
|
13882
|
+
return userRole?.role || null;
|
|
13883
|
+
}
|
|
13884
|
+
__name(getUserRole, "getUserRole");
|
|
13885
|
+
function assignRole(user, role, assignedBy) {
|
|
13886
|
+
const config = loadRBACConfig();
|
|
13887
|
+
if (!config.roles[role]) {
|
|
13888
|
+
throw new Error(`Role "${role}" does not exist`);
|
|
13889
|
+
}
|
|
13890
|
+
config.userRoles = config.userRoles.filter((ur) => ur.user !== user);
|
|
13891
|
+
config.userRoles.push({
|
|
13892
|
+
user,
|
|
13893
|
+
role,
|
|
13894
|
+
assignedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13895
|
+
assignedBy
|
|
13896
|
+
});
|
|
13897
|
+
saveRBACConfig(config);
|
|
13898
|
+
}
|
|
13899
|
+
__name(assignRole, "assignRole");
|
|
13900
|
+
function removeUserRole(user) {
|
|
13901
|
+
const config = loadRBACConfig();
|
|
13902
|
+
config.userRoles = config.userRoles.filter((ur) => ur.user !== user);
|
|
13903
|
+
saveRBACConfig(config);
|
|
13904
|
+
}
|
|
13905
|
+
__name(removeUserRole, "removeUserRole");
|
|
13906
|
+
function createRole(name, description, permissions) {
|
|
13907
|
+
const config = loadRBACConfig();
|
|
13908
|
+
if (config.roles[name]) {
|
|
13909
|
+
throw new Error(`Role "${name}" already exists`);
|
|
12685
13910
|
}
|
|
12686
|
-
|
|
12687
|
-
|
|
12688
|
-
|
|
12689
|
-
|
|
12690
|
-
monthlyCost,
|
|
12691
|
-
hourlyCost,
|
|
12692
|
-
details
|
|
13911
|
+
config.roles[name] = {
|
|
13912
|
+
name,
|
|
13913
|
+
description,
|
|
13914
|
+
permissions
|
|
12693
13915
|
};
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
const
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
});
|
|
12713
|
-
const createCost = creates.reduce((sum, e) => sum + e.monthlyCost, 0);
|
|
12714
|
-
const destroyCost = Math.abs(
|
|
12715
|
-
destroys.reduce((sum, e) => sum + e.monthlyCost, 0)
|
|
12716
|
-
);
|
|
12717
|
-
const modifyCost = modifies.reduce((sum, e) => sum + e.monthlyCost, 0);
|
|
12718
|
-
const currentMonthlyCost = destroyCost;
|
|
12719
|
-
const newMonthlyCost = createCost + modifyCost;
|
|
12720
|
-
const difference = newMonthlyCost - currentMonthlyCost;
|
|
12721
|
-
const percentChange = currentMonthlyCost > 0 ? difference / currentMonthlyCost * 100 : difference > 0 ? 100 : 0;
|
|
13916
|
+
saveRBACConfig(config);
|
|
13917
|
+
}
|
|
13918
|
+
__name(createRole, "createRole");
|
|
13919
|
+
function deleteRole(name) {
|
|
13920
|
+
const config = loadRBACConfig();
|
|
13921
|
+
if (DEFAULT_ROLES[name]) {
|
|
13922
|
+
throw new Error(`Cannot delete default role "${name}"`);
|
|
13923
|
+
}
|
|
13924
|
+
if (!config.roles[name]) {
|
|
13925
|
+
throw new Error(`Role "${name}" does not exist`);
|
|
13926
|
+
}
|
|
13927
|
+
delete config.roles[name];
|
|
13928
|
+
config.userRoles = config.userRoles.filter((ur) => ur.role !== name);
|
|
13929
|
+
saveRBACConfig(config);
|
|
13930
|
+
}
|
|
13931
|
+
__name(deleteRole, "deleteRole");
|
|
13932
|
+
function parsePermission(permissionStr) {
|
|
13933
|
+
const parts = permissionStr.split(":");
|
|
12722
13934
|
return {
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
currentMonthlyCost,
|
|
12727
|
-
newMonthlyCost,
|
|
12728
|
-
difference,
|
|
12729
|
-
percentChange
|
|
13935
|
+
resource: parts[0] || "*",
|
|
13936
|
+
action: parts[1] || "*",
|
|
13937
|
+
scope: parts[2]
|
|
12730
13938
|
};
|
|
12731
13939
|
}
|
|
12732
|
-
__name(
|
|
12733
|
-
function
|
|
12734
|
-
|
|
12735
|
-
|
|
12736
|
-
return JSON.stringify(summary, null, 2);
|
|
13940
|
+
__name(parsePermission, "parsePermission");
|
|
13941
|
+
function permissionMatches(required, granted) {
|
|
13942
|
+
if (granted.resource === "*") {
|
|
13943
|
+
return true;
|
|
12737
13944
|
}
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
"\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E\n"
|
|
12741
|
-
);
|
|
12742
|
-
result += import_chalk17.default.bold.blue(
|
|
12743
|
-
"\u2502" + import_chalk17.default.white(" Terraform Cost Estimate ") + "\u2502\n"
|
|
12744
|
-
);
|
|
12745
|
-
result += import_chalk17.default.bold.blue(
|
|
12746
|
-
"\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n"
|
|
12747
|
-
);
|
|
12748
|
-
if (summary.creates.length > 0) {
|
|
12749
|
-
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.green("Resources to CREATE:") + " ".repeat(38) + import_chalk17.default.bold.blue("\u2502\n");
|
|
12750
|
-
result += import_chalk17.default.bold.blue(
|
|
12751
|
-
"\u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2502\n"
|
|
12752
|
-
);
|
|
12753
|
-
summary.creates.forEach((est) => {
|
|
12754
|
-
const resourceLine = `+ ${est.resource} (${est.details})`;
|
|
12755
|
-
const costLine = ` \u2514\u2500\u2500 Monthly: $${est.monthlyCost.toFixed(2)} | Hourly: $${est.hourlyCost.toFixed(4)}`;
|
|
12756
|
-
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.green(resourceLine.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
12757
|
-
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.gray(costLine.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
12758
|
-
});
|
|
12759
|
-
result += import_chalk17.default.bold.blue(
|
|
12760
|
-
"\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n"
|
|
12761
|
-
);
|
|
13945
|
+
if (granted.resource !== required.resource) {
|
|
13946
|
+
return false;
|
|
12762
13947
|
}
|
|
12763
|
-
if (
|
|
12764
|
-
|
|
12765
|
-
result += import_chalk17.default.bold.blue(
|
|
12766
|
-
"\u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2502\n"
|
|
12767
|
-
);
|
|
12768
|
-
summary.modifies.forEach((est) => {
|
|
12769
|
-
const resourceLine = `~ ${est.resource}`;
|
|
12770
|
-
const costLine = ` \u2514\u2500\u2500 ${est.details}: ${est.monthlyCost >= 0 ? "+" : ""}$${est.monthlyCost.toFixed(2)}/month`;
|
|
12771
|
-
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.yellow(resourceLine.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
12772
|
-
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.gray(costLine.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
12773
|
-
});
|
|
12774
|
-
result += import_chalk17.default.bold.blue(
|
|
12775
|
-
"\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n"
|
|
12776
|
-
);
|
|
13948
|
+
if (granted.action !== "*" && granted.action !== required.action) {
|
|
13949
|
+
return false;
|
|
12777
13950
|
}
|
|
12778
|
-
if (
|
|
12779
|
-
|
|
12780
|
-
|
|
12781
|
-
|
|
12782
|
-
)
|
|
12783
|
-
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
}
|
|
12789
|
-
result += import_chalk17.default.bold.blue(
|
|
12790
|
-
"\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n"
|
|
12791
|
-
);
|
|
13951
|
+
if (required.scope) {
|
|
13952
|
+
if (!granted.scope || granted.scope === "*") {
|
|
13953
|
+
return true;
|
|
13954
|
+
}
|
|
13955
|
+
if (granted.scope !== required.scope && !granted.scope.endsWith(":*")) {
|
|
13956
|
+
return false;
|
|
13957
|
+
}
|
|
13958
|
+
if (granted.scope.endsWith(":*")) {
|
|
13959
|
+
const prefix = granted.scope.slice(0, -2);
|
|
13960
|
+
return required.scope.startsWith(prefix);
|
|
13961
|
+
}
|
|
12792
13962
|
}
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
const
|
|
12798
|
-
const
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
result += import_chalk17.default.bold.blue("\u2502 ") + currentLine.padEnd(59) + import_chalk17.default.bold.blue("\u2502\n");
|
|
12802
|
-
result += import_chalk17.default.bold.blue("\u2502 ") + newLine.padEnd(59) + import_chalk17.default.bold.blue("\u2502\n");
|
|
12803
|
-
const diffColor = summary.difference > 0 ? import_chalk17.default.red : import_chalk17.default.green;
|
|
12804
|
-
result += import_chalk17.default.bold.blue("\u2502 ") + diffColor(diffLine).padEnd(69) + import_chalk17.default.bold.blue("\u2502\n");
|
|
12805
|
-
const threshold = parseFloat(options.threshold || "20");
|
|
12806
|
-
if (Math.abs(summary.percentChange) > threshold) {
|
|
12807
|
-
result += import_chalk17.default.bold.blue("\u2502 ") + " ".repeat(59) + import_chalk17.default.bold.blue("\u2502\n");
|
|
12808
|
-
const warning = `\u26A0\uFE0F Cost ${summary.difference > 0 ? "increase" : "decrease"} exceeds ${threshold}% threshold!`;
|
|
12809
|
-
result += import_chalk17.default.bold.blue("\u2502 ") + import_chalk17.default.yellow.bold(warning.padEnd(59)) + import_chalk17.default.bold.blue("\u2502\n");
|
|
13963
|
+
return true;
|
|
13964
|
+
}
|
|
13965
|
+
__name(permissionMatches, "permissionMatches");
|
|
13966
|
+
function hasPermission(user, permissionStr, context) {
|
|
13967
|
+
const config = loadRBACConfig();
|
|
13968
|
+
const userRole = config.userRoles.find((ur) => ur.user === user);
|
|
13969
|
+
if (!userRole) {
|
|
13970
|
+
return false;
|
|
12810
13971
|
}
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
13972
|
+
const role = config.roles[userRole.role];
|
|
13973
|
+
if (!role) {
|
|
13974
|
+
return false;
|
|
13975
|
+
}
|
|
13976
|
+
const required = parsePermission(permissionStr);
|
|
13977
|
+
for (const grantedStr of role.permissions) {
|
|
13978
|
+
const granted = parsePermission(grantedStr);
|
|
13979
|
+
if (permissionMatches(required, granted)) {
|
|
13980
|
+
return true;
|
|
13981
|
+
}
|
|
13982
|
+
}
|
|
13983
|
+
return false;
|
|
12815
13984
|
}
|
|
12816
|
-
__name(
|
|
12817
|
-
|
|
12818
|
-
const
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
|
|
13985
|
+
__name(hasPermission, "hasPermission");
|
|
13986
|
+
function getUserPermissions(user) {
|
|
13987
|
+
const config = loadRBACConfig();
|
|
13988
|
+
const userRole = config.userRoles.find((ur) => ur.user === user);
|
|
13989
|
+
if (!userRole) {
|
|
13990
|
+
return [];
|
|
13991
|
+
}
|
|
13992
|
+
const role = config.roles[userRole.role];
|
|
13993
|
+
if (!role) {
|
|
13994
|
+
return [];
|
|
13995
|
+
}
|
|
13996
|
+
return role.permissions;
|
|
13997
|
+
}
|
|
13998
|
+
__name(getUserPermissions, "getUserPermissions");
|
|
13999
|
+
function listRoles() {
|
|
14000
|
+
const config = loadRBACConfig();
|
|
14001
|
+
return Object.values(config.roles);
|
|
14002
|
+
}
|
|
14003
|
+
__name(listRoles, "listRoles");
|
|
14004
|
+
function listUserRoles() {
|
|
14005
|
+
const config = loadRBACConfig();
|
|
14006
|
+
return config.userRoles;
|
|
14007
|
+
}
|
|
14008
|
+
__name(listUserRoles, "listUserRoles");
|
|
14009
|
+
|
|
14010
|
+
// src/cli/commands/rbac/index.ts
|
|
14011
|
+
async function handleAssignRole(options) {
|
|
14012
|
+
const { user, role, assignedBy } = options;
|
|
14013
|
+
if (!user || !role) {
|
|
14014
|
+
console.error(import_chalk19.default.red("Error: --user and --role are required"));
|
|
14015
|
+
console.log("\nExample:");
|
|
14016
|
+
console.log(import_chalk19.default.gray(" infra-cost rbac assign-role --user john@company.com --role finance"));
|
|
12822
14017
|
process.exit(1);
|
|
12823
14018
|
}
|
|
12824
14019
|
try {
|
|
12825
|
-
|
|
12826
|
-
|
|
12827
|
-
const summary = analyzePlan(terraformPlan);
|
|
12828
|
-
const formatted = formatCostEstimate(summary, options);
|
|
12829
|
-
console.log(formatted);
|
|
12830
|
-
const thresholdValue = parseFloat(threshold || "0");
|
|
12831
|
-
if (thresholdValue > 0 && Math.abs(summary.percentChange) > thresholdValue) {
|
|
12832
|
-
process.exit(1);
|
|
12833
|
-
}
|
|
14020
|
+
assignRole(user, role, assignedBy);
|
|
14021
|
+
console.log(import_chalk19.default.green(`\u2705 Assigned role "${role}" to user "${user}"`));
|
|
12834
14022
|
} catch (error) {
|
|
12835
|
-
console.error(
|
|
14023
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
12836
14024
|
process.exit(1);
|
|
12837
14025
|
}
|
|
12838
14026
|
}
|
|
12839
|
-
__name(
|
|
12840
|
-
function
|
|
12841
|
-
|
|
12842
|
-
|
|
12843
|
-
"
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
"
|
|
12849
|
-
|
|
14027
|
+
__name(handleAssignRole, "handleAssignRole");
|
|
14028
|
+
async function handleRemoveRole(options) {
|
|
14029
|
+
const { user } = options;
|
|
14030
|
+
if (!user) {
|
|
14031
|
+
console.error(import_chalk19.default.red("Error: --user is required"));
|
|
14032
|
+
process.exit(1);
|
|
14033
|
+
}
|
|
14034
|
+
try {
|
|
14035
|
+
removeUserRole(user);
|
|
14036
|
+
console.log(import_chalk19.default.green(`\u2705 Removed role from user "${user}"`));
|
|
14037
|
+
} catch (error) {
|
|
14038
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14039
|
+
process.exit(1);
|
|
14040
|
+
}
|
|
12850
14041
|
}
|
|
12851
|
-
__name(
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
function getConfig() {
|
|
12863
|
-
if (!(0, import_fs5.existsSync)(CONFIG_FILE)) {
|
|
12864
|
-
return {
|
|
12865
|
-
schedules: [],
|
|
12866
|
-
daemon: {
|
|
12867
|
-
pidFile: PID_FILE,
|
|
12868
|
-
logFile: LOG_FILE
|
|
12869
|
-
}
|
|
12870
|
-
};
|
|
14042
|
+
__name(handleRemoveRole, "handleRemoveRole");
|
|
14043
|
+
async function handleCreateRole(options) {
|
|
14044
|
+
const { name, description, permissions } = options;
|
|
14045
|
+
if (!name || !description || !permissions) {
|
|
14046
|
+
console.error(import_chalk19.default.red("Error: --name, --description, and --permissions are required"));
|
|
14047
|
+
console.log("\nExample:");
|
|
14048
|
+
console.log(import_chalk19.default.gray(" infra-cost rbac create-role \\"));
|
|
14049
|
+
console.log(import_chalk19.default.gray(" --name team-lead \\"));
|
|
14050
|
+
console.log(import_chalk19.default.gray(' --description "Team lead access" \\'));
|
|
14051
|
+
console.log(import_chalk19.default.gray(' --permissions "costs:read,optimization:read"'));
|
|
14052
|
+
process.exit(1);
|
|
12871
14053
|
}
|
|
12872
14054
|
try {
|
|
12873
|
-
|
|
14055
|
+
const permList = permissions.split(",").map((p) => p.trim());
|
|
14056
|
+
createRole(name, description, permList);
|
|
14057
|
+
console.log(import_chalk19.default.green(`\u2705 Created role "${name}"`));
|
|
14058
|
+
console.log(import_chalk19.default.gray(` Permissions: ${permList.join(", ")}`));
|
|
12874
14059
|
} catch (error) {
|
|
12875
|
-
console.error(
|
|
12876
|
-
|
|
12877
|
-
schedules: [],
|
|
12878
|
-
daemon: {
|
|
12879
|
-
pidFile: PID_FILE,
|
|
12880
|
-
logFile: LOG_FILE
|
|
12881
|
-
}
|
|
12882
|
-
};
|
|
14060
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14061
|
+
process.exit(1);
|
|
12883
14062
|
}
|
|
12884
14063
|
}
|
|
12885
|
-
__name(
|
|
12886
|
-
function
|
|
14064
|
+
__name(handleCreateRole, "handleCreateRole");
|
|
14065
|
+
async function handleDeleteRole(options) {
|
|
14066
|
+
const { name } = options;
|
|
14067
|
+
if (!name) {
|
|
14068
|
+
console.error(import_chalk19.default.red("Error: --name is required"));
|
|
14069
|
+
process.exit(1);
|
|
14070
|
+
}
|
|
12887
14071
|
try {
|
|
12888
|
-
(
|
|
14072
|
+
deleteRole(name);
|
|
14073
|
+
console.log(import_chalk19.default.green(`\u2705 Deleted role "${name}"`));
|
|
12889
14074
|
} catch (error) {
|
|
12890
|
-
console.error(
|
|
14075
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
12891
14076
|
process.exit(1);
|
|
12892
14077
|
}
|
|
12893
14078
|
}
|
|
12894
|
-
__name(
|
|
12895
|
-
function
|
|
12896
|
-
|
|
12897
|
-
|
|
14079
|
+
__name(handleDeleteRole, "handleDeleteRole");
|
|
14080
|
+
async function handleShowPermissions(options) {
|
|
14081
|
+
const { user } = options;
|
|
14082
|
+
if (!user) {
|
|
14083
|
+
console.error(import_chalk19.default.red("Error: --user is required"));
|
|
14084
|
+
process.exit(1);
|
|
12898
14085
|
}
|
|
12899
14086
|
try {
|
|
12900
|
-
const
|
|
12901
|
-
|
|
12902
|
-
|
|
14087
|
+
const role = getUserRole(user);
|
|
14088
|
+
const permissions = getUserPermissions(user);
|
|
14089
|
+
console.log(import_chalk19.default.bold(`
|
|
14090
|
+
\u{1F464} User: ${user}
|
|
14091
|
+
`));
|
|
14092
|
+
if (!role) {
|
|
14093
|
+
console.log(import_chalk19.default.yellow("No role assigned"));
|
|
14094
|
+
return;
|
|
14095
|
+
}
|
|
14096
|
+
console.log(import_chalk19.default.bold(`Role: ${role}
|
|
14097
|
+
`));
|
|
14098
|
+
console.log(import_chalk19.default.bold("Permissions:"));
|
|
14099
|
+
if (permissions.length === 0) {
|
|
14100
|
+
console.log(import_chalk19.default.gray(" No permissions"));
|
|
14101
|
+
} else {
|
|
14102
|
+
permissions.forEach((perm) => {
|
|
14103
|
+
console.log(import_chalk19.default.gray(` \u2022 ${perm}`));
|
|
14104
|
+
});
|
|
14105
|
+
}
|
|
14106
|
+
console.log("");
|
|
12903
14107
|
} catch (error) {
|
|
12904
|
-
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
|
|
14108
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14109
|
+
process.exit(1);
|
|
14110
|
+
}
|
|
14111
|
+
}
|
|
14112
|
+
__name(handleShowPermissions, "handleShowPermissions");
|
|
14113
|
+
async function handleListRoles(options) {
|
|
14114
|
+
try {
|
|
14115
|
+
const roles = listRoles();
|
|
14116
|
+
console.log(import_chalk19.default.bold("\n\u{1F4CB} Available Roles\n"));
|
|
14117
|
+
roles.forEach((role) => {
|
|
14118
|
+
console.log(import_chalk19.default.bold(`${role.name}`));
|
|
14119
|
+
console.log(import_chalk19.default.gray(` ${role.description}`));
|
|
14120
|
+
console.log(import_chalk19.default.gray(` Permissions: ${role.permissions.slice(0, 3).join(", ")}${role.permissions.length > 3 ? "..." : ""}`));
|
|
14121
|
+
console.log("");
|
|
14122
|
+
});
|
|
14123
|
+
} catch (error) {
|
|
14124
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14125
|
+
process.exit(1);
|
|
14126
|
+
}
|
|
14127
|
+
}
|
|
14128
|
+
__name(handleListRoles, "handleListRoles");
|
|
14129
|
+
async function handleListUsers(options) {
|
|
14130
|
+
try {
|
|
14131
|
+
const userRoles = listUserRoles();
|
|
14132
|
+
console.log(import_chalk19.default.bold("\n\u{1F465} User Role Assignments\n"));
|
|
14133
|
+
if (userRoles.length === 0) {
|
|
14134
|
+
console.log(import_chalk19.default.yellow("No user roles assigned"));
|
|
14135
|
+
return;
|
|
12909
14136
|
}
|
|
12910
|
-
|
|
14137
|
+
userRoles.forEach((ur) => {
|
|
14138
|
+
console.log(import_chalk19.default.bold(ur.user));
|
|
14139
|
+
console.log(import_chalk19.default.gray(` Role: ${ur.role}`));
|
|
14140
|
+
console.log(import_chalk19.default.gray(` Assigned: ${new Date(ur.assignedAt).toLocaleString()}`));
|
|
14141
|
+
if (ur.assignedBy) {
|
|
14142
|
+
console.log(import_chalk19.default.gray(` Assigned by: ${ur.assignedBy}`));
|
|
14143
|
+
}
|
|
14144
|
+
console.log("");
|
|
14145
|
+
});
|
|
14146
|
+
} catch (error) {
|
|
14147
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14148
|
+
process.exit(1);
|
|
12911
14149
|
}
|
|
12912
14150
|
}
|
|
12913
|
-
__name(
|
|
12914
|
-
function
|
|
12915
|
-
const
|
|
12916
|
-
|
|
12917
|
-
|
|
14151
|
+
__name(handleListUsers, "handleListUsers");
|
|
14152
|
+
async function handleCheckPermission(options) {
|
|
14153
|
+
const { user, permission } = options;
|
|
14154
|
+
if (!user || !permission) {
|
|
14155
|
+
console.error(import_chalk19.default.red("Error: --user and --permission are required"));
|
|
14156
|
+
console.log("\nExample:");
|
|
14157
|
+
console.log(import_chalk19.default.gray(' infra-cost rbac check-permission --user john@company.com --permission "costs:read"'));
|
|
14158
|
+
process.exit(1);
|
|
14159
|
+
}
|
|
14160
|
+
try {
|
|
14161
|
+
const has = hasPermission(user, permission);
|
|
14162
|
+
console.log(import_chalk19.default.bold(`
|
|
14163
|
+
\u{1F464} User: ${user}`));
|
|
14164
|
+
console.log(import_chalk19.default.bold(`\u{1F510} Permission: ${permission}
|
|
14165
|
+
`));
|
|
14166
|
+
if (has) {
|
|
14167
|
+
console.log(import_chalk19.default.green("\u2705 ALLOWED"));
|
|
14168
|
+
} else {
|
|
14169
|
+
console.log(import_chalk19.default.red("\u274C DENIED"));
|
|
14170
|
+
}
|
|
14171
|
+
console.log("");
|
|
14172
|
+
} catch (error) {
|
|
14173
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14174
|
+
process.exit(1);
|
|
14175
|
+
}
|
|
12918
14176
|
}
|
|
12919
|
-
__name(
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
14177
|
+
__name(handleCheckPermission, "handleCheckPermission");
|
|
14178
|
+
function registerRBACCommands(program) {
|
|
14179
|
+
const rbac = program.command("rbac").description("Role-Based Access Control management");
|
|
14180
|
+
rbac.command("assign-role").description("Assign role to user").option("--user <email>", "User email").option("--role <name>", "Role name").option("--assigned-by <email>", "Who assigned the role").action(handleAssignRole);
|
|
14181
|
+
rbac.command("remove-role").description("Remove role from user").option("--user <email>", "User email").action(handleRemoveRole);
|
|
14182
|
+
rbac.command("create-role").description("Create custom role").option("--name <name>", "Role name").option("--description <text>", "Role description").option("--permissions <list>", "Comma-separated permissions").action(handleCreateRole);
|
|
14183
|
+
rbac.command("delete-role").description("Delete custom role").option("--name <name>", "Role name").action(handleDeleteRole);
|
|
14184
|
+
rbac.command("show-permissions").description("Show user permissions").option("--user <email>", "User email").action(handleShowPermissions);
|
|
14185
|
+
rbac.command("list-roles").description("List all available roles").action(handleListRoles);
|
|
14186
|
+
rbac.command("list-users").description("List all user role assignments").action(handleListUsers);
|
|
14187
|
+
rbac.command("check-permission").description("Check if user has permission").option("--user <email>", "User email").option("--permission <perm>", 'Permission string (e.g., "costs:read")').action(handleCheckPermission);
|
|
14188
|
+
}
|
|
14189
|
+
__name(registerRBACCommands, "registerRBACCommands");
|
|
14190
|
+
|
|
14191
|
+
// src/cli/commands/sso/index.ts
|
|
14192
|
+
var import_chalk20 = __toESM(require("chalk"));
|
|
14193
|
+
|
|
14194
|
+
// src/core/sso.ts
|
|
14195
|
+
var import_fs7 = require("fs");
|
|
14196
|
+
var import_path5 = require("path");
|
|
14197
|
+
var import_os5 = require("os");
|
|
14198
|
+
var CONFIG_DIR3 = (0, import_path5.join)((0, import_os5.homedir)(), ".infra-cost");
|
|
14199
|
+
var SSO_DIR = (0, import_path5.join)(CONFIG_DIR3, "sso");
|
|
14200
|
+
var SSO_CONFIG_FILE = (0, import_path5.join)(CONFIG_DIR3, "sso-config.json");
|
|
14201
|
+
var SESSION_FILE = (0, import_path5.join)(SSO_DIR, "session.json");
|
|
14202
|
+
function ensureSSODir() {
|
|
14203
|
+
if (!(0, import_fs7.existsSync)(SSO_DIR)) {
|
|
14204
|
+
(0, import_fs7.mkdirSync)(SSO_DIR, { recursive: true });
|
|
14205
|
+
}
|
|
14206
|
+
}
|
|
14207
|
+
__name(ensureSSODir, "ensureSSODir");
|
|
14208
|
+
function loadSSOConfig() {
|
|
14209
|
+
if (!(0, import_fs7.existsSync)(SSO_CONFIG_FILE)) {
|
|
14210
|
+
return null;
|
|
12925
14211
|
}
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
12929
|
-
|
|
12930
|
-
|
|
14212
|
+
try {
|
|
14213
|
+
const data = (0, import_fs7.readFileSync)(SSO_CONFIG_FILE, "utf-8");
|
|
14214
|
+
return JSON.parse(data);
|
|
14215
|
+
} catch (error) {
|
|
14216
|
+
console.error("Error loading SSO config:", error);
|
|
14217
|
+
return null;
|
|
14218
|
+
}
|
|
14219
|
+
}
|
|
14220
|
+
__name(loadSSOConfig, "loadSSOConfig");
|
|
14221
|
+
function saveSSOConfig(config) {
|
|
14222
|
+
try {
|
|
14223
|
+
ensureSSODir();
|
|
14224
|
+
(0, import_fs7.writeFileSync)(SSO_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
14225
|
+
} catch (error) {
|
|
14226
|
+
throw new Error(`Failed to save SSO config: ${error}`);
|
|
12931
14227
|
}
|
|
12932
|
-
console.log(import_chalk18.default.blue("\u{1F680} Starting scheduler daemon..."));
|
|
12933
|
-
console.log(import_chalk18.default.gray(` Log file: ${LOG_FILE}`));
|
|
12934
|
-
console.log(import_chalk18.default.gray(` PID file: ${PID_FILE}`));
|
|
12935
|
-
console.log(import_chalk18.default.gray(` Schedules: ${config.schedules.filter((s) => s.enabled).length} enabled`));
|
|
12936
|
-
console.log(import_chalk18.default.green("\u2705 Scheduler daemon started"));
|
|
12937
|
-
console.log(import_chalk18.default.gray("Run `infra-cost scheduler status` to check status"));
|
|
12938
|
-
console.log(import_chalk18.default.gray("Run `infra-cost scheduler logs` to view execution logs"));
|
|
12939
|
-
(0, import_fs5.writeFileSync)(PID_FILE, process.pid.toString());
|
|
12940
14228
|
}
|
|
12941
|
-
__name(
|
|
12942
|
-
|
|
12943
|
-
if (!
|
|
12944
|
-
|
|
12945
|
-
return;
|
|
14229
|
+
__name(saveSSOConfig, "saveSSOConfig");
|
|
14230
|
+
function loadSSOSession() {
|
|
14231
|
+
if (!(0, import_fs7.existsSync)(SESSION_FILE)) {
|
|
14232
|
+
return null;
|
|
12946
14233
|
}
|
|
12947
14234
|
try {
|
|
12948
|
-
const
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
if (
|
|
12952
|
-
|
|
14235
|
+
const data = (0, import_fs7.readFileSync)(SESSION_FILE, "utf-8");
|
|
14236
|
+
const session = JSON.parse(data);
|
|
14237
|
+
const expiresAt = new Date(session.expiresAt);
|
|
14238
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
14239
|
+
return null;
|
|
12953
14240
|
}
|
|
12954
|
-
|
|
14241
|
+
return session;
|
|
12955
14242
|
} catch (error) {
|
|
12956
|
-
|
|
12957
|
-
process.exit(1);
|
|
14243
|
+
return null;
|
|
12958
14244
|
}
|
|
12959
14245
|
}
|
|
12960
|
-
__name(
|
|
12961
|
-
|
|
12962
|
-
|
|
12963
|
-
|
|
12964
|
-
|
|
12965
|
-
|
|
12966
|
-
|
|
12967
|
-
const pid = (0, import_fs5.readFileSync)(PID_FILE, "utf-8").trim();
|
|
12968
|
-
console.log(import_chalk18.default.green(" Status: \u2705 Running"));
|
|
12969
|
-
console.log(import_chalk18.default.gray(` PID: ${pid}`));
|
|
12970
|
-
} else {
|
|
12971
|
-
console.log(import_chalk18.default.red(" Status: \u274C Stopped"));
|
|
14246
|
+
__name(loadSSOSession, "loadSSOSession");
|
|
14247
|
+
function saveSSOSession(session) {
|
|
14248
|
+
try {
|
|
14249
|
+
ensureSSODir();
|
|
14250
|
+
(0, import_fs7.writeFileSync)(SESSION_FILE, JSON.stringify(session, null, 2));
|
|
14251
|
+
} catch (error) {
|
|
14252
|
+
throw new Error(`Failed to save SSO session: ${error}`);
|
|
12972
14253
|
}
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
|
|
12980
|
-
|
|
12981
|
-
console.log(import_chalk18.default.gray(` Total: ${config.schedules.length} (${enabled.length} enabled, ${disabled.length} disabled)`));
|
|
12982
|
-
config.schedules.forEach((schedule) => {
|
|
12983
|
-
const status = schedule.enabled ? import_chalk18.default.green("\u2713") : import_chalk18.default.gray("\u2717");
|
|
12984
|
-
console.log(` ${status} ${import_chalk18.default.bold(schedule.name)}`);
|
|
12985
|
-
console.log(import_chalk18.default.gray(` Cron: ${schedule.cron}`));
|
|
12986
|
-
console.log(import_chalk18.default.gray(` Command: infra-cost ${schedule.command}`));
|
|
12987
|
-
if (schedule.lastRun) {
|
|
12988
|
-
console.log(import_chalk18.default.gray(` Last run: ${schedule.lastRun}`));
|
|
12989
|
-
}
|
|
12990
|
-
});
|
|
14254
|
+
}
|
|
14255
|
+
__name(saveSSOSession, "saveSSOSession");
|
|
14256
|
+
function clearSSOSession() {
|
|
14257
|
+
try {
|
|
14258
|
+
if ((0, import_fs7.existsSync)(SESSION_FILE)) {
|
|
14259
|
+
require("fs").unlinkSync(SESSION_FILE);
|
|
14260
|
+
}
|
|
14261
|
+
} catch (error) {
|
|
12991
14262
|
}
|
|
12992
|
-
console.log("");
|
|
12993
14263
|
}
|
|
12994
|
-
__name(
|
|
12995
|
-
|
|
12996
|
-
const
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
|
|
13000
|
-
|
|
13001
|
-
|
|
13002
|
-
|
|
13003
|
-
|
|
14264
|
+
__name(clearSSOSession, "clearSSOSession");
|
|
14265
|
+
function isLoggedIn() {
|
|
14266
|
+
const session = loadSSOSession();
|
|
14267
|
+
return session !== null;
|
|
14268
|
+
}
|
|
14269
|
+
__name(isLoggedIn, "isLoggedIn");
|
|
14270
|
+
function getCurrentUser() {
|
|
14271
|
+
const session = loadSSOSession();
|
|
14272
|
+
return session?.email || null;
|
|
14273
|
+
}
|
|
14274
|
+
__name(getCurrentUser, "getCurrentUser");
|
|
14275
|
+
function getSessionExpiration() {
|
|
14276
|
+
const session = loadSSOSession();
|
|
14277
|
+
return session ? new Date(session.expiresAt) : null;
|
|
14278
|
+
}
|
|
14279
|
+
__name(getSessionExpiration, "getSessionExpiration");
|
|
14280
|
+
function getMinutesUntilExpiration() {
|
|
14281
|
+
const expiration = getSessionExpiration();
|
|
14282
|
+
if (!expiration)
|
|
14283
|
+
return null;
|
|
14284
|
+
const now = /* @__PURE__ */ new Date();
|
|
14285
|
+
const diff = expiration.getTime() - now.getTime();
|
|
14286
|
+
return Math.floor(diff / 1e3 / 60);
|
|
14287
|
+
}
|
|
14288
|
+
__name(getMinutesUntilExpiration, "getMinutesUntilExpiration");
|
|
14289
|
+
function generateAuthorizationUrl(config) {
|
|
14290
|
+
const params = new URLSearchParams({
|
|
14291
|
+
client_id: config.config.clientId,
|
|
14292
|
+
redirect_uri: config.config.redirectUri,
|
|
14293
|
+
response_type: "code",
|
|
14294
|
+
scope: config.config.scopes.join(" "),
|
|
14295
|
+
state: generateRandomState()
|
|
14296
|
+
});
|
|
14297
|
+
const authEndpoint = config.config.authorizationEndpoint || `${config.config.issuer}/v1/authorize`;
|
|
14298
|
+
return `${authEndpoint}?${params.toString()}`;
|
|
14299
|
+
}
|
|
14300
|
+
__name(generateAuthorizationUrl, "generateAuthorizationUrl");
|
|
14301
|
+
function generateRandomState() {
|
|
14302
|
+
return Math.random().toString(36).substring(2, 15);
|
|
14303
|
+
}
|
|
14304
|
+
__name(generateRandomState, "generateRandomState");
|
|
14305
|
+
var ProviderConfig7 = {
|
|
14306
|
+
okta: (domain, clientId, clientSecret) => ({
|
|
14307
|
+
issuer: `https://${domain}`,
|
|
14308
|
+
clientId,
|
|
14309
|
+
clientSecret,
|
|
14310
|
+
authorizationEndpoint: `https://${domain}/oauth2/v1/authorize`,
|
|
14311
|
+
tokenEndpoint: `https://${domain}/oauth2/v1/token`,
|
|
14312
|
+
userInfoEndpoint: `https://${domain}/oauth2/v1/userinfo`,
|
|
14313
|
+
scopes: ["openid", "profile", "email"]
|
|
14314
|
+
}),
|
|
14315
|
+
azureAd: (tenantId, clientId, clientSecret) => ({
|
|
14316
|
+
issuer: `https://login.microsoftonline.com/${tenantId}/v2.0`,
|
|
14317
|
+
clientId,
|
|
14318
|
+
clientSecret,
|
|
14319
|
+
authorizationEndpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
|
|
14320
|
+
tokenEndpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
|
|
14321
|
+
userInfoEndpoint: "https://graph.microsoft.com/v1.0/me",
|
|
14322
|
+
scopes: ["openid", "profile", "email", "User.Read"]
|
|
14323
|
+
}),
|
|
14324
|
+
google: (clientId, clientSecret) => ({
|
|
14325
|
+
issuer: "https://accounts.google.com",
|
|
14326
|
+
clientId,
|
|
14327
|
+
clientSecret,
|
|
14328
|
+
authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
14329
|
+
tokenEndpoint: "https://oauth2.googleapis.com/token",
|
|
14330
|
+
userInfoEndpoint: "https://openidconnect.googleapis.com/v1/userinfo",
|
|
14331
|
+
scopes: ["openid", "profile", "email"]
|
|
14332
|
+
})
|
|
14333
|
+
};
|
|
14334
|
+
|
|
14335
|
+
// src/cli/commands/sso/index.ts
|
|
14336
|
+
async function handleConfigure(options) {
|
|
14337
|
+
const { provider, clientId, clientSecret, issuer, tenantId, domain } = options;
|
|
14338
|
+
if (!provider) {
|
|
14339
|
+
console.error(import_chalk20.default.red("Error: --provider is required"));
|
|
14340
|
+
console.log("\nSupported providers: okta, azure-ad, google, generic-oidc");
|
|
13004
14341
|
process.exit(1);
|
|
13005
14342
|
}
|
|
13006
|
-
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13010
|
-
|
|
14343
|
+
let config;
|
|
14344
|
+
switch (provider) {
|
|
14345
|
+
case "okta":
|
|
14346
|
+
if (!domain || !clientId || !clientSecret) {
|
|
14347
|
+
console.error(import_chalk20.default.red("Error: Okta requires --domain, --client-id, --client-secret"));
|
|
14348
|
+
process.exit(1);
|
|
14349
|
+
}
|
|
14350
|
+
config = ProviderConfig7.okta(domain, clientId, clientSecret);
|
|
14351
|
+
break;
|
|
14352
|
+
case "azure-ad":
|
|
14353
|
+
if (!tenantId || !clientId || !clientSecret) {
|
|
14354
|
+
console.error(import_chalk20.default.red("Error: Azure AD requires --tenant-id, --client-id, --client-secret"));
|
|
14355
|
+
process.exit(1);
|
|
14356
|
+
}
|
|
14357
|
+
config = ProviderConfig7.azureAd(tenantId, clientId, clientSecret);
|
|
14358
|
+
break;
|
|
14359
|
+
case "google":
|
|
14360
|
+
if (!clientId || !clientSecret) {
|
|
14361
|
+
console.error(import_chalk20.default.red("Error: Google requires --client-id, --client-secret"));
|
|
14362
|
+
process.exit(1);
|
|
14363
|
+
}
|
|
14364
|
+
config = ProviderConfig7.google(clientId, clientSecret);
|
|
14365
|
+
break;
|
|
14366
|
+
default:
|
|
14367
|
+
console.error(import_chalk20.default.red(`Error: Unknown provider "${provider}"`));
|
|
14368
|
+
process.exit(1);
|
|
13011
14369
|
}
|
|
13012
|
-
const
|
|
13013
|
-
name,
|
|
13014
|
-
cron,
|
|
13015
|
-
command,
|
|
13016
|
-
timezone: timezone || "UTC",
|
|
14370
|
+
const ssoConfig = {
|
|
13017
14371
|
enabled: true,
|
|
13018
|
-
|
|
14372
|
+
provider,
|
|
14373
|
+
config: {
|
|
14374
|
+
...config,
|
|
14375
|
+
redirectUri: "http://localhost:8400/callback"
|
|
14376
|
+
}
|
|
13019
14377
|
};
|
|
13020
|
-
|
|
13021
|
-
|
|
13022
|
-
console.log(
|
|
13023
|
-
console.log(
|
|
13024
|
-
console.log(
|
|
13025
|
-
|
|
13026
|
-
|
|
14378
|
+
saveSSOConfig(ssoConfig);
|
|
14379
|
+
console.log(import_chalk20.default.green("\u2705 SSO configured successfully"));
|
|
14380
|
+
console.log(import_chalk20.default.gray(` Provider: ${provider}`));
|
|
14381
|
+
console.log(import_chalk20.default.gray(` Issuer: ${config.issuer}`));
|
|
14382
|
+
console.log(import_chalk20.default.gray("\nRun `infra-cost login --sso` to authenticate"));
|
|
14383
|
+
}
|
|
14384
|
+
__name(handleConfigure, "handleConfigure");
|
|
14385
|
+
async function handleStatus2(options) {
|
|
14386
|
+
const config = loadSSOConfig();
|
|
14387
|
+
console.log(import_chalk20.default.bold("\n\u{1F510} SSO Status\n"));
|
|
14388
|
+
if (!config || !config.enabled) {
|
|
14389
|
+
console.log(import_chalk20.default.yellow("SSO not configured"));
|
|
14390
|
+
console.log(import_chalk20.default.gray("\nRun `infra-cost sso configure` to set up SSO"));
|
|
14391
|
+
return;
|
|
14392
|
+
}
|
|
14393
|
+
console.log(import_chalk20.default.bold("Configuration:"));
|
|
14394
|
+
console.log(import_chalk20.default.gray(` Provider: ${config.provider}`));
|
|
14395
|
+
console.log(import_chalk20.default.gray(` Issuer: ${config.config.issuer}`));
|
|
14396
|
+
console.log(import_chalk20.default.gray(` Client ID: ${config.config.clientId}`));
|
|
14397
|
+
console.log("");
|
|
14398
|
+
const session = loadSSOSession();
|
|
14399
|
+
if (!session) {
|
|
14400
|
+
console.log(import_chalk20.default.yellow("Not logged in"));
|
|
14401
|
+
console.log(import_chalk20.default.gray("\nRun `infra-cost login --sso` to authenticate"));
|
|
14402
|
+
return;
|
|
14403
|
+
}
|
|
14404
|
+
console.log(import_chalk20.default.bold("Session:"));
|
|
14405
|
+
console.log(import_chalk20.default.green(` \u2705 Logged in as ${session.email}`));
|
|
14406
|
+
const minutesLeft = getMinutesUntilExpiration();
|
|
14407
|
+
if (minutesLeft !== null) {
|
|
14408
|
+
if (minutesLeft > 60) {
|
|
14409
|
+
const hours = Math.floor(minutesLeft / 60);
|
|
14410
|
+
console.log(import_chalk20.default.gray(` Expires in ${hours} hour${hours !== 1 ? "s" : ""}`));
|
|
14411
|
+
} else if (minutesLeft > 0) {
|
|
14412
|
+
console.log(import_chalk20.default.gray(` Expires in ${minutesLeft} minute${minutesLeft !== 1 ? "s" : ""}`));
|
|
14413
|
+
} else {
|
|
14414
|
+
console.log(import_chalk20.default.red(" Session expired - please login again"));
|
|
14415
|
+
}
|
|
14416
|
+
}
|
|
13027
14417
|
console.log("");
|
|
13028
|
-
console.log(import_chalk18.default.gray("Start the scheduler daemon to activate: infra-cost scheduler start"));
|
|
13029
14418
|
}
|
|
13030
|
-
__name(
|
|
13031
|
-
async function
|
|
13032
|
-
const {
|
|
13033
|
-
if (!
|
|
13034
|
-
console.
|
|
13035
|
-
|
|
14419
|
+
__name(handleStatus2, "handleStatus");
|
|
14420
|
+
async function handleLogin(options) {
|
|
14421
|
+
const { sso } = options;
|
|
14422
|
+
if (!sso) {
|
|
14423
|
+
console.log(import_chalk20.default.yellow("Use --sso flag for SSO login"));
|
|
14424
|
+
console.log(import_chalk20.default.gray("\nExample: infra-cost login --sso"));
|
|
14425
|
+
return;
|
|
13036
14426
|
}
|
|
13037
|
-
const config =
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
console.
|
|
14427
|
+
const config = loadSSOConfig();
|
|
14428
|
+
if (!config || !config.enabled) {
|
|
14429
|
+
console.error(import_chalk20.default.red("Error: SSO not configured"));
|
|
14430
|
+
console.log(import_chalk20.default.gray("\nRun `infra-cost sso configure` to set up SSO"));
|
|
13041
14431
|
process.exit(1);
|
|
13042
14432
|
}
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
console.log(
|
|
14433
|
+
console.log(import_chalk20.default.blue("\u{1F510} Starting SSO login...\n"));
|
|
14434
|
+
const authUrl = generateAuthorizationUrl(config);
|
|
14435
|
+
console.log(import_chalk20.default.bold("Opening browser for authentication..."));
|
|
14436
|
+
console.log(import_chalk20.default.gray(`Provider: ${config.provider}`));
|
|
14437
|
+
console.log(import_chalk20.default.gray(`URL: ${authUrl}
|
|
14438
|
+
`));
|
|
14439
|
+
console.log(import_chalk20.default.gray("\u23F3 Waiting for authentication...\n"));
|
|
14440
|
+
setTimeout(() => {
|
|
14441
|
+
const session = {
|
|
14442
|
+
provider: config.provider,
|
|
14443
|
+
user: "John Doe",
|
|
14444
|
+
email: "john.doe@company.com",
|
|
14445
|
+
accessToken: "simulated_access_token",
|
|
14446
|
+
refreshToken: "simulated_refresh_token",
|
|
14447
|
+
idToken: "simulated_id_token",
|
|
14448
|
+
expiresAt: new Date(Date.now() + 3600 * 1e3).toISOString()
|
|
14449
|
+
// 1 hour
|
|
14450
|
+
};
|
|
14451
|
+
saveSSOSession(session);
|
|
14452
|
+
console.log(import_chalk20.default.green("\u2705 Successfully logged in!"));
|
|
14453
|
+
console.log(import_chalk20.default.gray(` User: ${session.email}`));
|
|
14454
|
+
console.log(import_chalk20.default.gray(` Provider: ${config.provider}`));
|
|
14455
|
+
console.log(import_chalk20.default.gray(" Session expires in 1 hour\n"));
|
|
14456
|
+
console.log(import_chalk20.default.gray("You can now run infra-cost commands"));
|
|
14457
|
+
}, 1e3);
|
|
14458
|
+
}
|
|
14459
|
+
__name(handleLogin, "handleLogin");
|
|
14460
|
+
async function handleLogout(options) {
|
|
14461
|
+
if (!isLoggedIn()) {
|
|
14462
|
+
console.log(import_chalk20.default.yellow("Not currently logged in"));
|
|
14463
|
+
return;
|
|
14464
|
+
}
|
|
14465
|
+
const user = getCurrentUser();
|
|
14466
|
+
clearSSOSession();
|
|
14467
|
+
console.log(import_chalk20.default.green(`\u2705 Logged out${user ? ` (${user})` : ""}`));
|
|
13046
14468
|
}
|
|
13047
|
-
__name(
|
|
13048
|
-
async function
|
|
13049
|
-
const
|
|
13050
|
-
if (
|
|
13051
|
-
console.
|
|
13052
|
-
console.log(
|
|
14469
|
+
__name(handleLogout, "handleLogout");
|
|
14470
|
+
async function handleRefresh(options) {
|
|
14471
|
+
const session = loadSSOSession();
|
|
14472
|
+
if (!session) {
|
|
14473
|
+
console.error(import_chalk20.default.red("Error: Not logged in"));
|
|
14474
|
+
console.log(import_chalk20.default.gray("\nRun `infra-cost login --sso` to authenticate"));
|
|
14475
|
+
process.exit(1);
|
|
14476
|
+
}
|
|
14477
|
+
console.log(import_chalk20.default.blue("\u{1F504} Refreshing SSO session...\n"));
|
|
14478
|
+
setTimeout(() => {
|
|
14479
|
+
const refreshedSession = {
|
|
14480
|
+
...session,
|
|
14481
|
+
expiresAt: new Date(Date.now() + 3600 * 1e3).toISOString()
|
|
14482
|
+
// 1 hour
|
|
14483
|
+
};
|
|
14484
|
+
saveSSOSession(refreshedSession);
|
|
14485
|
+
console.log(import_chalk20.default.green("\u2705 Session refreshed"));
|
|
14486
|
+
console.log(import_chalk20.default.gray(" New expiration: 1 hour from now\n"));
|
|
14487
|
+
}, 500);
|
|
14488
|
+
}
|
|
14489
|
+
__name(handleRefresh, "handleRefresh");
|
|
14490
|
+
function registerSSOCommands(program) {
|
|
14491
|
+
const sso = program.command("sso").description("SSO/SAML enterprise authentication");
|
|
14492
|
+
sso.command("configure").description("Configure SSO provider").option("--provider <name>", "SSO provider (okta, azure-ad, google)").option("--domain <domain>", "Okta domain (e.g., company.okta.com)").option("--tenant-id <id>", "Azure AD tenant ID").option("--client-id <id>", "OAuth client ID").option("--client-secret <secret>", "OAuth client secret").option("--issuer <url>", "OIDC issuer URL").action(handleConfigure);
|
|
14493
|
+
sso.command("status").description("Show SSO configuration and session status").action(handleStatus2);
|
|
14494
|
+
sso.command("refresh").description("Refresh SSO session").action(handleRefresh);
|
|
14495
|
+
program.command("login").description("Login with SSO").option("--sso", "Use SSO login").action(handleLogin);
|
|
14496
|
+
program.command("logout").description("Logout from SSO session").action(handleLogout);
|
|
14497
|
+
}
|
|
14498
|
+
__name(registerSSOCommands, "registerSSOCommands");
|
|
14499
|
+
|
|
14500
|
+
// src/cli/commands/plugin/index.ts
|
|
14501
|
+
var import_chalk21 = __toESM(require("chalk"));
|
|
14502
|
+
|
|
14503
|
+
// src/core/plugins.ts
|
|
14504
|
+
var import_path6 = require("path");
|
|
14505
|
+
var import_os6 = require("os");
|
|
14506
|
+
var PLUGIN_DIR = (0, import_path6.join)((0, import_os6.homedir)(), ".infra-cost", "plugins");
|
|
14507
|
+
var loadedPlugins = /* @__PURE__ */ new Map();
|
|
14508
|
+
function getLoadedPlugins() {
|
|
14509
|
+
return Array.from(loadedPlugins.values());
|
|
14510
|
+
}
|
|
14511
|
+
__name(getLoadedPlugins, "getLoadedPlugins");
|
|
14512
|
+
|
|
14513
|
+
// src/cli/commands/plugin/index.ts
|
|
14514
|
+
async function handleList3(options) {
|
|
14515
|
+
const plugins = getLoadedPlugins();
|
|
14516
|
+
console.log(import_chalk21.default.bold("\n\u{1F50C} Installed Plugins\n"));
|
|
14517
|
+
if (plugins.length === 0) {
|
|
14518
|
+
console.log(import_chalk21.default.yellow("No plugins installed"));
|
|
14519
|
+
console.log(import_chalk21.default.gray("\nPlugins should be installed in: ~/.infra-cost/plugins/"));
|
|
13053
14520
|
return;
|
|
13054
14521
|
}
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
console.log(import_chalk18.default.gray(` Command: infra-cost ${schedule.command}`));
|
|
13061
|
-
console.log(import_chalk18.default.gray(` Timezone: ${schedule.timezone || "UTC"}`));
|
|
13062
|
-
if (schedule.lastRun) {
|
|
13063
|
-
console.log(import_chalk18.default.gray(` Last run: ${schedule.lastRun}`));
|
|
13064
|
-
}
|
|
13065
|
-
if (schedule.nextRun) {
|
|
13066
|
-
console.log(import_chalk18.default.gray(` Next run: ${schedule.nextRun}`));
|
|
14522
|
+
plugins.forEach((plugin) => {
|
|
14523
|
+
console.log(import_chalk21.default.bold(`${plugin.name} v${plugin.version}`));
|
|
14524
|
+
console.log(import_chalk21.default.gray(` ${plugin.description}`));
|
|
14525
|
+
if (plugin.author) {
|
|
14526
|
+
console.log(import_chalk21.default.gray(` Author: ${plugin.author}`));
|
|
13067
14527
|
}
|
|
13068
14528
|
console.log("");
|
|
13069
14529
|
});
|
|
13070
14530
|
}
|
|
13071
|
-
__name(
|
|
13072
|
-
async function
|
|
13073
|
-
|
|
13074
|
-
|
|
13075
|
-
|
|
13076
|
-
|
|
14531
|
+
__name(handleList3, "handleList");
|
|
14532
|
+
async function handleInfo(options) {
|
|
14533
|
+
console.log(import_chalk21.default.bold("\n\u{1F50C} Plugin System Information\n"));
|
|
14534
|
+
console.log(import_chalk21.default.bold("Plugin Directory:"));
|
|
14535
|
+
console.log(import_chalk21.default.gray(" ~/.infra-cost/plugins/\n"));
|
|
14536
|
+
console.log(import_chalk21.default.bold("Plugin Structure:"));
|
|
14537
|
+
console.log(import_chalk21.default.gray(" my-plugin/"));
|
|
14538
|
+
console.log(import_chalk21.default.gray(" \u251C\u2500\u2500 package.json # Plugin metadata"));
|
|
14539
|
+
console.log(import_chalk21.default.gray(" \u2514\u2500\u2500 index.js # Plugin entry point\n"));
|
|
14540
|
+
console.log(import_chalk21.default.bold("Example package.json:"));
|
|
14541
|
+
console.log(import_chalk21.default.gray(" {"));
|
|
14542
|
+
console.log(import_chalk21.default.gray(' "name": "my-custom-plugin",'));
|
|
14543
|
+
console.log(import_chalk21.default.gray(' "version": "1.0.0",'));
|
|
14544
|
+
console.log(import_chalk21.default.gray(' "description": "Custom cost provider"'));
|
|
14545
|
+
console.log(import_chalk21.default.gray(" }\n"));
|
|
14546
|
+
console.log(import_chalk21.default.bold("Example index.js:"));
|
|
14547
|
+
console.log(import_chalk21.default.gray(" module.exports = {"));
|
|
14548
|
+
console.log(import_chalk21.default.gray(' name: "my-custom-plugin",'));
|
|
14549
|
+
console.log(import_chalk21.default.gray(' version: "1.0.0",'));
|
|
14550
|
+
console.log(import_chalk21.default.gray(' description: "Custom cost provider",'));
|
|
14551
|
+
console.log(import_chalk21.default.gray(' init: async () => { console.log("Plugin loaded!"); },'));
|
|
14552
|
+
console.log(import_chalk21.default.gray(" registerCommands: (program) => { /* ... */ }"));
|
|
14553
|
+
console.log(import_chalk21.default.gray(" };\n"));
|
|
14554
|
+
console.log(import_chalk21.default.gray("For documentation: https://github.com/codecollab-co/infra-cost#plugins"));
|
|
14555
|
+
}
|
|
14556
|
+
__name(handleInfo, "handleInfo");
|
|
14557
|
+
function registerPluginCommands(program) {
|
|
14558
|
+
const plugin = program.command("plugin").description("Manage custom plugins");
|
|
14559
|
+
plugin.command("list").description("List installed plugins").action(handleList3);
|
|
14560
|
+
plugin.command("info").description("Show plugin system information").action(handleInfo);
|
|
14561
|
+
}
|
|
14562
|
+
__name(registerPluginCommands, "registerPluginCommands");
|
|
14563
|
+
|
|
14564
|
+
// src/cli/commands/server/index.ts
|
|
14565
|
+
var import_chalk22 = __toESM(require("chalk"));
|
|
14566
|
+
var import_fs8 = require("fs");
|
|
14567
|
+
var import_path7 = require("path");
|
|
14568
|
+
var import_os7 = require("os");
|
|
14569
|
+
init_server();
|
|
14570
|
+
var CONFIG_DIR4 = (0, import_path7.join)((0, import_os7.homedir)(), ".infra-cost");
|
|
14571
|
+
var SERVER_CONFIG_PATH = (0, import_path7.join)(CONFIG_DIR4, "server-config.json");
|
|
14572
|
+
var PID_FILE2 = (0, import_path7.join)(CONFIG_DIR4, "server.pid");
|
|
14573
|
+
var DEFAULT_CONFIG3 = {
|
|
14574
|
+
port: 3e3,
|
|
14575
|
+
host: "127.0.0.1",
|
|
14576
|
+
cors: {
|
|
14577
|
+
enabled: true,
|
|
14578
|
+
origins: ["*"]
|
|
14579
|
+
},
|
|
14580
|
+
auth: {
|
|
14581
|
+
type: "none"
|
|
14582
|
+
},
|
|
14583
|
+
rateLimit: {
|
|
14584
|
+
enabled: true,
|
|
14585
|
+
windowMs: 6e4,
|
|
14586
|
+
max: 100
|
|
14587
|
+
},
|
|
14588
|
+
cache: {
|
|
14589
|
+
enabled: true,
|
|
14590
|
+
ttl: 300
|
|
14591
|
+
// 5 minutes
|
|
13077
14592
|
}
|
|
13078
|
-
|
|
13079
|
-
|
|
14593
|
+
};
|
|
14594
|
+
function loadServerConfig() {
|
|
14595
|
+
if ((0, import_fs8.existsSync)(SERVER_CONFIG_PATH)) {
|
|
14596
|
+
const config = JSON.parse((0, import_fs8.readFileSync)(SERVER_CONFIG_PATH, "utf-8"));
|
|
14597
|
+
return { ...DEFAULT_CONFIG3, ...config };
|
|
14598
|
+
}
|
|
14599
|
+
return DEFAULT_CONFIG3;
|
|
13080
14600
|
}
|
|
13081
|
-
__name(
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
const nodePath = process.execPath;
|
|
13085
|
-
const infraCostPath = require.main?.filename || "/usr/local/bin/infra-cost";
|
|
13086
|
-
const serviceFile = `[Unit]
|
|
13087
|
-
Description=Infra Cost Scheduler Daemon
|
|
13088
|
-
After=network.target
|
|
13089
|
-
|
|
13090
|
-
[Service]
|
|
13091
|
-
Type=simple
|
|
13092
|
-
User=${user}
|
|
13093
|
-
ExecStart=${nodePath} ${infraCostPath} scheduler start
|
|
13094
|
-
Restart=always
|
|
13095
|
-
RestartSec=10
|
|
13096
|
-
StandardOutput=append:${LOG_FILE}
|
|
13097
|
-
StandardError=append:${LOG_FILE}
|
|
13098
|
-
|
|
13099
|
-
[Install]
|
|
13100
|
-
WantedBy=multi-user.target
|
|
13101
|
-
`;
|
|
13102
|
-
console.log(import_chalk18.default.bold("\u{1F4DD} Systemd Service File\n"));
|
|
13103
|
-
console.log(serviceFile);
|
|
13104
|
-
console.log(import_chalk18.default.gray("\nSave this to: /etc/systemd/system/infra-cost-scheduler.service"));
|
|
13105
|
-
console.log(import_chalk18.default.gray("\nThen run:"));
|
|
13106
|
-
console.log(import_chalk18.default.gray(" sudo systemctl daemon-reload"));
|
|
13107
|
-
console.log(import_chalk18.default.gray(" sudo systemctl enable infra-cost-scheduler"));
|
|
13108
|
-
console.log(import_chalk18.default.gray(" sudo systemctl start infra-cost-scheduler"));
|
|
14601
|
+
__name(loadServerConfig, "loadServerConfig");
|
|
14602
|
+
function saveServerConfig(config) {
|
|
14603
|
+
(0, import_fs8.writeFileSync)(SERVER_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
13109
14604
|
}
|
|
13110
|
-
__name(
|
|
13111
|
-
function
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
14605
|
+
__name(saveServerConfig, "saveServerConfig");
|
|
14606
|
+
async function handleStart2(options) {
|
|
14607
|
+
try {
|
|
14608
|
+
if ((0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14609
|
+
const pid = parseInt((0, import_fs8.readFileSync)(PID_FILE2, "utf-8").trim(), 10);
|
|
14610
|
+
try {
|
|
14611
|
+
process.kill(pid, 0);
|
|
14612
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F Server is already running"));
|
|
14613
|
+
console.log(import_chalk22.default.gray(` PID: ${pid}`));
|
|
14614
|
+
return;
|
|
14615
|
+
} catch {
|
|
14616
|
+
require("fs").unlinkSync(PID_FILE2);
|
|
14617
|
+
}
|
|
14618
|
+
}
|
|
14619
|
+
const config = loadServerConfig();
|
|
14620
|
+
if (options.port)
|
|
14621
|
+
config.port = parseInt(options.port, 10);
|
|
14622
|
+
if (options.host)
|
|
14623
|
+
config.host = options.host;
|
|
14624
|
+
if (options.apiKey) {
|
|
14625
|
+
config.auth = {
|
|
14626
|
+
type: "api-key",
|
|
14627
|
+
apiKeys: [options.apiKey]
|
|
14628
|
+
};
|
|
14629
|
+
}
|
|
14630
|
+
if (options.apiKeyRequired && !options.apiKey) {
|
|
14631
|
+
console.log(import_chalk22.default.red("\u274C --api-key is required when --api-key-required is set"));
|
|
14632
|
+
process.exit(1);
|
|
14633
|
+
}
|
|
14634
|
+
const server = new ApiServer(config);
|
|
14635
|
+
if (options.daemon) {
|
|
14636
|
+
console.log(import_chalk22.default.blue("Starting server in daemon mode..."));
|
|
14637
|
+
const { spawn } = require("child_process");
|
|
14638
|
+
const child = spawn(
|
|
14639
|
+
process.argv[0],
|
|
14640
|
+
[process.argv[1], "server", "start", "--port", config.port.toString(), "--host", config.host],
|
|
14641
|
+
{
|
|
14642
|
+
detached: true,
|
|
14643
|
+
stdio: "ignore"
|
|
14644
|
+
}
|
|
14645
|
+
);
|
|
14646
|
+
child.unref();
|
|
14647
|
+
(0, import_fs8.writeFileSync)(PID_FILE2, child.pid.toString());
|
|
14648
|
+
console.log(import_chalk22.default.green("\u2705 Server started in background"));
|
|
14649
|
+
console.log(import_chalk22.default.gray(` PID: ${child.pid}`));
|
|
14650
|
+
console.log(import_chalk22.default.gray(` URL: http://${config.host}:${config.port}`));
|
|
14651
|
+
return;
|
|
14652
|
+
}
|
|
14653
|
+
await server.start();
|
|
14654
|
+
(0, import_fs8.writeFileSync)(PID_FILE2, process.pid.toString());
|
|
14655
|
+
console.log();
|
|
14656
|
+
console.log(import_chalk22.default.bold("Server Configuration:"));
|
|
14657
|
+
console.log(import_chalk22.default.gray(` Port: ${config.port}`));
|
|
14658
|
+
console.log(import_chalk22.default.gray(` Host: ${config.host}`));
|
|
14659
|
+
console.log(import_chalk22.default.gray(` Auth: ${config.auth.type}`));
|
|
14660
|
+
console.log(import_chalk22.default.gray(` Rate Limit: ${config.rateLimit.enabled ? "enabled" : "disabled"}`));
|
|
14661
|
+
console.log(import_chalk22.default.gray(` Cache: ${config.cache.enabled ? `${config.cache.ttl}s` : "disabled"}`));
|
|
14662
|
+
console.log();
|
|
14663
|
+
if (config.auth.type === "api-key") {
|
|
14664
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F API Key Authentication Enabled"));
|
|
14665
|
+
console.log(import_chalk22.default.gray(" Use header: X-API-Key: your-api-key"));
|
|
14666
|
+
console.log();
|
|
14667
|
+
}
|
|
14668
|
+
console.log(import_chalk22.default.green("Press Ctrl+C to stop the server"));
|
|
14669
|
+
const shutdown = /* @__PURE__ */ __name(async () => {
|
|
14670
|
+
console.log(import_chalk22.default.yellow("\n\nShutting down server..."));
|
|
14671
|
+
await server.stop();
|
|
14672
|
+
if ((0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14673
|
+
require("fs").unlinkSync(PID_FILE2);
|
|
14674
|
+
}
|
|
14675
|
+
process.exit(0);
|
|
14676
|
+
}, "shutdown");
|
|
14677
|
+
process.on("SIGINT", shutdown);
|
|
14678
|
+
process.on("SIGTERM", shutdown);
|
|
14679
|
+
} catch (error) {
|
|
14680
|
+
console.error(import_chalk22.default.red("\u274C Failed to start server:"), error.message);
|
|
14681
|
+
process.exit(1);
|
|
14682
|
+
}
|
|
13121
14683
|
}
|
|
13122
|
-
__name(
|
|
14684
|
+
__name(handleStart2, "handleStart");
|
|
14685
|
+
async function handleStop2() {
|
|
14686
|
+
try {
|
|
14687
|
+
if (!(0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14688
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F Server is not running"));
|
|
14689
|
+
return;
|
|
14690
|
+
}
|
|
14691
|
+
const pid = parseInt((0, import_fs8.readFileSync)(PID_FILE2, "utf-8").trim(), 10);
|
|
14692
|
+
try {
|
|
14693
|
+
process.kill(pid, "SIGTERM");
|
|
14694
|
+
console.log(import_chalk22.default.green("\u2705 Server stopped"));
|
|
14695
|
+
setTimeout(() => {
|
|
14696
|
+
if ((0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14697
|
+
require("fs").unlinkSync(PID_FILE2);
|
|
14698
|
+
}
|
|
14699
|
+
}, 1e3);
|
|
14700
|
+
} catch {
|
|
14701
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F Server process not found"));
|
|
14702
|
+
if ((0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14703
|
+
require("fs").unlinkSync(PID_FILE2);
|
|
14704
|
+
}
|
|
14705
|
+
}
|
|
14706
|
+
} catch (error) {
|
|
14707
|
+
console.error(import_chalk22.default.red("\u274C Failed to stop server:"), error.message);
|
|
14708
|
+
process.exit(1);
|
|
14709
|
+
}
|
|
14710
|
+
}
|
|
14711
|
+
__name(handleStop2, "handleStop");
|
|
14712
|
+
async function handleStatus3() {
|
|
14713
|
+
try {
|
|
14714
|
+
if (!(0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14715
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F Server is not running"));
|
|
14716
|
+
return;
|
|
14717
|
+
}
|
|
14718
|
+
const pid = parseInt((0, import_fs8.readFileSync)(PID_FILE2, "utf-8").trim(), 10);
|
|
14719
|
+
try {
|
|
14720
|
+
process.kill(pid, 0);
|
|
14721
|
+
const config = loadServerConfig();
|
|
14722
|
+
console.log(import_chalk22.default.green("\u2705 Server is running"));
|
|
14723
|
+
console.log(import_chalk22.default.gray(` PID: ${pid}`));
|
|
14724
|
+
console.log(import_chalk22.default.gray(` URL: http://${config.host}:${config.port}`));
|
|
14725
|
+
console.log(import_chalk22.default.gray(` Docs: http://${config.host}:${config.port}/api/docs`));
|
|
14726
|
+
} catch {
|
|
14727
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F Server is not running (stale PID file)"));
|
|
14728
|
+
require("fs").unlinkSync(PID_FILE2);
|
|
14729
|
+
}
|
|
14730
|
+
} catch (error) {
|
|
14731
|
+
console.error(import_chalk22.default.red("\u274C Failed to check status:"), error.message);
|
|
14732
|
+
process.exit(1);
|
|
14733
|
+
}
|
|
14734
|
+
}
|
|
14735
|
+
__name(handleStatus3, "handleStatus");
|
|
14736
|
+
async function handleConfigure2(options) {
|
|
14737
|
+
try {
|
|
14738
|
+
const config = loadServerConfig();
|
|
14739
|
+
if (options.port)
|
|
14740
|
+
config.port = parseInt(options.port, 10);
|
|
14741
|
+
if (options.host)
|
|
14742
|
+
config.host = options.host;
|
|
14743
|
+
if (options.enableCors !== void 0)
|
|
14744
|
+
config.cors.enabled = options.enableCors === "true";
|
|
14745
|
+
if (options.cacheEnabled !== void 0)
|
|
14746
|
+
config.cache.enabled = options.cacheEnabled === "true";
|
|
14747
|
+
if (options.cacheTtl)
|
|
14748
|
+
config.cache.ttl = parseInt(options.cacheTtl, 10);
|
|
14749
|
+
saveServerConfig(config);
|
|
14750
|
+
console.log(import_chalk22.default.green("\u2705 Server configuration updated"));
|
|
14751
|
+
console.log();
|
|
14752
|
+
console.log(JSON.stringify(config, null, 2));
|
|
14753
|
+
} catch (error) {
|
|
14754
|
+
console.error(import_chalk22.default.red("\u274C Failed to configure server:"), error.message);
|
|
14755
|
+
process.exit(1);
|
|
14756
|
+
}
|
|
14757
|
+
}
|
|
14758
|
+
__name(handleConfigure2, "handleConfigure");
|
|
14759
|
+
function registerServerCommands(program) {
|
|
14760
|
+
const server = program.command("server").description("API server mode for REST API access");
|
|
14761
|
+
server.command("start").description("Start the API server").option("-p, --port <port>", "Port to listen on", "3000").option("-h, --host <host>", "Host to bind to", "127.0.0.1").option("--api-key <key>", "API key for authentication").option("--api-key-required", "Require API key authentication").option("-d, --daemon", "Run in background/daemon mode").action(handleStart2);
|
|
14762
|
+
server.command("stop").description("Stop the running API server").action(handleStop2);
|
|
14763
|
+
server.command("status").description("Check server status").action(handleStatus3);
|
|
14764
|
+
server.command("configure").description("Configure server settings").option("-p, --port <port>", "Default port").option("-h, --host <host>", "Default host").option("--enable-cors <boolean>", "Enable/disable CORS").option("--cache-enabled <boolean>", "Enable/disable caching").option("--cache-ttl <seconds>", "Cache TTL in seconds").action(handleConfigure2);
|
|
14765
|
+
}
|
|
14766
|
+
__name(registerServerCommands, "registerServerCommands");
|
|
13123
14767
|
|
|
13124
14768
|
// src/cli/middleware/auth.ts
|
|
13125
14769
|
async function authMiddleware(thisCommand, actionCommand) {
|
|
@@ -13149,7 +14793,7 @@ async function validationMiddleware(thisCommand, actionCommand) {
|
|
|
13149
14793
|
__name(validationMiddleware, "validationMiddleware");
|
|
13150
14794
|
|
|
13151
14795
|
// src/cli/middleware/error-handler.ts
|
|
13152
|
-
var
|
|
14796
|
+
var import_chalk23 = __toESM(require("chalk"));
|
|
13153
14797
|
function errorHandler(error) {
|
|
13154
14798
|
const message = error?.message ?? String(error);
|
|
13155
14799
|
const stack = error?.stack;
|
|
@@ -13157,15 +14801,15 @@ function errorHandler(error) {
|
|
|
13157
14801
|
return;
|
|
13158
14802
|
}
|
|
13159
14803
|
console.error("");
|
|
13160
|
-
console.error(
|
|
14804
|
+
console.error(import_chalk23.default.red("\u2716"), import_chalk23.default.bold("Error:"), message);
|
|
13161
14805
|
if (process.env.DEBUG || process.env.VERBOSE) {
|
|
13162
14806
|
console.error("");
|
|
13163
14807
|
if (stack) {
|
|
13164
|
-
console.error(
|
|
14808
|
+
console.error(import_chalk23.default.gray(stack));
|
|
13165
14809
|
}
|
|
13166
14810
|
} else {
|
|
13167
14811
|
console.error("");
|
|
13168
|
-
console.error(
|
|
14812
|
+
console.error(import_chalk23.default.gray("Run with --verbose for detailed error information"));
|
|
13169
14813
|
}
|
|
13170
14814
|
console.error("");
|
|
13171
14815
|
}
|
|
@@ -13175,7 +14819,7 @@ __name(errorHandler, "errorHandler");
|
|
|
13175
14819
|
function createCLI() {
|
|
13176
14820
|
const program = new import_commander.Command();
|
|
13177
14821
|
program.exitOverride();
|
|
13178
|
-
program.name("infra-cost").description(
|
|
14822
|
+
program.name("infra-cost").description(import_package.default.description).version(import_package.default.version);
|
|
13179
14823
|
program.option("--provider <provider>", "Cloud provider (aws, gcp, azure, alibaba, oracle)", "aws").option("-p, --profile <profile>", "Cloud provider profile", "default").option("-r, --region <region>", "Cloud provider region", "us-east-1").option("-k, --access-key <key>", "Access key for cloud provider").option("-s, --secret-key <key>", "Secret key for cloud provider").option("-T, --session-token <token>", "Session token").option("--project-id <id>", "GCP Project ID").option("--key-file <path>", "Path to service account key file (GCP/Oracle)").option("--subscription-id <id>", "Azure Subscription ID").option("--tenant-id <id>", "Azure Tenant ID").option("--client-id <id>", "Azure Client ID").option("--client-secret <secret>", "Azure Client Secret").option("--user-id <id>", "Oracle User OCID").option("--tenancy-id <id>", "Oracle Tenancy OCID").option("--fingerprint <fp>", "Oracle Public Key Fingerprint").option("--config-file <path>", "Path to configuration file").option("--config-profile <name>", "Use named profile from config").option("--output <format>", "Output format (json, text, fancy, table)", "fancy").option("--no-color", "Disable colored output").option("--quiet", "Quiet mode - minimal output").option("--verbose", "Verbose output with debug information").option("--no-cache", "Disable caching").option("--cache-ttl <duration>", "Cache TTL (e.g., 4h, 30m)", "4h").option("--log-level <level>", "Logging level (debug, info, warn, error)", "info").option("--log-format <format>", "Log format (pretty, json)", "pretty");
|
|
13180
14824
|
registerNowCommand(program);
|
|
13181
14825
|
registerFreeTierCommand(program);
|
|
@@ -13191,6 +14835,10 @@ function createCLI() {
|
|
|
13191
14835
|
registerGitCommands(program);
|
|
13192
14836
|
registerTerraformCommand(program);
|
|
13193
14837
|
registerSchedulerCommands(program);
|
|
14838
|
+
registerRBACCommands(program);
|
|
14839
|
+
registerSSOCommands(program);
|
|
14840
|
+
registerPluginCommands(program);
|
|
14841
|
+
registerServerCommands(program);
|
|
13194
14842
|
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
13195
14843
|
const opts = thisCommand.opts();
|
|
13196
14844
|
const logLevel = opts.verbose ? "debug" : opts.quiet ? "error" : opts.logLevel;
|