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/index.js
CHANGED
|
@@ -9,6 +9,9 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
9
9
|
var __esm = (fn, res) => function __init() {
|
|
10
10
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
11
|
};
|
|
12
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
13
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
14
|
+
};
|
|
12
15
|
var __export = (target, all) => {
|
|
13
16
|
for (var name in all)
|
|
14
17
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -29,6 +32,172 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
32
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
33
|
mod
|
|
31
34
|
));
|
|
35
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
36
|
+
|
|
37
|
+
// package.json
|
|
38
|
+
var require_package = __commonJS({
|
|
39
|
+
"package.json"(exports, module2) {
|
|
40
|
+
module2.exports = {
|
|
41
|
+
name: "infra-cost",
|
|
42
|
+
version: "1.7.0",
|
|
43
|
+
description: "Multi-cloud FinOps CLI tool for comprehensive cost analysis and infrastructure optimization across AWS, GCP, Azure, Alibaba Cloud, and Oracle Cloud",
|
|
44
|
+
keywords: [
|
|
45
|
+
"aws",
|
|
46
|
+
"gcp",
|
|
47
|
+
"azure",
|
|
48
|
+
"cloud-cost",
|
|
49
|
+
"finops",
|
|
50
|
+
"cost-optimization",
|
|
51
|
+
"multi-cloud",
|
|
52
|
+
"cost-analysis",
|
|
53
|
+
"infrastructure",
|
|
54
|
+
"cloud-billing",
|
|
55
|
+
"cost-management",
|
|
56
|
+
"devops",
|
|
57
|
+
"cli-tool",
|
|
58
|
+
"cost-monitoring",
|
|
59
|
+
"budget-tracking"
|
|
60
|
+
],
|
|
61
|
+
author: {
|
|
62
|
+
name: "Code Collab",
|
|
63
|
+
email: "codecollab.co@gmail.com",
|
|
64
|
+
url: "https://github.com/codecollab-co/infra-cost"
|
|
65
|
+
},
|
|
66
|
+
files: [
|
|
67
|
+
"!tests/**/*",
|
|
68
|
+
"dist/**/*",
|
|
69
|
+
"!dist/**/*.js.map",
|
|
70
|
+
"bin/**/*"
|
|
71
|
+
],
|
|
72
|
+
bin: {
|
|
73
|
+
"infra-cost": "./bin/index.js",
|
|
74
|
+
"aws-cost": "./bin/index.js"
|
|
75
|
+
},
|
|
76
|
+
main: "./dist/index.js",
|
|
77
|
+
scripts: {
|
|
78
|
+
prebuild: "run-s clean",
|
|
79
|
+
build: "tsup",
|
|
80
|
+
clean: "rm -rf dist",
|
|
81
|
+
typecheck: "tsc --noEmit",
|
|
82
|
+
lint: "eslint src --ext .ts",
|
|
83
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
84
|
+
test: "jest",
|
|
85
|
+
"test:watch": "jest --watch",
|
|
86
|
+
"test:coverage": "jest --coverage",
|
|
87
|
+
dev: "tsup --watch",
|
|
88
|
+
"version:check": `echo "Current version: $(npm pkg get version | tr -d '"')"`,
|
|
89
|
+
"version:next": "npm version patch --no-git-tag-version",
|
|
90
|
+
"version:bump:patch": "npm version patch",
|
|
91
|
+
"version:bump:minor": "npm version minor",
|
|
92
|
+
"version:bump:major": "npm version major",
|
|
93
|
+
"publish:dry": "npm publish --dry-run",
|
|
94
|
+
"publish:latest": "npm publish",
|
|
95
|
+
"publish:beta": "npm publish --tag beta",
|
|
96
|
+
"prepare-release": "npm run build && npm run test && npm run version:bump:patch",
|
|
97
|
+
postpublish: 'echo "\u{1F389} Published $(npm pkg get name)@$(npm pkg get version) to npm!"',
|
|
98
|
+
prepublishOnly: "npm run build"
|
|
99
|
+
},
|
|
100
|
+
repository: {
|
|
101
|
+
type: "git",
|
|
102
|
+
url: "https://github.com/codecollab-co/infra-cost.git"
|
|
103
|
+
},
|
|
104
|
+
bugs: {
|
|
105
|
+
url: "https://github.com/codecollab-co/infra-cost/issues"
|
|
106
|
+
},
|
|
107
|
+
homepage: "https://github.com/codecollab-co/infra-cost#readme",
|
|
108
|
+
license: "MIT",
|
|
109
|
+
engines: {
|
|
110
|
+
node: ">=20.0.0",
|
|
111
|
+
npm: ">=10.0.0"
|
|
112
|
+
},
|
|
113
|
+
dependencies: {
|
|
114
|
+
"@alicloud/bssopenapi20171214": "^2.0.1",
|
|
115
|
+
"@alicloud/cs20151215": "^4.0.1",
|
|
116
|
+
"@alicloud/ecs20140526": "^4.0.3",
|
|
117
|
+
"@alicloud/oss20190517": "^1.0.6",
|
|
118
|
+
"@alicloud/rds20140815": "^3.0.2",
|
|
119
|
+
"@alicloud/tea-util": "^1.4.7",
|
|
120
|
+
"@aws-sdk/client-budgets": "^3.975.0",
|
|
121
|
+
"@aws-sdk/client-cost-explorer": "^3.975.0",
|
|
122
|
+
"@aws-sdk/client-ec2": "^3.975.0",
|
|
123
|
+
"@aws-sdk/client-elastic-load-balancing-v2": "^3.975.0",
|
|
124
|
+
"@aws-sdk/client-iam": "^3.975.0",
|
|
125
|
+
"@aws-sdk/client-lambda": "^3.975.0",
|
|
126
|
+
"@aws-sdk/client-rds": "^3.975.0",
|
|
127
|
+
"@aws-sdk/client-s3": "^3.975.0",
|
|
128
|
+
"@aws-sdk/client-sts": "^3.975.0",
|
|
129
|
+
"@aws-sdk/credential-providers": "^3.975.0",
|
|
130
|
+
"@azure/arm-compute": "^23.3.0",
|
|
131
|
+
"@azure/arm-consumption": "^9.2.1",
|
|
132
|
+
"@azure/arm-containerservice": "^24.1.0",
|
|
133
|
+
"@azure/arm-costmanagement": "^1.0.0-beta.2",
|
|
134
|
+
"@azure/arm-network": "^35.0.0",
|
|
135
|
+
"@azure/arm-sql": "^10.0.0",
|
|
136
|
+
"@azure/arm-storage": "^19.1.0",
|
|
137
|
+
"@azure/arm-subscriptions": "^6.0.0",
|
|
138
|
+
"@azure/identity": "^4.13.0",
|
|
139
|
+
"@google-cloud/bigquery": "^8.1.1",
|
|
140
|
+
"@google-cloud/billing": "^5.1.1",
|
|
141
|
+
"@google-cloud/compute": "^6.7.0",
|
|
142
|
+
"@google-cloud/container": "^6.6.0",
|
|
143
|
+
"@google-cloud/monitoring": "^5.3.1",
|
|
144
|
+
"@google-cloud/resource-manager": "^6.2.1",
|
|
145
|
+
"@google-cloud/sql": "^0.24.0",
|
|
146
|
+
"@google-cloud/storage": "^7.18.0",
|
|
147
|
+
"@slack/web-api": "^7.5.0",
|
|
148
|
+
callsites: "^3.1.0",
|
|
149
|
+
chalk: "^4.1.2",
|
|
150
|
+
"cli-progress": "^3.12.0",
|
|
151
|
+
"cli-table3": "^0.6.5",
|
|
152
|
+
commander: "^12.1.0",
|
|
153
|
+
cors: "^2.8.6",
|
|
154
|
+
dayjs: "^1.11.19",
|
|
155
|
+
exceljs: "^4.4.0",
|
|
156
|
+
express: "^5.2.1",
|
|
157
|
+
"express-rate-limit": "^8.2.1",
|
|
158
|
+
"fd-slicer": "^1.1.0",
|
|
159
|
+
"google-auth-library": "^10.5.0",
|
|
160
|
+
googleapis: "^171.0.0",
|
|
161
|
+
helmet: "^8.1.0",
|
|
162
|
+
ini: "^6.0.0",
|
|
163
|
+
ink: "^6.6.0",
|
|
164
|
+
moment: "^2.30.1",
|
|
165
|
+
"node-fetch": "^2.7.0",
|
|
166
|
+
"oci-budget": "^2.88.0",
|
|
167
|
+
"oci-common": "^2.88.0",
|
|
168
|
+
"oci-containerengine": "^2.88.0",
|
|
169
|
+
"oci-core": "^2.88.0",
|
|
170
|
+
"oci-database": "^2.88.0",
|
|
171
|
+
"oci-identity": "^2.88.0",
|
|
172
|
+
"oci-objectstorage": "^2.88.0",
|
|
173
|
+
"oci-usageapi": "^2.88.0",
|
|
174
|
+
ora: "^9.1.0",
|
|
175
|
+
pako: "^2.1.0",
|
|
176
|
+
pend: "^1.2.0",
|
|
177
|
+
react: "^19.2.4",
|
|
178
|
+
"swagger-jsdoc": "^6.2.8",
|
|
179
|
+
"swagger-ui-express": "^5.0.1",
|
|
180
|
+
yauzl: "^3.0.0",
|
|
181
|
+
zod: "^3.23.8"
|
|
182
|
+
},
|
|
183
|
+
devDependencies: {
|
|
184
|
+
"@types/cors": "^2.8.19",
|
|
185
|
+
"@types/express": "^5.0.6",
|
|
186
|
+
"@types/jest": "^29.5.12",
|
|
187
|
+
"@types/node": "^22.5.4",
|
|
188
|
+
"@types/yauzl": "^2.10.3",
|
|
189
|
+
"@typescript-eslint/eslint-plugin": "^8.5.0",
|
|
190
|
+
"@typescript-eslint/parser": "^8.5.0",
|
|
191
|
+
eslint: "^8.57.0",
|
|
192
|
+
jest: "^29.7.0",
|
|
193
|
+
"npm-run-all": "^4.1.5",
|
|
194
|
+
"ts-jest": "^29.2.5",
|
|
195
|
+
tsup: "^6.7.0",
|
|
196
|
+
typescript: "^5.6.2"
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
});
|
|
32
201
|
|
|
33
202
|
// src/core/logging/structured-logger.ts
|
|
34
203
|
function initializeLogger(config) {
|
|
@@ -565,6 +734,209 @@ var init_schema = __esm({
|
|
|
565
734
|
}
|
|
566
735
|
});
|
|
567
736
|
|
|
737
|
+
// src/core/config/loader.ts
|
|
738
|
+
function discoverConfigFile() {
|
|
739
|
+
for (const configPath of CONFIG_PATHS) {
|
|
740
|
+
if ((0, import_fs.existsSync)(configPath)) {
|
|
741
|
+
return configPath;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
function loadConfigFile(configPath) {
|
|
747
|
+
try {
|
|
748
|
+
const content = (0, import_fs.readFileSync)(configPath, "utf8");
|
|
749
|
+
const config = JSON.parse(content);
|
|
750
|
+
if (config.profiles && config.defaults?.profile) {
|
|
751
|
+
const activeProfile = config.profiles[config.defaults.profile];
|
|
752
|
+
if (activeProfile) {
|
|
753
|
+
return mergeConfigs(config.defaults, activeProfile);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return config.defaults || config;
|
|
757
|
+
} catch (error) {
|
|
758
|
+
console.warn(`Warning: Could not load config from ${configPath}: ${error.message}`);
|
|
759
|
+
return {};
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function resolveEnvVars(config) {
|
|
763
|
+
const resolved = JSON.parse(JSON.stringify(config));
|
|
764
|
+
if (process.env.AWS_ACCESS_KEY_ID) {
|
|
765
|
+
resolved.accessKey = process.env.AWS_ACCESS_KEY_ID;
|
|
766
|
+
}
|
|
767
|
+
if (process.env.AWS_SECRET_ACCESS_KEY) {
|
|
768
|
+
resolved.secretKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
769
|
+
}
|
|
770
|
+
if (process.env.AWS_SESSION_TOKEN) {
|
|
771
|
+
resolved.sessionToken = process.env.AWS_SESSION_TOKEN;
|
|
772
|
+
}
|
|
773
|
+
if (process.env.AWS_REGION) {
|
|
774
|
+
resolved.region = process.env.AWS_REGION;
|
|
775
|
+
}
|
|
776
|
+
if (process.env.AWS_PROFILE) {
|
|
777
|
+
resolved.profile = process.env.AWS_PROFILE;
|
|
778
|
+
}
|
|
779
|
+
if (process.env.SLACK_TOKEN || process.env.SLACK_CHANNEL) {
|
|
780
|
+
resolved.slack = {
|
|
781
|
+
...resolved.slack,
|
|
782
|
+
token: process.env.SLACK_TOKEN ?? resolved.slack?.token,
|
|
783
|
+
channel: process.env.SLACK_CHANNEL ?? resolved.slack?.channel,
|
|
784
|
+
enabled: true
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
return resolved;
|
|
788
|
+
}
|
|
789
|
+
function mergeConfigs(...configs) {
|
|
790
|
+
const result = {};
|
|
791
|
+
for (const config of configs) {
|
|
792
|
+
for (const [key, value] of Object.entries(config)) {
|
|
793
|
+
if (value === void 0 || value === null) {
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
797
|
+
result[key] = { ...result[key], ...value };
|
|
798
|
+
} else {
|
|
799
|
+
result[key] = value;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
function autoLoadConfig(cliOptions = {}) {
|
|
806
|
+
let config = { ...DEFAULT_CONFIG2 };
|
|
807
|
+
const configPath = cliOptions.configFile || discoverConfigFile();
|
|
808
|
+
if (configPath) {
|
|
809
|
+
const fileConfig = loadConfigFile(configPath);
|
|
810
|
+
config = mergeConfigs(config, fileConfig);
|
|
811
|
+
}
|
|
812
|
+
config = mergeConfigs(config, resolveEnvVars(config));
|
|
813
|
+
const cliConfig = mapCliOptionsToConfig(cliOptions);
|
|
814
|
+
config = mergeConfigs(config, cliConfig);
|
|
815
|
+
return config;
|
|
816
|
+
}
|
|
817
|
+
function mapCliOptionsToConfig(options) {
|
|
818
|
+
const config = {};
|
|
819
|
+
if (options.provider)
|
|
820
|
+
config.provider = options.provider;
|
|
821
|
+
if (options.profile)
|
|
822
|
+
config.profile = options.profile;
|
|
823
|
+
if (options.region)
|
|
824
|
+
config.region = options.region;
|
|
825
|
+
if (options.accessKey)
|
|
826
|
+
config.accessKey = options.accessKey;
|
|
827
|
+
if (options.secretKey)
|
|
828
|
+
config.secretKey = options.secretKey;
|
|
829
|
+
if (options.sessionToken)
|
|
830
|
+
config.sessionToken = options.sessionToken;
|
|
831
|
+
if (options.json || options.text || options.summary) {
|
|
832
|
+
config.output = config.output || {};
|
|
833
|
+
if (options.json)
|
|
834
|
+
config.output.format = "json";
|
|
835
|
+
if (options.text)
|
|
836
|
+
config.output.format = "text";
|
|
837
|
+
if (options.summary)
|
|
838
|
+
config.output.summary = true;
|
|
839
|
+
}
|
|
840
|
+
if (options.delta !== void 0) {
|
|
841
|
+
config.output = config.output || {};
|
|
842
|
+
config.output.showDelta = options.delta;
|
|
843
|
+
}
|
|
844
|
+
if (options.deltaThreshold) {
|
|
845
|
+
config.output = config.output || {};
|
|
846
|
+
config.output.deltaThreshold = parseFloat(options.deltaThreshold);
|
|
847
|
+
}
|
|
848
|
+
if (options.quickWins !== void 0) {
|
|
849
|
+
config.output = config.output || {};
|
|
850
|
+
config.output.showQuickWins = options.quickWins;
|
|
851
|
+
}
|
|
852
|
+
if (options.quickWinsCount) {
|
|
853
|
+
config.output = config.output || {};
|
|
854
|
+
config.output.quickWinsCount = parseInt(options.quickWinsCount, 10);
|
|
855
|
+
}
|
|
856
|
+
if (options.cache !== void 0 || options.noCache !== void 0) {
|
|
857
|
+
config.cache = config.cache || {};
|
|
858
|
+
config.cache.enabled = options.cache === true || options.noCache !== true;
|
|
859
|
+
}
|
|
860
|
+
if (options.cacheTtl) {
|
|
861
|
+
config.cache = config.cache || {};
|
|
862
|
+
config.cache.ttl = options.cacheTtl;
|
|
863
|
+
}
|
|
864
|
+
if (options.cacheType) {
|
|
865
|
+
config.cache = config.cache || {};
|
|
866
|
+
config.cache.type = options.cacheType;
|
|
867
|
+
}
|
|
868
|
+
if (options.slackToken || options.slackChannel) {
|
|
869
|
+
config.slack = config.slack || {};
|
|
870
|
+
if (options.slackToken)
|
|
871
|
+
config.slack.token = options.slackToken;
|
|
872
|
+
if (options.slackChannel)
|
|
873
|
+
config.slack.channel = options.slackChannel;
|
|
874
|
+
config.slack.enabled = true;
|
|
875
|
+
}
|
|
876
|
+
if (options.logLevel || options.verbose || options.quiet) {
|
|
877
|
+
config.logging = config.logging || {};
|
|
878
|
+
if (options.logLevel)
|
|
879
|
+
config.logging.level = options.logLevel;
|
|
880
|
+
if (options.verbose)
|
|
881
|
+
config.logging.level = "debug";
|
|
882
|
+
if (options.quiet)
|
|
883
|
+
config.logging.level = "error";
|
|
884
|
+
}
|
|
885
|
+
return config;
|
|
886
|
+
}
|
|
887
|
+
var import_fs, import_path, import_os, CONFIG_PATHS, DEFAULT_CONFIG2;
|
|
888
|
+
var init_loader = __esm({
|
|
889
|
+
"src/core/config/loader.ts"() {
|
|
890
|
+
import_fs = require("fs");
|
|
891
|
+
import_path = require("path");
|
|
892
|
+
import_os = require("os");
|
|
893
|
+
CONFIG_PATHS = [
|
|
894
|
+
(0, import_path.join)(process.cwd(), "infra-cost.config.json"),
|
|
895
|
+
// Project-specific
|
|
896
|
+
(0, import_path.join)(process.cwd(), ".infra-cost.config.json"),
|
|
897
|
+
// Project-specific (hidden)
|
|
898
|
+
(0, import_path.join)(process.cwd(), ".infra-cost", "config.json"),
|
|
899
|
+
// Project directory
|
|
900
|
+
(0, import_path.join)((0, import_os.homedir)(), ".infra-cost", "config.json"),
|
|
901
|
+
// User global
|
|
902
|
+
(0, import_path.join)((0, import_os.homedir)(), ".config", "infra-cost", "config.json")
|
|
903
|
+
// XDG standard
|
|
904
|
+
];
|
|
905
|
+
DEFAULT_CONFIG2 = {
|
|
906
|
+
provider: "aws",
|
|
907
|
+
profile: "default",
|
|
908
|
+
region: "us-east-1",
|
|
909
|
+
output: {
|
|
910
|
+
format: "fancy",
|
|
911
|
+
summary: false,
|
|
912
|
+
showDelta: true,
|
|
913
|
+
// NEW: Show delta by default
|
|
914
|
+
showQuickWins: true,
|
|
915
|
+
// NEW: Show quick wins by default
|
|
916
|
+
deltaThreshold: 10,
|
|
917
|
+
quickWinsCount: 3
|
|
918
|
+
},
|
|
919
|
+
cache: {
|
|
920
|
+
enabled: true,
|
|
921
|
+
// NEW: Cache enabled by default
|
|
922
|
+
ttl: "4h",
|
|
923
|
+
type: "file"
|
|
924
|
+
},
|
|
925
|
+
logging: {
|
|
926
|
+
level: "info",
|
|
927
|
+
format: "pretty",
|
|
928
|
+
auditEnabled: false
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
__name(discoverConfigFile, "discoverConfigFile");
|
|
932
|
+
__name(loadConfigFile, "loadConfigFile");
|
|
933
|
+
__name(resolveEnvVars, "resolveEnvVars");
|
|
934
|
+
__name(mergeConfigs, "mergeConfigs");
|
|
935
|
+
__name(autoLoadConfig, "autoLoadConfig");
|
|
936
|
+
__name(mapCliOptionsToConfig, "mapCliOptionsToConfig");
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
|
|
568
940
|
// src/types/providers.ts
|
|
569
941
|
var CloudProviderAdapter, ResourceType;
|
|
570
942
|
var init_providers = __esm({
|
|
@@ -10208,366 +10580,669 @@ var init_multicloud = __esm({
|
|
|
10208
10580
|
}
|
|
10209
10581
|
});
|
|
10210
10582
|
|
|
10211
|
-
// src/
|
|
10212
|
-
|
|
10583
|
+
// src/api/utils.ts
|
|
10584
|
+
async function getProviderFromConfig() {
|
|
10585
|
+
const config = autoLoadConfig();
|
|
10586
|
+
const factory = new CloudProviderFactory();
|
|
10587
|
+
return factory.createProvider(config);
|
|
10588
|
+
}
|
|
10589
|
+
function getConfig2() {
|
|
10590
|
+
return autoLoadConfig();
|
|
10591
|
+
}
|
|
10592
|
+
var init_utils = __esm({
|
|
10593
|
+
"src/api/utils.ts"() {
|
|
10594
|
+
init_factory();
|
|
10595
|
+
init_loader();
|
|
10596
|
+
__name(getProviderFromConfig, "getProviderFromConfig");
|
|
10597
|
+
__name(getConfig2, "getConfig");
|
|
10598
|
+
}
|
|
10599
|
+
});
|
|
10213
10600
|
|
|
10214
|
-
//
|
|
10215
|
-
var
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
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
|
-
jest: "^29.7.0",
|
|
10361
|
-
"npm-run-all": "^4.1.5",
|
|
10362
|
-
"ts-jest": "^29.2.5",
|
|
10363
|
-
tsup: "^6.7.0",
|
|
10364
|
-
typescript: "^5.6.2"
|
|
10601
|
+
// src/api/routes/costs.ts
|
|
10602
|
+
var costs_exports2 = {};
|
|
10603
|
+
__export(costs_exports2, {
|
|
10604
|
+
default: () => costs_default
|
|
10605
|
+
});
|
|
10606
|
+
var import_express, router, costs_default;
|
|
10607
|
+
var init_costs2 = __esm({
|
|
10608
|
+
"src/api/routes/costs.ts"() {
|
|
10609
|
+
import_express = require("express");
|
|
10610
|
+
init_utils();
|
|
10611
|
+
init_server();
|
|
10612
|
+
router = (0, import_express.Router)();
|
|
10613
|
+
router.get("/", async (req, res) => {
|
|
10614
|
+
try {
|
|
10615
|
+
const config = getConfig2();
|
|
10616
|
+
const provider = await getProviderFromConfig();
|
|
10617
|
+
const today = /* @__PURE__ */ new Date();
|
|
10618
|
+
const todayStart = new Date(today.setHours(0, 0, 0, 0));
|
|
10619
|
+
const todayEnd = new Date(today.setHours(23, 59, 59, 999));
|
|
10620
|
+
const monthStart = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
10621
|
+
const monthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0);
|
|
10622
|
+
const [todayCosts, mtdCosts] = await Promise.all([
|
|
10623
|
+
provider.getCostBreakdown(todayStart, todayEnd, "SERVICE"),
|
|
10624
|
+
provider.getCostBreakdown(monthStart, monthEnd, "SERVICE")
|
|
10625
|
+
]);
|
|
10626
|
+
const serviceBreakdown = {};
|
|
10627
|
+
todayCosts.breakdown.forEach((item) => {
|
|
10628
|
+
serviceBreakdown[item.service] = item.cost;
|
|
10629
|
+
});
|
|
10630
|
+
const accountInfo = await provider.getAccountInfo();
|
|
10631
|
+
const response = {
|
|
10632
|
+
account: {
|
|
10633
|
+
id: accountInfo.accountId,
|
|
10634
|
+
name: accountInfo.accountAlias || accountInfo.accountId,
|
|
10635
|
+
provider: config.provider
|
|
10636
|
+
},
|
|
10637
|
+
costs: {
|
|
10638
|
+
today: {
|
|
10639
|
+
total: todayCosts.totalCost,
|
|
10640
|
+
currency: "USD",
|
|
10641
|
+
byService: serviceBreakdown
|
|
10642
|
+
},
|
|
10643
|
+
mtd: {
|
|
10644
|
+
total: mtdCosts.totalCost,
|
|
10645
|
+
projected: mtdCosts.totalCost * (30 / today.getDate())
|
|
10646
|
+
},
|
|
10647
|
+
delta: {
|
|
10648
|
+
vsYesterday: 0,
|
|
10649
|
+
// Would need historical data
|
|
10650
|
+
vsLastWeek: 0
|
|
10651
|
+
// Would need historical data
|
|
10652
|
+
}
|
|
10653
|
+
},
|
|
10654
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10655
|
+
};
|
|
10656
|
+
res.json(createApiResponse(response));
|
|
10657
|
+
} catch (error) {
|
|
10658
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10659
|
+
}
|
|
10660
|
+
});
|
|
10661
|
+
router.get("/services", async (req, res) => {
|
|
10662
|
+
try {
|
|
10663
|
+
const provider = await getProviderFromConfig();
|
|
10664
|
+
const { startDate, endDate } = req.query;
|
|
10665
|
+
const start = startDate ? new Date(startDate) : /* @__PURE__ */ new Date();
|
|
10666
|
+
const end = endDate ? new Date(endDate) : /* @__PURE__ */ new Date();
|
|
10667
|
+
const breakdown = await provider.getCostBreakdown(start, end, "SERVICE");
|
|
10668
|
+
const services = breakdown.breakdown.map((item) => ({
|
|
10669
|
+
service: item.service,
|
|
10670
|
+
cost: item.cost,
|
|
10671
|
+
percentage: item.cost / breakdown.totalCost * 100
|
|
10672
|
+
}));
|
|
10673
|
+
res.json(
|
|
10674
|
+
createApiResponse({
|
|
10675
|
+
total: breakdown.totalCost,
|
|
10676
|
+
services,
|
|
10677
|
+
period: {
|
|
10678
|
+
start: start.toISOString(),
|
|
10679
|
+
end: end.toISOString()
|
|
10680
|
+
}
|
|
10681
|
+
})
|
|
10682
|
+
);
|
|
10683
|
+
} catch (error) {
|
|
10684
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10685
|
+
}
|
|
10686
|
+
});
|
|
10687
|
+
router.get("/daily", async (req, res) => {
|
|
10688
|
+
try {
|
|
10689
|
+
const provider = await getProviderFromConfig();
|
|
10690
|
+
const { days = 30 } = req.query;
|
|
10691
|
+
const daysNum = parseInt(days, 10);
|
|
10692
|
+
const end = /* @__PURE__ */ new Date();
|
|
10693
|
+
const start = /* @__PURE__ */ new Date();
|
|
10694
|
+
start.setDate(start.getDate() - daysNum);
|
|
10695
|
+
const breakdown = await provider.getCostBreakdown(start, end, "DAILY");
|
|
10696
|
+
const dailyCosts = breakdown.breakdown.map((item) => ({
|
|
10697
|
+
date: item.date,
|
|
10698
|
+
cost: item.cost
|
|
10699
|
+
}));
|
|
10700
|
+
res.json(
|
|
10701
|
+
createApiResponse({
|
|
10702
|
+
total: breakdown.totalCost,
|
|
10703
|
+
daily: dailyCosts,
|
|
10704
|
+
period: {
|
|
10705
|
+
start: start.toISOString(),
|
|
10706
|
+
end: end.toISOString(),
|
|
10707
|
+
days: daysNum
|
|
10708
|
+
}
|
|
10709
|
+
})
|
|
10710
|
+
);
|
|
10711
|
+
} catch (error) {
|
|
10712
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10713
|
+
}
|
|
10714
|
+
});
|
|
10715
|
+
router.get("/trends", async (req, res) => {
|
|
10716
|
+
try {
|
|
10717
|
+
const provider = await getProviderFromConfig();
|
|
10718
|
+
const end = /* @__PURE__ */ new Date();
|
|
10719
|
+
const start = /* @__PURE__ */ new Date();
|
|
10720
|
+
start.setMonth(start.getMonth() - 3);
|
|
10721
|
+
const breakdown = await provider.getCostBreakdown(start, end, "MONTHLY");
|
|
10722
|
+
const trends = breakdown.breakdown.map((item, index, arr) => {
|
|
10723
|
+
const previousMonth = index > 0 ? arr[index - 1].cost : null;
|
|
10724
|
+
const changePercent = previousMonth ? (item.cost - previousMonth) / previousMonth * 100 : 0;
|
|
10725
|
+
return {
|
|
10726
|
+
month: item.date,
|
|
10727
|
+
cost: item.cost,
|
|
10728
|
+
change: item.cost - (previousMonth || 0),
|
|
10729
|
+
changePercent
|
|
10730
|
+
};
|
|
10731
|
+
});
|
|
10732
|
+
res.json(
|
|
10733
|
+
createApiResponse({
|
|
10734
|
+
trends,
|
|
10735
|
+
average: breakdown.totalCost / breakdown.breakdown.length,
|
|
10736
|
+
period: {
|
|
10737
|
+
start: start.toISOString(),
|
|
10738
|
+
end: end.toISOString()
|
|
10739
|
+
}
|
|
10740
|
+
})
|
|
10741
|
+
);
|
|
10742
|
+
} catch (error) {
|
|
10743
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10744
|
+
}
|
|
10745
|
+
});
|
|
10746
|
+
costs_default = router;
|
|
10365
10747
|
}
|
|
10366
|
-
};
|
|
10748
|
+
});
|
|
10367
10749
|
|
|
10368
|
-
// src/
|
|
10369
|
-
|
|
10750
|
+
// src/api/routes/inventory.ts
|
|
10751
|
+
var inventory_exports2 = {};
|
|
10752
|
+
__export(inventory_exports2, {
|
|
10753
|
+
default: () => inventory_default
|
|
10754
|
+
});
|
|
10755
|
+
var import_express2, router2, inventory_default;
|
|
10756
|
+
var init_inventory6 = __esm({
|
|
10757
|
+
"src/api/routes/inventory.ts"() {
|
|
10758
|
+
import_express2 = require("express");
|
|
10759
|
+
init_utils();
|
|
10760
|
+
init_server();
|
|
10761
|
+
router2 = (0, import_express2.Router)();
|
|
10762
|
+
router2.get("/", async (req, res) => {
|
|
10763
|
+
try {
|
|
10764
|
+
const provider = await getProviderFromConfig();
|
|
10765
|
+
const inventory = await provider.getResourceInventory();
|
|
10766
|
+
const summary = {
|
|
10767
|
+
totalResources: inventory.resources.length,
|
|
10768
|
+
byType: inventory.resources.reduce((acc, resource) => {
|
|
10769
|
+
acc[resource.type] = (acc[resource.type] || 0) + 1;
|
|
10770
|
+
return acc;
|
|
10771
|
+
}, {}),
|
|
10772
|
+
byRegion: inventory.resources.reduce((acc, resource) => {
|
|
10773
|
+
const region = resource.region || "global";
|
|
10774
|
+
acc[region] = (acc[region] || 0) + 1;
|
|
10775
|
+
return acc;
|
|
10776
|
+
}, {})
|
|
10777
|
+
};
|
|
10778
|
+
res.json(
|
|
10779
|
+
createApiResponse({
|
|
10780
|
+
summary,
|
|
10781
|
+
resources: inventory.resources
|
|
10782
|
+
})
|
|
10783
|
+
);
|
|
10784
|
+
} catch (error) {
|
|
10785
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10786
|
+
}
|
|
10787
|
+
});
|
|
10788
|
+
inventory_default = router2;
|
|
10789
|
+
}
|
|
10790
|
+
});
|
|
10370
10791
|
|
|
10371
|
-
// src/
|
|
10372
|
-
|
|
10792
|
+
// src/api/routes/optimization.ts
|
|
10793
|
+
var optimization_exports = {};
|
|
10794
|
+
__export(optimization_exports, {
|
|
10795
|
+
default: () => optimization_default
|
|
10796
|
+
});
|
|
10797
|
+
var import_express3, router3, optimization_default;
|
|
10798
|
+
var init_optimization = __esm({
|
|
10799
|
+
"src/api/routes/optimization.ts"() {
|
|
10800
|
+
import_express3 = require("express");
|
|
10801
|
+
init_utils();
|
|
10802
|
+
init_server();
|
|
10803
|
+
router3 = (0, import_express3.Router)();
|
|
10804
|
+
router3.get("/", async (req, res) => {
|
|
10805
|
+
try {
|
|
10806
|
+
const provider = await getProviderFromConfig();
|
|
10807
|
+
const recommendations = await provider.getOptimizationRecommendations();
|
|
10808
|
+
const summary = {
|
|
10809
|
+
totalSavings: recommendations.reduce((sum, rec) => sum + (rec.estimatedMonthlySavings || 0), 0),
|
|
10810
|
+
recommendationCount: recommendations.length,
|
|
10811
|
+
byCategory: recommendations.reduce((acc, rec) => {
|
|
10812
|
+
const category = rec.category || "other";
|
|
10813
|
+
acc[category] = (acc[category] || 0) + 1;
|
|
10814
|
+
return acc;
|
|
10815
|
+
}, {})
|
|
10816
|
+
};
|
|
10817
|
+
res.json(
|
|
10818
|
+
createApiResponse({
|
|
10819
|
+
summary,
|
|
10820
|
+
recommendations
|
|
10821
|
+
})
|
|
10822
|
+
);
|
|
10823
|
+
} catch (error) {
|
|
10824
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10825
|
+
}
|
|
10826
|
+
});
|
|
10827
|
+
optimization_default = router3;
|
|
10828
|
+
}
|
|
10829
|
+
});
|
|
10373
10830
|
|
|
10374
|
-
// src/
|
|
10375
|
-
var
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
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
|
-
logging: {
|
|
10411
|
-
level: "info",
|
|
10412
|
-
format: "pretty",
|
|
10413
|
-
auditEnabled: false
|
|
10414
|
-
}
|
|
10415
|
-
};
|
|
10416
|
-
function discoverConfigFile() {
|
|
10417
|
-
for (const configPath of CONFIG_PATHS) {
|
|
10418
|
-
if ((0, import_fs.existsSync)(configPath)) {
|
|
10419
|
-
return configPath;
|
|
10420
|
-
}
|
|
10421
|
-
}
|
|
10422
|
-
return null;
|
|
10423
|
-
}
|
|
10424
|
-
__name(discoverConfigFile, "discoverConfigFile");
|
|
10425
|
-
function loadConfigFile(configPath) {
|
|
10426
|
-
try {
|
|
10427
|
-
const content = (0, import_fs.readFileSync)(configPath, "utf8");
|
|
10428
|
-
const config = JSON.parse(content);
|
|
10429
|
-
if (config.profiles && config.defaults?.profile) {
|
|
10430
|
-
const activeProfile = config.profiles[config.defaults.profile];
|
|
10431
|
-
if (activeProfile) {
|
|
10432
|
-
return mergeConfigs(config.defaults, activeProfile);
|
|
10831
|
+
// src/api/routes/chargeback.ts
|
|
10832
|
+
var chargeback_exports = {};
|
|
10833
|
+
__export(chargeback_exports, {
|
|
10834
|
+
default: () => chargeback_default
|
|
10835
|
+
});
|
|
10836
|
+
var import_express4, router4, chargeback_default;
|
|
10837
|
+
var init_chargeback = __esm({
|
|
10838
|
+
"src/api/routes/chargeback.ts"() {
|
|
10839
|
+
import_express4 = require("express");
|
|
10840
|
+
init_utils();
|
|
10841
|
+
init_server();
|
|
10842
|
+
router4 = (0, import_express4.Router)();
|
|
10843
|
+
router4.get("/", async (req, res) => {
|
|
10844
|
+
try {
|
|
10845
|
+
const provider = await getProviderFromConfig();
|
|
10846
|
+
const { groupBy = "tag" } = req.query;
|
|
10847
|
+
const now = /* @__PURE__ */ new Date();
|
|
10848
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
10849
|
+
const breakdown = await provider.getCostBreakdown(
|
|
10850
|
+
monthStart,
|
|
10851
|
+
now,
|
|
10852
|
+
groupBy === "tag" ? "TAG" : "SERVICE"
|
|
10853
|
+
);
|
|
10854
|
+
res.json(
|
|
10855
|
+
createApiResponse({
|
|
10856
|
+
total: breakdown.totalCost,
|
|
10857
|
+
breakdown: breakdown.breakdown,
|
|
10858
|
+
period: {
|
|
10859
|
+
start: monthStart.toISOString(),
|
|
10860
|
+
end: now.toISOString()
|
|
10861
|
+
},
|
|
10862
|
+
groupBy
|
|
10863
|
+
})
|
|
10864
|
+
);
|
|
10865
|
+
} catch (error) {
|
|
10866
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10433
10867
|
}
|
|
10434
|
-
}
|
|
10435
|
-
|
|
10436
|
-
} catch (error) {
|
|
10437
|
-
console.warn(`Warning: Could not load config from ${configPath}: ${error.message}`);
|
|
10438
|
-
return {};
|
|
10439
|
-
}
|
|
10440
|
-
}
|
|
10441
|
-
__name(loadConfigFile, "loadConfigFile");
|
|
10442
|
-
function resolveEnvVars(config) {
|
|
10443
|
-
const resolved = JSON.parse(JSON.stringify(config));
|
|
10444
|
-
if (process.env.AWS_ACCESS_KEY_ID) {
|
|
10445
|
-
resolved.accessKey = process.env.AWS_ACCESS_KEY_ID;
|
|
10446
|
-
}
|
|
10447
|
-
if (process.env.AWS_SECRET_ACCESS_KEY) {
|
|
10448
|
-
resolved.secretKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
10449
|
-
}
|
|
10450
|
-
if (process.env.AWS_SESSION_TOKEN) {
|
|
10451
|
-
resolved.sessionToken = process.env.AWS_SESSION_TOKEN;
|
|
10452
|
-
}
|
|
10453
|
-
if (process.env.AWS_REGION) {
|
|
10454
|
-
resolved.region = process.env.AWS_REGION;
|
|
10455
|
-
}
|
|
10456
|
-
if (process.env.AWS_PROFILE) {
|
|
10457
|
-
resolved.profile = process.env.AWS_PROFILE;
|
|
10868
|
+
});
|
|
10869
|
+
chargeback_default = router4;
|
|
10458
10870
|
}
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10871
|
+
});
|
|
10872
|
+
|
|
10873
|
+
// src/api/routes/forecast.ts
|
|
10874
|
+
var forecast_exports = {};
|
|
10875
|
+
__export(forecast_exports, {
|
|
10876
|
+
default: () => forecast_default
|
|
10877
|
+
});
|
|
10878
|
+
var import_express5, router5, forecast_default;
|
|
10879
|
+
var init_forecast = __esm({
|
|
10880
|
+
"src/api/routes/forecast.ts"() {
|
|
10881
|
+
import_express5 = require("express");
|
|
10882
|
+
init_server();
|
|
10883
|
+
router5 = (0, import_express5.Router)();
|
|
10884
|
+
router5.get("/", async (req, res) => {
|
|
10885
|
+
try {
|
|
10886
|
+
const { days = 30 } = req.query;
|
|
10887
|
+
const daysNum = parseInt(days, 10);
|
|
10888
|
+
const currentDailyAverage = 150;
|
|
10889
|
+
const projectedDaily = currentDailyAverage * 1.05;
|
|
10890
|
+
const forecast = [];
|
|
10891
|
+
for (let i = 1; i <= daysNum; i++) {
|
|
10892
|
+
const date = /* @__PURE__ */ new Date();
|
|
10893
|
+
date.setDate(date.getDate() + i);
|
|
10894
|
+
forecast.push({
|
|
10895
|
+
date: date.toISOString().split("T")[0],
|
|
10896
|
+
projected: projectedDaily,
|
|
10897
|
+
confidence: Math.max(0.9 - i * 0.01, 0.5)
|
|
10898
|
+
// Decreasing confidence
|
|
10899
|
+
});
|
|
10900
|
+
}
|
|
10901
|
+
const totalProjected = projectedDaily * daysNum;
|
|
10902
|
+
res.json(
|
|
10903
|
+
createApiResponse({
|
|
10904
|
+
forecast,
|
|
10905
|
+
summary: {
|
|
10906
|
+
totalProjected,
|
|
10907
|
+
averageDaily: projectedDaily,
|
|
10908
|
+
period: {
|
|
10909
|
+
days: daysNum,
|
|
10910
|
+
start: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
10911
|
+
end: forecast[forecast.length - 1].date
|
|
10912
|
+
}
|
|
10913
|
+
},
|
|
10914
|
+
model: "linear-growth",
|
|
10915
|
+
confidence: 0.75
|
|
10916
|
+
})
|
|
10917
|
+
);
|
|
10918
|
+
} catch (error) {
|
|
10919
|
+
res.status(500).json(createErrorResponse("FORECAST_ERROR", error.message));
|
|
10920
|
+
}
|
|
10921
|
+
});
|
|
10922
|
+
forecast_default = router5;
|
|
10466
10923
|
}
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10924
|
+
});
|
|
10925
|
+
|
|
10926
|
+
// src/api/routes/accounts.ts
|
|
10927
|
+
var accounts_exports = {};
|
|
10928
|
+
__export(accounts_exports, {
|
|
10929
|
+
default: () => accounts_default
|
|
10930
|
+
});
|
|
10931
|
+
var import_express6, router6, accounts_default;
|
|
10932
|
+
var init_accounts = __esm({
|
|
10933
|
+
"src/api/routes/accounts.ts"() {
|
|
10934
|
+
import_express6 = require("express");
|
|
10935
|
+
init_utils();
|
|
10936
|
+
init_server();
|
|
10937
|
+
router6 = (0, import_express6.Router)();
|
|
10938
|
+
router6.get("/", async (req, res) => {
|
|
10939
|
+
try {
|
|
10940
|
+
const config = getConfig2();
|
|
10941
|
+
const provider = await getProviderFromConfig();
|
|
10942
|
+
const accountInfo = await provider.getAccountInfo();
|
|
10943
|
+
const accounts = [
|
|
10944
|
+
{
|
|
10945
|
+
id: accountInfo.accountId,
|
|
10946
|
+
name: accountInfo.accountAlias || accountInfo.accountId,
|
|
10947
|
+
provider: config.provider,
|
|
10948
|
+
region: accountInfo.region
|
|
10949
|
+
}
|
|
10950
|
+
];
|
|
10951
|
+
res.json(
|
|
10952
|
+
createApiResponse({
|
|
10953
|
+
accounts,
|
|
10954
|
+
count: accounts.length
|
|
10955
|
+
})
|
|
10956
|
+
);
|
|
10957
|
+
} catch (error) {
|
|
10958
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10476
10959
|
}
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10960
|
+
});
|
|
10961
|
+
router6.get("/:id/costs", async (req, res) => {
|
|
10962
|
+
try {
|
|
10963
|
+
const config = getConfig2();
|
|
10964
|
+
const provider = await getProviderFromConfig();
|
|
10965
|
+
const { id } = req.params;
|
|
10966
|
+
const accountInfo = await provider.getAccountInfo();
|
|
10967
|
+
if (accountInfo.accountId !== id) {
|
|
10968
|
+
return res.status(404).json(createErrorResponse("NOT_FOUND", `Account ${id} not found`));
|
|
10969
|
+
}
|
|
10970
|
+
const now = /* @__PURE__ */ new Date();
|
|
10971
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
10972
|
+
const costs = await provider.getCostBreakdown(monthStart, now, "SERVICE");
|
|
10973
|
+
res.json(
|
|
10974
|
+
createApiResponse({
|
|
10975
|
+
accountId: id,
|
|
10976
|
+
costs: {
|
|
10977
|
+
total: costs.totalCost,
|
|
10978
|
+
breakdown: costs.breakdown
|
|
10979
|
+
},
|
|
10980
|
+
period: {
|
|
10981
|
+
start: monthStart.toISOString(),
|
|
10982
|
+
end: now.toISOString()
|
|
10983
|
+
}
|
|
10984
|
+
})
|
|
10985
|
+
);
|
|
10986
|
+
} catch (error) {
|
|
10987
|
+
res.status(500).json(createErrorResponse("FETCH_ERROR", error.message));
|
|
10481
10988
|
}
|
|
10482
|
-
}
|
|
10989
|
+
});
|
|
10990
|
+
accounts_default = router6;
|
|
10483
10991
|
}
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10992
|
+
});
|
|
10993
|
+
|
|
10994
|
+
// src/api/routes/reports.ts
|
|
10995
|
+
var reports_exports2 = {};
|
|
10996
|
+
__export(reports_exports2, {
|
|
10997
|
+
default: () => reports_default
|
|
10998
|
+
});
|
|
10999
|
+
var import_express7, router7, reports_default;
|
|
11000
|
+
var init_reports2 = __esm({
|
|
11001
|
+
"src/api/routes/reports.ts"() {
|
|
11002
|
+
import_express7 = require("express");
|
|
11003
|
+
init_utils();
|
|
11004
|
+
init_server();
|
|
11005
|
+
router7 = (0, import_express7.Router)();
|
|
11006
|
+
router7.post("/generate", async (req, res) => {
|
|
11007
|
+
try {
|
|
11008
|
+
const {
|
|
11009
|
+
reportType = "summary",
|
|
11010
|
+
startDate,
|
|
11011
|
+
endDate,
|
|
11012
|
+
format = "json",
|
|
11013
|
+
groupBy = "service"
|
|
11014
|
+
} = req.body;
|
|
11015
|
+
const config = getConfig2();
|
|
11016
|
+
const provider = await getProviderFromConfig();
|
|
11017
|
+
const start = startDate ? new Date(startDate) : /* @__PURE__ */ new Date();
|
|
11018
|
+
const end = endDate ? new Date(endDate) : /* @__PURE__ */ new Date();
|
|
11019
|
+
let groupByType = "SERVICE";
|
|
11020
|
+
if (groupBy === "tag")
|
|
11021
|
+
groupByType = "TAG";
|
|
11022
|
+
else if (groupBy === "daily")
|
|
11023
|
+
groupByType = "DAILY";
|
|
11024
|
+
else if (groupBy === "monthly")
|
|
11025
|
+
groupByType = "MONTHLY";
|
|
11026
|
+
const breakdown = await provider.getCostBreakdown(start, end, groupByType);
|
|
11027
|
+
const report = {
|
|
11028
|
+
reportType,
|
|
11029
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11030
|
+
period: {
|
|
11031
|
+
start: start.toISOString(),
|
|
11032
|
+
end: end.toISOString()
|
|
11033
|
+
},
|
|
11034
|
+
data: {
|
|
11035
|
+
total: breakdown.totalCost,
|
|
11036
|
+
breakdown: breakdown.breakdown,
|
|
11037
|
+
groupBy
|
|
11038
|
+
},
|
|
11039
|
+
format
|
|
11040
|
+
};
|
|
11041
|
+
res.json(createApiResponse(report));
|
|
11042
|
+
} catch (error) {
|
|
11043
|
+
res.status(500).json(createErrorResponse("REPORT_ERROR", error.message));
|
|
11044
|
+
}
|
|
11045
|
+
});
|
|
11046
|
+
reports_default = router7;
|
|
10493
11047
|
}
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
11048
|
+
});
|
|
11049
|
+
|
|
11050
|
+
// src/api/server.ts
|
|
11051
|
+
function createApiResponse(data) {
|
|
11052
|
+
return {
|
|
11053
|
+
status: "success",
|
|
11054
|
+
data,
|
|
11055
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11056
|
+
};
|
|
10498
11057
|
}
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
config.profile = options.profile;
|
|
10506
|
-
if (options.region)
|
|
10507
|
-
config.region = options.region;
|
|
10508
|
-
if (options.accessKey)
|
|
10509
|
-
config.accessKey = options.accessKey;
|
|
10510
|
-
if (options.secretKey)
|
|
10511
|
-
config.secretKey = options.secretKey;
|
|
10512
|
-
if (options.sessionToken)
|
|
10513
|
-
config.sessionToken = options.sessionToken;
|
|
10514
|
-
if (options.json || options.text || options.summary) {
|
|
10515
|
-
config.output = config.output || {};
|
|
10516
|
-
if (options.json)
|
|
10517
|
-
config.output.format = "json";
|
|
10518
|
-
if (options.text)
|
|
10519
|
-
config.output.format = "text";
|
|
10520
|
-
if (options.summary)
|
|
10521
|
-
config.output.summary = true;
|
|
10522
|
-
}
|
|
10523
|
-
if (options.delta !== void 0) {
|
|
10524
|
-
config.output = config.output || {};
|
|
10525
|
-
config.output.showDelta = options.delta;
|
|
10526
|
-
}
|
|
10527
|
-
if (options.deltaThreshold) {
|
|
10528
|
-
config.output = config.output || {};
|
|
10529
|
-
config.output.deltaThreshold = parseFloat(options.deltaThreshold);
|
|
10530
|
-
}
|
|
10531
|
-
if (options.quickWins !== void 0) {
|
|
10532
|
-
config.output = config.output || {};
|
|
10533
|
-
config.output.showQuickWins = options.quickWins;
|
|
10534
|
-
}
|
|
10535
|
-
if (options.quickWinsCount) {
|
|
10536
|
-
config.output = config.output || {};
|
|
10537
|
-
config.output.quickWinsCount = parseInt(options.quickWinsCount, 10);
|
|
10538
|
-
}
|
|
10539
|
-
if (options.cache !== void 0 || options.noCache !== void 0) {
|
|
10540
|
-
config.cache = config.cache || {};
|
|
10541
|
-
config.cache.enabled = options.cache === true || options.noCache !== true;
|
|
10542
|
-
}
|
|
10543
|
-
if (options.cacheTtl) {
|
|
10544
|
-
config.cache = config.cache || {};
|
|
10545
|
-
config.cache.ttl = options.cacheTtl;
|
|
10546
|
-
}
|
|
10547
|
-
if (options.cacheType) {
|
|
10548
|
-
config.cache = config.cache || {};
|
|
10549
|
-
config.cache.type = options.cacheType;
|
|
10550
|
-
}
|
|
10551
|
-
if (options.slackToken || options.slackChannel) {
|
|
10552
|
-
config.slack = config.slack || {};
|
|
10553
|
-
if (options.slackToken)
|
|
10554
|
-
config.slack.token = options.slackToken;
|
|
10555
|
-
if (options.slackChannel)
|
|
10556
|
-
config.slack.channel = options.slackChannel;
|
|
10557
|
-
config.slack.enabled = true;
|
|
10558
|
-
}
|
|
10559
|
-
if (options.logLevel || options.verbose || options.quiet) {
|
|
10560
|
-
config.logging = config.logging || {};
|
|
10561
|
-
if (options.logLevel)
|
|
10562
|
-
config.logging.level = options.logLevel;
|
|
10563
|
-
if (options.verbose)
|
|
10564
|
-
config.logging.level = "debug";
|
|
10565
|
-
if (options.quiet)
|
|
10566
|
-
config.logging.level = "error";
|
|
10567
|
-
}
|
|
10568
|
-
return config;
|
|
11058
|
+
function createErrorResponse(code, message) {
|
|
11059
|
+
return {
|
|
11060
|
+
status: "error",
|
|
11061
|
+
error: { code, message },
|
|
11062
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11063
|
+
};
|
|
10569
11064
|
}
|
|
10570
|
-
|
|
11065
|
+
var import_express8, import_cors, import_express_rate_limit, import_helmet, cache, ApiServer;
|
|
11066
|
+
var init_server = __esm({
|
|
11067
|
+
"src/api/server.ts"() {
|
|
11068
|
+
import_express8 = __toESM(require("express"));
|
|
11069
|
+
import_cors = __toESM(require("cors"));
|
|
11070
|
+
import_express_rate_limit = __toESM(require("express-rate-limit"));
|
|
11071
|
+
import_helmet = __toESM(require("helmet"));
|
|
11072
|
+
cache = /* @__PURE__ */ new Map();
|
|
11073
|
+
ApiServer = class {
|
|
11074
|
+
constructor(config) {
|
|
11075
|
+
this.config = config;
|
|
11076
|
+
this.app = (0, import_express8.default)();
|
|
11077
|
+
this.setupMiddleware();
|
|
11078
|
+
this.setupRoutes();
|
|
11079
|
+
}
|
|
11080
|
+
setupMiddleware() {
|
|
11081
|
+
this.app.use((0, import_helmet.default)());
|
|
11082
|
+
this.app.use(import_express8.default.json());
|
|
11083
|
+
if (this.config.cors.enabled) {
|
|
11084
|
+
this.app.use(
|
|
11085
|
+
(0, import_cors.default)({
|
|
11086
|
+
origin: this.config.cors.origins || "*",
|
|
11087
|
+
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
11088
|
+
credentials: true
|
|
11089
|
+
})
|
|
11090
|
+
);
|
|
11091
|
+
}
|
|
11092
|
+
if (this.config.rateLimit.enabled) {
|
|
11093
|
+
const limiter = (0, import_express_rate_limit.default)({
|
|
11094
|
+
windowMs: this.config.rateLimit.windowMs,
|
|
11095
|
+
max: this.config.rateLimit.max,
|
|
11096
|
+
message: {
|
|
11097
|
+
status: "error",
|
|
11098
|
+
error: {
|
|
11099
|
+
code: "RATE_LIMIT_EXCEEDED",
|
|
11100
|
+
message: "Too many requests, please try again later."
|
|
11101
|
+
},
|
|
11102
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11103
|
+
}
|
|
11104
|
+
});
|
|
11105
|
+
this.app.use("/api/", limiter);
|
|
11106
|
+
}
|
|
11107
|
+
this.app.use("/api/", this.authMiddleware.bind(this));
|
|
11108
|
+
if (this.config.cache.enabled) {
|
|
11109
|
+
this.app.use("/api/", this.cacheMiddleware.bind(this));
|
|
11110
|
+
}
|
|
11111
|
+
}
|
|
11112
|
+
authMiddleware(req, res, next) {
|
|
11113
|
+
if (req.path === "/api/v1/health") {
|
|
11114
|
+
return next();
|
|
11115
|
+
}
|
|
11116
|
+
if (this.config.auth.type === "none") {
|
|
11117
|
+
return next();
|
|
11118
|
+
}
|
|
11119
|
+
if (this.config.auth.type === "api-key") {
|
|
11120
|
+
const apiKey = req.headers["x-api-key"] || req.query.apiKey;
|
|
11121
|
+
if (!apiKey || !this.config.auth.apiKeys?.includes(apiKey)) {
|
|
11122
|
+
return res.status(401).json({
|
|
11123
|
+
status: "error",
|
|
11124
|
+
error: {
|
|
11125
|
+
code: "UNAUTHORIZED",
|
|
11126
|
+
message: "Invalid or missing API key"
|
|
11127
|
+
},
|
|
11128
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11129
|
+
});
|
|
11130
|
+
}
|
|
11131
|
+
}
|
|
11132
|
+
next();
|
|
11133
|
+
}
|
|
11134
|
+
cacheMiddleware(req, res, next) {
|
|
11135
|
+
if (req.method !== "GET") {
|
|
11136
|
+
return next();
|
|
11137
|
+
}
|
|
11138
|
+
const key = `${req.method}:${req.path}:${JSON.stringify(req.query)}`;
|
|
11139
|
+
const cached = cache.get(key);
|
|
11140
|
+
if (cached && cached.expiry > Date.now()) {
|
|
11141
|
+
return res.json(cached.data);
|
|
11142
|
+
}
|
|
11143
|
+
const originalJson = res.json.bind(res);
|
|
11144
|
+
res.json = (body) => {
|
|
11145
|
+
if (res.statusCode === 200) {
|
|
11146
|
+
cache.set(key, {
|
|
11147
|
+
data: body,
|
|
11148
|
+
expiry: Date.now() + this.config.cache.ttl * 1e3
|
|
11149
|
+
});
|
|
11150
|
+
}
|
|
11151
|
+
return originalJson(body);
|
|
11152
|
+
};
|
|
11153
|
+
next();
|
|
11154
|
+
}
|
|
11155
|
+
setupRoutes() {
|
|
11156
|
+
this.app.get("/api/v1/health", (_req, res) => {
|
|
11157
|
+
res.json({
|
|
11158
|
+
status: "success",
|
|
11159
|
+
data: {
|
|
11160
|
+
healthy: true,
|
|
11161
|
+
version: require_package().version,
|
|
11162
|
+
uptime: process.uptime()
|
|
11163
|
+
},
|
|
11164
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11165
|
+
});
|
|
11166
|
+
});
|
|
11167
|
+
this.app.use("/api/v1/costs", (init_costs2(), __toCommonJS(costs_exports2)).default);
|
|
11168
|
+
this.app.use("/api/v1/inventory", (init_inventory6(), __toCommonJS(inventory_exports2)).default);
|
|
11169
|
+
this.app.use("/api/v1/optimization", (init_optimization(), __toCommonJS(optimization_exports)).default);
|
|
11170
|
+
this.app.use("/api/v1/chargeback", (init_chargeback(), __toCommonJS(chargeback_exports)).default);
|
|
11171
|
+
this.app.use("/api/v1/forecast", (init_forecast(), __toCommonJS(forecast_exports)).default);
|
|
11172
|
+
this.app.use("/api/v1/accounts", (init_accounts(), __toCommonJS(accounts_exports)).default);
|
|
11173
|
+
this.app.use("/api/v1/reports", (init_reports2(), __toCommonJS(reports_exports2)).default);
|
|
11174
|
+
this.app.use((_req, res) => {
|
|
11175
|
+
res.status(404).json({
|
|
11176
|
+
status: "error",
|
|
11177
|
+
error: {
|
|
11178
|
+
code: "NOT_FOUND",
|
|
11179
|
+
message: "API endpoint not found"
|
|
11180
|
+
},
|
|
11181
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11182
|
+
});
|
|
11183
|
+
});
|
|
11184
|
+
this.app.use((err, _req, res, _next) => {
|
|
11185
|
+
console.error("API Error:", err);
|
|
11186
|
+
res.status(500).json({
|
|
11187
|
+
status: "error",
|
|
11188
|
+
error: {
|
|
11189
|
+
code: "INTERNAL_ERROR",
|
|
11190
|
+
message: err.message || "Internal server error"
|
|
11191
|
+
},
|
|
11192
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11193
|
+
});
|
|
11194
|
+
});
|
|
11195
|
+
}
|
|
11196
|
+
async start() {
|
|
11197
|
+
return new Promise((resolve2, reject) => {
|
|
11198
|
+
try {
|
|
11199
|
+
this.server = this.app.listen(this.config.port, this.config.host, () => {
|
|
11200
|
+
console.log(
|
|
11201
|
+
`\u2705 API Server running at http://${this.config.host}:${this.config.port}`
|
|
11202
|
+
);
|
|
11203
|
+
console.log(`\u{1F4D6} API Documentation: http://${this.config.host}:${this.config.port}/api/docs`);
|
|
11204
|
+
resolve2();
|
|
11205
|
+
});
|
|
11206
|
+
this.server.on("error", reject);
|
|
11207
|
+
} catch (error) {
|
|
11208
|
+
reject(error);
|
|
11209
|
+
}
|
|
11210
|
+
});
|
|
11211
|
+
}
|
|
11212
|
+
async stop() {
|
|
11213
|
+
return new Promise((resolve2, reject) => {
|
|
11214
|
+
if (!this.server) {
|
|
11215
|
+
resolve2();
|
|
11216
|
+
return;
|
|
11217
|
+
}
|
|
11218
|
+
this.server.close((err) => {
|
|
11219
|
+
if (err) {
|
|
11220
|
+
reject(err);
|
|
11221
|
+
} else {
|
|
11222
|
+
console.log("API Server stopped");
|
|
11223
|
+
resolve2();
|
|
11224
|
+
}
|
|
11225
|
+
});
|
|
11226
|
+
});
|
|
11227
|
+
}
|
|
11228
|
+
getApp() {
|
|
11229
|
+
return this.app;
|
|
11230
|
+
}
|
|
11231
|
+
};
|
|
11232
|
+
__name(ApiServer, "ApiServer");
|
|
11233
|
+
__name(createApiResponse, "createApiResponse");
|
|
11234
|
+
__name(createErrorResponse, "createErrorResponse");
|
|
11235
|
+
}
|
|
11236
|
+
});
|
|
11237
|
+
|
|
11238
|
+
// src/cli/index.ts
|
|
11239
|
+
var import_commander = require("commander");
|
|
11240
|
+
var import_package = __toESM(require_package());
|
|
11241
|
+
init_logging();
|
|
11242
|
+
|
|
11243
|
+
// src/core/config/index.ts
|
|
11244
|
+
init_schema();
|
|
11245
|
+
init_loader();
|
|
10571
11246
|
|
|
10572
11247
|
// src/core/config/discovery.ts
|
|
10573
11248
|
var import_chalk2 = __toESM(require("chalk"));
|
|
@@ -11920,8 +12595,8 @@ __name(registerExportCommands, "registerExportCommands");
|
|
|
11920
12595
|
function registerOrganizationsCommands(program) {
|
|
11921
12596
|
const orgs = program.command("organizations").alias("orgs").description("AWS Organizations multi-account management");
|
|
11922
12597
|
orgs.command("list").description("List all accounts in the organization").option("--include-inactive", "Include suspended/closed accounts").action(async (options, command) => {
|
|
11923
|
-
const { handleList:
|
|
11924
|
-
await
|
|
12598
|
+
const { handleList: handleList4 } = await Promise.resolve().then(() => (init_list(), list_exports));
|
|
12599
|
+
await handleList4(options, command);
|
|
11925
12600
|
});
|
|
11926
12601
|
orgs.command("summary").description("Multi-account cost summary").option("--group-by <field>", "Group by (account, ou, tag)", "account").action(async (options, command) => {
|
|
11927
12602
|
const { handleSummary: handleSummary2 } = await Promise.resolve().then(() => (init_summary(), summary_exports));
|
|
@@ -13115,6 +13790,976 @@ function registerSchedulerCommands(program) {
|
|
|
13115
13790
|
}
|
|
13116
13791
|
__name(registerSchedulerCommands, "registerSchedulerCommands");
|
|
13117
13792
|
|
|
13793
|
+
// src/cli/commands/rbac/index.ts
|
|
13794
|
+
var import_chalk19 = __toESM(require("chalk"));
|
|
13795
|
+
|
|
13796
|
+
// src/core/rbac.ts
|
|
13797
|
+
var import_fs6 = require("fs");
|
|
13798
|
+
var import_path4 = require("path");
|
|
13799
|
+
var import_os4 = require("os");
|
|
13800
|
+
var CONFIG_DIR2 = (0, import_path4.join)((0, import_os4.homedir)(), ".infra-cost");
|
|
13801
|
+
var RBAC_CONFIG_FILE = (0, import_path4.join)(CONFIG_DIR2, "rbac.json");
|
|
13802
|
+
var DEFAULT_ROLES = {
|
|
13803
|
+
admin: {
|
|
13804
|
+
name: "admin",
|
|
13805
|
+
description: "Full access to all features",
|
|
13806
|
+
permissions: ["*"]
|
|
13807
|
+
},
|
|
13808
|
+
finance: {
|
|
13809
|
+
name: "finance",
|
|
13810
|
+
description: "Cost data and reports only",
|
|
13811
|
+
permissions: [
|
|
13812
|
+
"costs:read",
|
|
13813
|
+
"costs:read:*",
|
|
13814
|
+
"chargeback:read",
|
|
13815
|
+
"chargeback:read:*",
|
|
13816
|
+
"reports:generate",
|
|
13817
|
+
"budgets:read",
|
|
13818
|
+
"budgets:read:*",
|
|
13819
|
+
"export:read"
|
|
13820
|
+
]
|
|
13821
|
+
},
|
|
13822
|
+
developer: {
|
|
13823
|
+
name: "developer",
|
|
13824
|
+
description: "View costs for assigned resources",
|
|
13825
|
+
permissions: [
|
|
13826
|
+
"costs:read:own",
|
|
13827
|
+
"costs:read:team:*",
|
|
13828
|
+
"inventory:read:own",
|
|
13829
|
+
"inventory:read:team:*",
|
|
13830
|
+
"optimization:read:own",
|
|
13831
|
+
"optimization:read:team:*"
|
|
13832
|
+
]
|
|
13833
|
+
},
|
|
13834
|
+
viewer: {
|
|
13835
|
+
name: "viewer",
|
|
13836
|
+
description: "Read-only access to summaries",
|
|
13837
|
+
permissions: [
|
|
13838
|
+
"costs:read:summary",
|
|
13839
|
+
"budgets:read"
|
|
13840
|
+
]
|
|
13841
|
+
}
|
|
13842
|
+
};
|
|
13843
|
+
function loadRBACConfig() {
|
|
13844
|
+
if (!(0, import_fs6.existsSync)(RBAC_CONFIG_FILE)) {
|
|
13845
|
+
return {
|
|
13846
|
+
roles: DEFAULT_ROLES,
|
|
13847
|
+
userRoles: []
|
|
13848
|
+
};
|
|
13849
|
+
}
|
|
13850
|
+
try {
|
|
13851
|
+
const data = (0, import_fs6.readFileSync)(RBAC_CONFIG_FILE, "utf-8");
|
|
13852
|
+
const config = JSON.parse(data);
|
|
13853
|
+
return {
|
|
13854
|
+
...config,
|
|
13855
|
+
roles: { ...DEFAULT_ROLES, ...config.roles }
|
|
13856
|
+
};
|
|
13857
|
+
} catch (error) {
|
|
13858
|
+
console.error("Error loading RBAC config:", error);
|
|
13859
|
+
return {
|
|
13860
|
+
roles: DEFAULT_ROLES,
|
|
13861
|
+
userRoles: []
|
|
13862
|
+
};
|
|
13863
|
+
}
|
|
13864
|
+
}
|
|
13865
|
+
__name(loadRBACConfig, "loadRBACConfig");
|
|
13866
|
+
function saveRBACConfig(config) {
|
|
13867
|
+
try {
|
|
13868
|
+
(0, import_fs6.writeFileSync)(RBAC_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
13869
|
+
} catch (error) {
|
|
13870
|
+
throw new Error(`Failed to save RBAC config: ${error}`);
|
|
13871
|
+
}
|
|
13872
|
+
}
|
|
13873
|
+
__name(saveRBACConfig, "saveRBACConfig");
|
|
13874
|
+
function getUserRole(user) {
|
|
13875
|
+
const config = loadRBACConfig();
|
|
13876
|
+
const userRole = config.userRoles.find((ur) => ur.user === user);
|
|
13877
|
+
return userRole?.role || null;
|
|
13878
|
+
}
|
|
13879
|
+
__name(getUserRole, "getUserRole");
|
|
13880
|
+
function assignRole(user, role, assignedBy) {
|
|
13881
|
+
const config = loadRBACConfig();
|
|
13882
|
+
if (!config.roles[role]) {
|
|
13883
|
+
throw new Error(`Role "${role}" does not exist`);
|
|
13884
|
+
}
|
|
13885
|
+
config.userRoles = config.userRoles.filter((ur) => ur.user !== user);
|
|
13886
|
+
config.userRoles.push({
|
|
13887
|
+
user,
|
|
13888
|
+
role,
|
|
13889
|
+
assignedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13890
|
+
assignedBy
|
|
13891
|
+
});
|
|
13892
|
+
saveRBACConfig(config);
|
|
13893
|
+
}
|
|
13894
|
+
__name(assignRole, "assignRole");
|
|
13895
|
+
function removeUserRole(user) {
|
|
13896
|
+
const config = loadRBACConfig();
|
|
13897
|
+
config.userRoles = config.userRoles.filter((ur) => ur.user !== user);
|
|
13898
|
+
saveRBACConfig(config);
|
|
13899
|
+
}
|
|
13900
|
+
__name(removeUserRole, "removeUserRole");
|
|
13901
|
+
function createRole(name, description, permissions) {
|
|
13902
|
+
const config = loadRBACConfig();
|
|
13903
|
+
if (config.roles[name]) {
|
|
13904
|
+
throw new Error(`Role "${name}" already exists`);
|
|
13905
|
+
}
|
|
13906
|
+
config.roles[name] = {
|
|
13907
|
+
name,
|
|
13908
|
+
description,
|
|
13909
|
+
permissions
|
|
13910
|
+
};
|
|
13911
|
+
saveRBACConfig(config);
|
|
13912
|
+
}
|
|
13913
|
+
__name(createRole, "createRole");
|
|
13914
|
+
function deleteRole(name) {
|
|
13915
|
+
const config = loadRBACConfig();
|
|
13916
|
+
if (DEFAULT_ROLES[name]) {
|
|
13917
|
+
throw new Error(`Cannot delete default role "${name}"`);
|
|
13918
|
+
}
|
|
13919
|
+
if (!config.roles[name]) {
|
|
13920
|
+
throw new Error(`Role "${name}" does not exist`);
|
|
13921
|
+
}
|
|
13922
|
+
delete config.roles[name];
|
|
13923
|
+
config.userRoles = config.userRoles.filter((ur) => ur.role !== name);
|
|
13924
|
+
saveRBACConfig(config);
|
|
13925
|
+
}
|
|
13926
|
+
__name(deleteRole, "deleteRole");
|
|
13927
|
+
function parsePermission(permissionStr) {
|
|
13928
|
+
const parts = permissionStr.split(":");
|
|
13929
|
+
return {
|
|
13930
|
+
resource: parts[0] || "*",
|
|
13931
|
+
action: parts[1] || "*",
|
|
13932
|
+
scope: parts[2]
|
|
13933
|
+
};
|
|
13934
|
+
}
|
|
13935
|
+
__name(parsePermission, "parsePermission");
|
|
13936
|
+
function permissionMatches(required, granted) {
|
|
13937
|
+
if (granted.resource === "*") {
|
|
13938
|
+
return true;
|
|
13939
|
+
}
|
|
13940
|
+
if (granted.resource !== required.resource) {
|
|
13941
|
+
return false;
|
|
13942
|
+
}
|
|
13943
|
+
if (granted.action !== "*" && granted.action !== required.action) {
|
|
13944
|
+
return false;
|
|
13945
|
+
}
|
|
13946
|
+
if (required.scope) {
|
|
13947
|
+
if (!granted.scope || granted.scope === "*") {
|
|
13948
|
+
return true;
|
|
13949
|
+
}
|
|
13950
|
+
if (granted.scope !== required.scope && !granted.scope.endsWith(":*")) {
|
|
13951
|
+
return false;
|
|
13952
|
+
}
|
|
13953
|
+
if (granted.scope.endsWith(":*")) {
|
|
13954
|
+
const prefix = granted.scope.slice(0, -2);
|
|
13955
|
+
return required.scope.startsWith(prefix);
|
|
13956
|
+
}
|
|
13957
|
+
}
|
|
13958
|
+
return true;
|
|
13959
|
+
}
|
|
13960
|
+
__name(permissionMatches, "permissionMatches");
|
|
13961
|
+
function hasPermission(user, permissionStr, context) {
|
|
13962
|
+
const config = loadRBACConfig();
|
|
13963
|
+
const userRole = config.userRoles.find((ur) => ur.user === user);
|
|
13964
|
+
if (!userRole) {
|
|
13965
|
+
return false;
|
|
13966
|
+
}
|
|
13967
|
+
const role = config.roles[userRole.role];
|
|
13968
|
+
if (!role) {
|
|
13969
|
+
return false;
|
|
13970
|
+
}
|
|
13971
|
+
const required = parsePermission(permissionStr);
|
|
13972
|
+
for (const grantedStr of role.permissions) {
|
|
13973
|
+
const granted = parsePermission(grantedStr);
|
|
13974
|
+
if (permissionMatches(required, granted)) {
|
|
13975
|
+
return true;
|
|
13976
|
+
}
|
|
13977
|
+
}
|
|
13978
|
+
return false;
|
|
13979
|
+
}
|
|
13980
|
+
__name(hasPermission, "hasPermission");
|
|
13981
|
+
function getUserPermissions(user) {
|
|
13982
|
+
const config = loadRBACConfig();
|
|
13983
|
+
const userRole = config.userRoles.find((ur) => ur.user === user);
|
|
13984
|
+
if (!userRole) {
|
|
13985
|
+
return [];
|
|
13986
|
+
}
|
|
13987
|
+
const role = config.roles[userRole.role];
|
|
13988
|
+
if (!role) {
|
|
13989
|
+
return [];
|
|
13990
|
+
}
|
|
13991
|
+
return role.permissions;
|
|
13992
|
+
}
|
|
13993
|
+
__name(getUserPermissions, "getUserPermissions");
|
|
13994
|
+
function listRoles() {
|
|
13995
|
+
const config = loadRBACConfig();
|
|
13996
|
+
return Object.values(config.roles);
|
|
13997
|
+
}
|
|
13998
|
+
__name(listRoles, "listRoles");
|
|
13999
|
+
function listUserRoles() {
|
|
14000
|
+
const config = loadRBACConfig();
|
|
14001
|
+
return config.userRoles;
|
|
14002
|
+
}
|
|
14003
|
+
__name(listUserRoles, "listUserRoles");
|
|
14004
|
+
|
|
14005
|
+
// src/cli/commands/rbac/index.ts
|
|
14006
|
+
async function handleAssignRole(options) {
|
|
14007
|
+
const { user, role, assignedBy } = options;
|
|
14008
|
+
if (!user || !role) {
|
|
14009
|
+
console.error(import_chalk19.default.red("Error: --user and --role are required"));
|
|
14010
|
+
console.log("\nExample:");
|
|
14011
|
+
console.log(import_chalk19.default.gray(" infra-cost rbac assign-role --user john@company.com --role finance"));
|
|
14012
|
+
process.exit(1);
|
|
14013
|
+
}
|
|
14014
|
+
try {
|
|
14015
|
+
assignRole(user, role, assignedBy);
|
|
14016
|
+
console.log(import_chalk19.default.green(`\u2705 Assigned role "${role}" to user "${user}"`));
|
|
14017
|
+
} catch (error) {
|
|
14018
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14019
|
+
process.exit(1);
|
|
14020
|
+
}
|
|
14021
|
+
}
|
|
14022
|
+
__name(handleAssignRole, "handleAssignRole");
|
|
14023
|
+
async function handleRemoveRole(options) {
|
|
14024
|
+
const { user } = options;
|
|
14025
|
+
if (!user) {
|
|
14026
|
+
console.error(import_chalk19.default.red("Error: --user is required"));
|
|
14027
|
+
process.exit(1);
|
|
14028
|
+
}
|
|
14029
|
+
try {
|
|
14030
|
+
removeUserRole(user);
|
|
14031
|
+
console.log(import_chalk19.default.green(`\u2705 Removed role from user "${user}"`));
|
|
14032
|
+
} catch (error) {
|
|
14033
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14034
|
+
process.exit(1);
|
|
14035
|
+
}
|
|
14036
|
+
}
|
|
14037
|
+
__name(handleRemoveRole, "handleRemoveRole");
|
|
14038
|
+
async function handleCreateRole(options) {
|
|
14039
|
+
const { name, description, permissions } = options;
|
|
14040
|
+
if (!name || !description || !permissions) {
|
|
14041
|
+
console.error(import_chalk19.default.red("Error: --name, --description, and --permissions are required"));
|
|
14042
|
+
console.log("\nExample:");
|
|
14043
|
+
console.log(import_chalk19.default.gray(" infra-cost rbac create-role \\"));
|
|
14044
|
+
console.log(import_chalk19.default.gray(" --name team-lead \\"));
|
|
14045
|
+
console.log(import_chalk19.default.gray(' --description "Team lead access" \\'));
|
|
14046
|
+
console.log(import_chalk19.default.gray(' --permissions "costs:read,optimization:read"'));
|
|
14047
|
+
process.exit(1);
|
|
14048
|
+
}
|
|
14049
|
+
try {
|
|
14050
|
+
const permList = permissions.split(",").map((p) => p.trim());
|
|
14051
|
+
createRole(name, description, permList);
|
|
14052
|
+
console.log(import_chalk19.default.green(`\u2705 Created role "${name}"`));
|
|
14053
|
+
console.log(import_chalk19.default.gray(` Permissions: ${permList.join(", ")}`));
|
|
14054
|
+
} catch (error) {
|
|
14055
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14056
|
+
process.exit(1);
|
|
14057
|
+
}
|
|
14058
|
+
}
|
|
14059
|
+
__name(handleCreateRole, "handleCreateRole");
|
|
14060
|
+
async function handleDeleteRole(options) {
|
|
14061
|
+
const { name } = options;
|
|
14062
|
+
if (!name) {
|
|
14063
|
+
console.error(import_chalk19.default.red("Error: --name is required"));
|
|
14064
|
+
process.exit(1);
|
|
14065
|
+
}
|
|
14066
|
+
try {
|
|
14067
|
+
deleteRole(name);
|
|
14068
|
+
console.log(import_chalk19.default.green(`\u2705 Deleted role "${name}"`));
|
|
14069
|
+
} catch (error) {
|
|
14070
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14071
|
+
process.exit(1);
|
|
14072
|
+
}
|
|
14073
|
+
}
|
|
14074
|
+
__name(handleDeleteRole, "handleDeleteRole");
|
|
14075
|
+
async function handleShowPermissions(options) {
|
|
14076
|
+
const { user } = options;
|
|
14077
|
+
if (!user) {
|
|
14078
|
+
console.error(import_chalk19.default.red("Error: --user is required"));
|
|
14079
|
+
process.exit(1);
|
|
14080
|
+
}
|
|
14081
|
+
try {
|
|
14082
|
+
const role = getUserRole(user);
|
|
14083
|
+
const permissions = getUserPermissions(user);
|
|
14084
|
+
console.log(import_chalk19.default.bold(`
|
|
14085
|
+
\u{1F464} User: ${user}
|
|
14086
|
+
`));
|
|
14087
|
+
if (!role) {
|
|
14088
|
+
console.log(import_chalk19.default.yellow("No role assigned"));
|
|
14089
|
+
return;
|
|
14090
|
+
}
|
|
14091
|
+
console.log(import_chalk19.default.bold(`Role: ${role}
|
|
14092
|
+
`));
|
|
14093
|
+
console.log(import_chalk19.default.bold("Permissions:"));
|
|
14094
|
+
if (permissions.length === 0) {
|
|
14095
|
+
console.log(import_chalk19.default.gray(" No permissions"));
|
|
14096
|
+
} else {
|
|
14097
|
+
permissions.forEach((perm) => {
|
|
14098
|
+
console.log(import_chalk19.default.gray(` \u2022 ${perm}`));
|
|
14099
|
+
});
|
|
14100
|
+
}
|
|
14101
|
+
console.log("");
|
|
14102
|
+
} catch (error) {
|
|
14103
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14104
|
+
process.exit(1);
|
|
14105
|
+
}
|
|
14106
|
+
}
|
|
14107
|
+
__name(handleShowPermissions, "handleShowPermissions");
|
|
14108
|
+
async function handleListRoles(options) {
|
|
14109
|
+
try {
|
|
14110
|
+
const roles = listRoles();
|
|
14111
|
+
console.log(import_chalk19.default.bold("\n\u{1F4CB} Available Roles\n"));
|
|
14112
|
+
roles.forEach((role) => {
|
|
14113
|
+
console.log(import_chalk19.default.bold(`${role.name}`));
|
|
14114
|
+
console.log(import_chalk19.default.gray(` ${role.description}`));
|
|
14115
|
+
console.log(import_chalk19.default.gray(` Permissions: ${role.permissions.slice(0, 3).join(", ")}${role.permissions.length > 3 ? "..." : ""}`));
|
|
14116
|
+
console.log("");
|
|
14117
|
+
});
|
|
14118
|
+
} catch (error) {
|
|
14119
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14120
|
+
process.exit(1);
|
|
14121
|
+
}
|
|
14122
|
+
}
|
|
14123
|
+
__name(handleListRoles, "handleListRoles");
|
|
14124
|
+
async function handleListUsers(options) {
|
|
14125
|
+
try {
|
|
14126
|
+
const userRoles = listUserRoles();
|
|
14127
|
+
console.log(import_chalk19.default.bold("\n\u{1F465} User Role Assignments\n"));
|
|
14128
|
+
if (userRoles.length === 0) {
|
|
14129
|
+
console.log(import_chalk19.default.yellow("No user roles assigned"));
|
|
14130
|
+
return;
|
|
14131
|
+
}
|
|
14132
|
+
userRoles.forEach((ur) => {
|
|
14133
|
+
console.log(import_chalk19.default.bold(ur.user));
|
|
14134
|
+
console.log(import_chalk19.default.gray(` Role: ${ur.role}`));
|
|
14135
|
+
console.log(import_chalk19.default.gray(` Assigned: ${new Date(ur.assignedAt).toLocaleString()}`));
|
|
14136
|
+
if (ur.assignedBy) {
|
|
14137
|
+
console.log(import_chalk19.default.gray(` Assigned by: ${ur.assignedBy}`));
|
|
14138
|
+
}
|
|
14139
|
+
console.log("");
|
|
14140
|
+
});
|
|
14141
|
+
} catch (error) {
|
|
14142
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14143
|
+
process.exit(1);
|
|
14144
|
+
}
|
|
14145
|
+
}
|
|
14146
|
+
__name(handleListUsers, "handleListUsers");
|
|
14147
|
+
async function handleCheckPermission(options) {
|
|
14148
|
+
const { user, permission } = options;
|
|
14149
|
+
if (!user || !permission) {
|
|
14150
|
+
console.error(import_chalk19.default.red("Error: --user and --permission are required"));
|
|
14151
|
+
console.log("\nExample:");
|
|
14152
|
+
console.log(import_chalk19.default.gray(' infra-cost rbac check-permission --user john@company.com --permission "costs:read"'));
|
|
14153
|
+
process.exit(1);
|
|
14154
|
+
}
|
|
14155
|
+
try {
|
|
14156
|
+
const has = hasPermission(user, permission);
|
|
14157
|
+
console.log(import_chalk19.default.bold(`
|
|
14158
|
+
\u{1F464} User: ${user}`));
|
|
14159
|
+
console.log(import_chalk19.default.bold(`\u{1F510} Permission: ${permission}
|
|
14160
|
+
`));
|
|
14161
|
+
if (has) {
|
|
14162
|
+
console.log(import_chalk19.default.green("\u2705 ALLOWED"));
|
|
14163
|
+
} else {
|
|
14164
|
+
console.log(import_chalk19.default.red("\u274C DENIED"));
|
|
14165
|
+
}
|
|
14166
|
+
console.log("");
|
|
14167
|
+
} catch (error) {
|
|
14168
|
+
console.error(import_chalk19.default.red(`Error: ${error.message}`));
|
|
14169
|
+
process.exit(1);
|
|
14170
|
+
}
|
|
14171
|
+
}
|
|
14172
|
+
__name(handleCheckPermission, "handleCheckPermission");
|
|
14173
|
+
function registerRBACCommands(program) {
|
|
14174
|
+
const rbac = program.command("rbac").description("Role-Based Access Control management");
|
|
14175
|
+
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);
|
|
14176
|
+
rbac.command("remove-role").description("Remove role from user").option("--user <email>", "User email").action(handleRemoveRole);
|
|
14177
|
+
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);
|
|
14178
|
+
rbac.command("delete-role").description("Delete custom role").option("--name <name>", "Role name").action(handleDeleteRole);
|
|
14179
|
+
rbac.command("show-permissions").description("Show user permissions").option("--user <email>", "User email").action(handleShowPermissions);
|
|
14180
|
+
rbac.command("list-roles").description("List all available roles").action(handleListRoles);
|
|
14181
|
+
rbac.command("list-users").description("List all user role assignments").action(handleListUsers);
|
|
14182
|
+
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);
|
|
14183
|
+
}
|
|
14184
|
+
__name(registerRBACCommands, "registerRBACCommands");
|
|
14185
|
+
|
|
14186
|
+
// src/cli/commands/sso/index.ts
|
|
14187
|
+
var import_chalk20 = __toESM(require("chalk"));
|
|
14188
|
+
|
|
14189
|
+
// src/core/sso.ts
|
|
14190
|
+
var import_fs7 = require("fs");
|
|
14191
|
+
var import_path5 = require("path");
|
|
14192
|
+
var import_os5 = require("os");
|
|
14193
|
+
var CONFIG_DIR3 = (0, import_path5.join)((0, import_os5.homedir)(), ".infra-cost");
|
|
14194
|
+
var SSO_DIR = (0, import_path5.join)(CONFIG_DIR3, "sso");
|
|
14195
|
+
var SSO_CONFIG_FILE = (0, import_path5.join)(CONFIG_DIR3, "sso-config.json");
|
|
14196
|
+
var SESSION_FILE = (0, import_path5.join)(SSO_DIR, "session.json");
|
|
14197
|
+
function ensureSSODir() {
|
|
14198
|
+
if (!(0, import_fs7.existsSync)(SSO_DIR)) {
|
|
14199
|
+
(0, import_fs7.mkdirSync)(SSO_DIR, { recursive: true });
|
|
14200
|
+
}
|
|
14201
|
+
}
|
|
14202
|
+
__name(ensureSSODir, "ensureSSODir");
|
|
14203
|
+
function loadSSOConfig() {
|
|
14204
|
+
if (!(0, import_fs7.existsSync)(SSO_CONFIG_FILE)) {
|
|
14205
|
+
return null;
|
|
14206
|
+
}
|
|
14207
|
+
try {
|
|
14208
|
+
const data = (0, import_fs7.readFileSync)(SSO_CONFIG_FILE, "utf-8");
|
|
14209
|
+
return JSON.parse(data);
|
|
14210
|
+
} catch (error) {
|
|
14211
|
+
console.error("Error loading SSO config:", error);
|
|
14212
|
+
return null;
|
|
14213
|
+
}
|
|
14214
|
+
}
|
|
14215
|
+
__name(loadSSOConfig, "loadSSOConfig");
|
|
14216
|
+
function saveSSOConfig(config) {
|
|
14217
|
+
try {
|
|
14218
|
+
ensureSSODir();
|
|
14219
|
+
(0, import_fs7.writeFileSync)(SSO_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
14220
|
+
} catch (error) {
|
|
14221
|
+
throw new Error(`Failed to save SSO config: ${error}`);
|
|
14222
|
+
}
|
|
14223
|
+
}
|
|
14224
|
+
__name(saveSSOConfig, "saveSSOConfig");
|
|
14225
|
+
function loadSSOSession() {
|
|
14226
|
+
if (!(0, import_fs7.existsSync)(SESSION_FILE)) {
|
|
14227
|
+
return null;
|
|
14228
|
+
}
|
|
14229
|
+
try {
|
|
14230
|
+
const data = (0, import_fs7.readFileSync)(SESSION_FILE, "utf-8");
|
|
14231
|
+
const session = JSON.parse(data);
|
|
14232
|
+
const expiresAt = new Date(session.expiresAt);
|
|
14233
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
14234
|
+
return null;
|
|
14235
|
+
}
|
|
14236
|
+
return session;
|
|
14237
|
+
} catch (error) {
|
|
14238
|
+
return null;
|
|
14239
|
+
}
|
|
14240
|
+
}
|
|
14241
|
+
__name(loadSSOSession, "loadSSOSession");
|
|
14242
|
+
function saveSSOSession(session) {
|
|
14243
|
+
try {
|
|
14244
|
+
ensureSSODir();
|
|
14245
|
+
(0, import_fs7.writeFileSync)(SESSION_FILE, JSON.stringify(session, null, 2));
|
|
14246
|
+
} catch (error) {
|
|
14247
|
+
throw new Error(`Failed to save SSO session: ${error}`);
|
|
14248
|
+
}
|
|
14249
|
+
}
|
|
14250
|
+
__name(saveSSOSession, "saveSSOSession");
|
|
14251
|
+
function clearSSOSession() {
|
|
14252
|
+
try {
|
|
14253
|
+
if ((0, import_fs7.existsSync)(SESSION_FILE)) {
|
|
14254
|
+
require("fs").unlinkSync(SESSION_FILE);
|
|
14255
|
+
}
|
|
14256
|
+
} catch (error) {
|
|
14257
|
+
}
|
|
14258
|
+
}
|
|
14259
|
+
__name(clearSSOSession, "clearSSOSession");
|
|
14260
|
+
function isLoggedIn() {
|
|
14261
|
+
const session = loadSSOSession();
|
|
14262
|
+
return session !== null;
|
|
14263
|
+
}
|
|
14264
|
+
__name(isLoggedIn, "isLoggedIn");
|
|
14265
|
+
function getCurrentUser() {
|
|
14266
|
+
const session = loadSSOSession();
|
|
14267
|
+
return session?.email || null;
|
|
14268
|
+
}
|
|
14269
|
+
__name(getCurrentUser, "getCurrentUser");
|
|
14270
|
+
function getSessionExpiration() {
|
|
14271
|
+
const session = loadSSOSession();
|
|
14272
|
+
return session ? new Date(session.expiresAt) : null;
|
|
14273
|
+
}
|
|
14274
|
+
__name(getSessionExpiration, "getSessionExpiration");
|
|
14275
|
+
function getMinutesUntilExpiration() {
|
|
14276
|
+
const expiration = getSessionExpiration();
|
|
14277
|
+
if (!expiration)
|
|
14278
|
+
return null;
|
|
14279
|
+
const now = /* @__PURE__ */ new Date();
|
|
14280
|
+
const diff = expiration.getTime() - now.getTime();
|
|
14281
|
+
return Math.floor(diff / 1e3 / 60);
|
|
14282
|
+
}
|
|
14283
|
+
__name(getMinutesUntilExpiration, "getMinutesUntilExpiration");
|
|
14284
|
+
function generateAuthorizationUrl(config) {
|
|
14285
|
+
const params = new URLSearchParams({
|
|
14286
|
+
client_id: config.config.clientId,
|
|
14287
|
+
redirect_uri: config.config.redirectUri,
|
|
14288
|
+
response_type: "code",
|
|
14289
|
+
scope: config.config.scopes.join(" "),
|
|
14290
|
+
state: generateRandomState()
|
|
14291
|
+
});
|
|
14292
|
+
const authEndpoint = config.config.authorizationEndpoint || `${config.config.issuer}/v1/authorize`;
|
|
14293
|
+
return `${authEndpoint}?${params.toString()}`;
|
|
14294
|
+
}
|
|
14295
|
+
__name(generateAuthorizationUrl, "generateAuthorizationUrl");
|
|
14296
|
+
function generateRandomState() {
|
|
14297
|
+
return Math.random().toString(36).substring(2, 15);
|
|
14298
|
+
}
|
|
14299
|
+
__name(generateRandomState, "generateRandomState");
|
|
14300
|
+
var ProviderConfig7 = {
|
|
14301
|
+
okta: (domain, clientId, clientSecret) => ({
|
|
14302
|
+
issuer: `https://${domain}`,
|
|
14303
|
+
clientId,
|
|
14304
|
+
clientSecret,
|
|
14305
|
+
authorizationEndpoint: `https://${domain}/oauth2/v1/authorize`,
|
|
14306
|
+
tokenEndpoint: `https://${domain}/oauth2/v1/token`,
|
|
14307
|
+
userInfoEndpoint: `https://${domain}/oauth2/v1/userinfo`,
|
|
14308
|
+
scopes: ["openid", "profile", "email"]
|
|
14309
|
+
}),
|
|
14310
|
+
azureAd: (tenantId, clientId, clientSecret) => ({
|
|
14311
|
+
issuer: `https://login.microsoftonline.com/${tenantId}/v2.0`,
|
|
14312
|
+
clientId,
|
|
14313
|
+
clientSecret,
|
|
14314
|
+
authorizationEndpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
|
|
14315
|
+
tokenEndpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
|
|
14316
|
+
userInfoEndpoint: "https://graph.microsoft.com/v1.0/me",
|
|
14317
|
+
scopes: ["openid", "profile", "email", "User.Read"]
|
|
14318
|
+
}),
|
|
14319
|
+
google: (clientId, clientSecret) => ({
|
|
14320
|
+
issuer: "https://accounts.google.com",
|
|
14321
|
+
clientId,
|
|
14322
|
+
clientSecret,
|
|
14323
|
+
authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
14324
|
+
tokenEndpoint: "https://oauth2.googleapis.com/token",
|
|
14325
|
+
userInfoEndpoint: "https://openidconnect.googleapis.com/v1/userinfo",
|
|
14326
|
+
scopes: ["openid", "profile", "email"]
|
|
14327
|
+
})
|
|
14328
|
+
};
|
|
14329
|
+
|
|
14330
|
+
// src/cli/commands/sso/index.ts
|
|
14331
|
+
async function handleConfigure(options) {
|
|
14332
|
+
const { provider, clientId, clientSecret, issuer, tenantId, domain } = options;
|
|
14333
|
+
if (!provider) {
|
|
14334
|
+
console.error(import_chalk20.default.red("Error: --provider is required"));
|
|
14335
|
+
console.log("\nSupported providers: okta, azure-ad, google, generic-oidc");
|
|
14336
|
+
process.exit(1);
|
|
14337
|
+
}
|
|
14338
|
+
let config;
|
|
14339
|
+
switch (provider) {
|
|
14340
|
+
case "okta":
|
|
14341
|
+
if (!domain || !clientId || !clientSecret) {
|
|
14342
|
+
console.error(import_chalk20.default.red("Error: Okta requires --domain, --client-id, --client-secret"));
|
|
14343
|
+
process.exit(1);
|
|
14344
|
+
}
|
|
14345
|
+
config = ProviderConfig7.okta(domain, clientId, clientSecret);
|
|
14346
|
+
break;
|
|
14347
|
+
case "azure-ad":
|
|
14348
|
+
if (!tenantId || !clientId || !clientSecret) {
|
|
14349
|
+
console.error(import_chalk20.default.red("Error: Azure AD requires --tenant-id, --client-id, --client-secret"));
|
|
14350
|
+
process.exit(1);
|
|
14351
|
+
}
|
|
14352
|
+
config = ProviderConfig7.azureAd(tenantId, clientId, clientSecret);
|
|
14353
|
+
break;
|
|
14354
|
+
case "google":
|
|
14355
|
+
if (!clientId || !clientSecret) {
|
|
14356
|
+
console.error(import_chalk20.default.red("Error: Google requires --client-id, --client-secret"));
|
|
14357
|
+
process.exit(1);
|
|
14358
|
+
}
|
|
14359
|
+
config = ProviderConfig7.google(clientId, clientSecret);
|
|
14360
|
+
break;
|
|
14361
|
+
default:
|
|
14362
|
+
console.error(import_chalk20.default.red(`Error: Unknown provider "${provider}"`));
|
|
14363
|
+
process.exit(1);
|
|
14364
|
+
}
|
|
14365
|
+
const ssoConfig = {
|
|
14366
|
+
enabled: true,
|
|
14367
|
+
provider,
|
|
14368
|
+
config: {
|
|
14369
|
+
...config,
|
|
14370
|
+
redirectUri: "http://localhost:8400/callback"
|
|
14371
|
+
}
|
|
14372
|
+
};
|
|
14373
|
+
saveSSOConfig(ssoConfig);
|
|
14374
|
+
console.log(import_chalk20.default.green("\u2705 SSO configured successfully"));
|
|
14375
|
+
console.log(import_chalk20.default.gray(` Provider: ${provider}`));
|
|
14376
|
+
console.log(import_chalk20.default.gray(` Issuer: ${config.issuer}`));
|
|
14377
|
+
console.log(import_chalk20.default.gray("\nRun `infra-cost login --sso` to authenticate"));
|
|
14378
|
+
}
|
|
14379
|
+
__name(handleConfigure, "handleConfigure");
|
|
14380
|
+
async function handleStatus2(options) {
|
|
14381
|
+
const config = loadSSOConfig();
|
|
14382
|
+
console.log(import_chalk20.default.bold("\n\u{1F510} SSO Status\n"));
|
|
14383
|
+
if (!config || !config.enabled) {
|
|
14384
|
+
console.log(import_chalk20.default.yellow("SSO not configured"));
|
|
14385
|
+
console.log(import_chalk20.default.gray("\nRun `infra-cost sso configure` to set up SSO"));
|
|
14386
|
+
return;
|
|
14387
|
+
}
|
|
14388
|
+
console.log(import_chalk20.default.bold("Configuration:"));
|
|
14389
|
+
console.log(import_chalk20.default.gray(` Provider: ${config.provider}`));
|
|
14390
|
+
console.log(import_chalk20.default.gray(` Issuer: ${config.config.issuer}`));
|
|
14391
|
+
console.log(import_chalk20.default.gray(` Client ID: ${config.config.clientId}`));
|
|
14392
|
+
console.log("");
|
|
14393
|
+
const session = loadSSOSession();
|
|
14394
|
+
if (!session) {
|
|
14395
|
+
console.log(import_chalk20.default.yellow("Not logged in"));
|
|
14396
|
+
console.log(import_chalk20.default.gray("\nRun `infra-cost login --sso` to authenticate"));
|
|
14397
|
+
return;
|
|
14398
|
+
}
|
|
14399
|
+
console.log(import_chalk20.default.bold("Session:"));
|
|
14400
|
+
console.log(import_chalk20.default.green(` \u2705 Logged in as ${session.email}`));
|
|
14401
|
+
const minutesLeft = getMinutesUntilExpiration();
|
|
14402
|
+
if (minutesLeft !== null) {
|
|
14403
|
+
if (minutesLeft > 60) {
|
|
14404
|
+
const hours = Math.floor(minutesLeft / 60);
|
|
14405
|
+
console.log(import_chalk20.default.gray(` Expires in ${hours} hour${hours !== 1 ? "s" : ""}`));
|
|
14406
|
+
} else if (minutesLeft > 0) {
|
|
14407
|
+
console.log(import_chalk20.default.gray(` Expires in ${minutesLeft} minute${minutesLeft !== 1 ? "s" : ""}`));
|
|
14408
|
+
} else {
|
|
14409
|
+
console.log(import_chalk20.default.red(" Session expired - please login again"));
|
|
14410
|
+
}
|
|
14411
|
+
}
|
|
14412
|
+
console.log("");
|
|
14413
|
+
}
|
|
14414
|
+
__name(handleStatus2, "handleStatus");
|
|
14415
|
+
async function handleLogin(options) {
|
|
14416
|
+
const { sso } = options;
|
|
14417
|
+
if (!sso) {
|
|
14418
|
+
console.log(import_chalk20.default.yellow("Use --sso flag for SSO login"));
|
|
14419
|
+
console.log(import_chalk20.default.gray("\nExample: infra-cost login --sso"));
|
|
14420
|
+
return;
|
|
14421
|
+
}
|
|
14422
|
+
const config = loadSSOConfig();
|
|
14423
|
+
if (!config || !config.enabled) {
|
|
14424
|
+
console.error(import_chalk20.default.red("Error: SSO not configured"));
|
|
14425
|
+
console.log(import_chalk20.default.gray("\nRun `infra-cost sso configure` to set up SSO"));
|
|
14426
|
+
process.exit(1);
|
|
14427
|
+
}
|
|
14428
|
+
console.log(import_chalk20.default.blue("\u{1F510} Starting SSO login...\n"));
|
|
14429
|
+
const authUrl = generateAuthorizationUrl(config);
|
|
14430
|
+
console.log(import_chalk20.default.bold("Opening browser for authentication..."));
|
|
14431
|
+
console.log(import_chalk20.default.gray(`Provider: ${config.provider}`));
|
|
14432
|
+
console.log(import_chalk20.default.gray(`URL: ${authUrl}
|
|
14433
|
+
`));
|
|
14434
|
+
console.log(import_chalk20.default.gray("\u23F3 Waiting for authentication...\n"));
|
|
14435
|
+
setTimeout(() => {
|
|
14436
|
+
const session = {
|
|
14437
|
+
provider: config.provider,
|
|
14438
|
+
user: "John Doe",
|
|
14439
|
+
email: "john.doe@company.com",
|
|
14440
|
+
accessToken: "simulated_access_token",
|
|
14441
|
+
refreshToken: "simulated_refresh_token",
|
|
14442
|
+
idToken: "simulated_id_token",
|
|
14443
|
+
expiresAt: new Date(Date.now() + 3600 * 1e3).toISOString()
|
|
14444
|
+
// 1 hour
|
|
14445
|
+
};
|
|
14446
|
+
saveSSOSession(session);
|
|
14447
|
+
console.log(import_chalk20.default.green("\u2705 Successfully logged in!"));
|
|
14448
|
+
console.log(import_chalk20.default.gray(` User: ${session.email}`));
|
|
14449
|
+
console.log(import_chalk20.default.gray(` Provider: ${config.provider}`));
|
|
14450
|
+
console.log(import_chalk20.default.gray(" Session expires in 1 hour\n"));
|
|
14451
|
+
console.log(import_chalk20.default.gray("You can now run infra-cost commands"));
|
|
14452
|
+
}, 1e3);
|
|
14453
|
+
}
|
|
14454
|
+
__name(handleLogin, "handleLogin");
|
|
14455
|
+
async function handleLogout(options) {
|
|
14456
|
+
if (!isLoggedIn()) {
|
|
14457
|
+
console.log(import_chalk20.default.yellow("Not currently logged in"));
|
|
14458
|
+
return;
|
|
14459
|
+
}
|
|
14460
|
+
const user = getCurrentUser();
|
|
14461
|
+
clearSSOSession();
|
|
14462
|
+
console.log(import_chalk20.default.green(`\u2705 Logged out${user ? ` (${user})` : ""}`));
|
|
14463
|
+
}
|
|
14464
|
+
__name(handleLogout, "handleLogout");
|
|
14465
|
+
async function handleRefresh(options) {
|
|
14466
|
+
const session = loadSSOSession();
|
|
14467
|
+
if (!session) {
|
|
14468
|
+
console.error(import_chalk20.default.red("Error: Not logged in"));
|
|
14469
|
+
console.log(import_chalk20.default.gray("\nRun `infra-cost login --sso` to authenticate"));
|
|
14470
|
+
process.exit(1);
|
|
14471
|
+
}
|
|
14472
|
+
console.log(import_chalk20.default.blue("\u{1F504} Refreshing SSO session...\n"));
|
|
14473
|
+
setTimeout(() => {
|
|
14474
|
+
const refreshedSession = {
|
|
14475
|
+
...session,
|
|
14476
|
+
expiresAt: new Date(Date.now() + 3600 * 1e3).toISOString()
|
|
14477
|
+
// 1 hour
|
|
14478
|
+
};
|
|
14479
|
+
saveSSOSession(refreshedSession);
|
|
14480
|
+
console.log(import_chalk20.default.green("\u2705 Session refreshed"));
|
|
14481
|
+
console.log(import_chalk20.default.gray(" New expiration: 1 hour from now\n"));
|
|
14482
|
+
}, 500);
|
|
14483
|
+
}
|
|
14484
|
+
__name(handleRefresh, "handleRefresh");
|
|
14485
|
+
function registerSSOCommands(program) {
|
|
14486
|
+
const sso = program.command("sso").description("SSO/SAML enterprise authentication");
|
|
14487
|
+
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);
|
|
14488
|
+
sso.command("status").description("Show SSO configuration and session status").action(handleStatus2);
|
|
14489
|
+
sso.command("refresh").description("Refresh SSO session").action(handleRefresh);
|
|
14490
|
+
program.command("login").description("Login with SSO").option("--sso", "Use SSO login").action(handleLogin);
|
|
14491
|
+
program.command("logout").description("Logout from SSO session").action(handleLogout);
|
|
14492
|
+
}
|
|
14493
|
+
__name(registerSSOCommands, "registerSSOCommands");
|
|
14494
|
+
|
|
14495
|
+
// src/cli/commands/plugin/index.ts
|
|
14496
|
+
var import_chalk21 = __toESM(require("chalk"));
|
|
14497
|
+
|
|
14498
|
+
// src/core/plugins.ts
|
|
14499
|
+
var import_path6 = require("path");
|
|
14500
|
+
var import_os6 = require("os");
|
|
14501
|
+
var PLUGIN_DIR = (0, import_path6.join)((0, import_os6.homedir)(), ".infra-cost", "plugins");
|
|
14502
|
+
var loadedPlugins = /* @__PURE__ */ new Map();
|
|
14503
|
+
function getLoadedPlugins() {
|
|
14504
|
+
return Array.from(loadedPlugins.values());
|
|
14505
|
+
}
|
|
14506
|
+
__name(getLoadedPlugins, "getLoadedPlugins");
|
|
14507
|
+
|
|
14508
|
+
// src/cli/commands/plugin/index.ts
|
|
14509
|
+
async function handleList3(options) {
|
|
14510
|
+
const plugins = getLoadedPlugins();
|
|
14511
|
+
console.log(import_chalk21.default.bold("\n\u{1F50C} Installed Plugins\n"));
|
|
14512
|
+
if (plugins.length === 0) {
|
|
14513
|
+
console.log(import_chalk21.default.yellow("No plugins installed"));
|
|
14514
|
+
console.log(import_chalk21.default.gray("\nPlugins should be installed in: ~/.infra-cost/plugins/"));
|
|
14515
|
+
return;
|
|
14516
|
+
}
|
|
14517
|
+
plugins.forEach((plugin) => {
|
|
14518
|
+
console.log(import_chalk21.default.bold(`${plugin.name} v${plugin.version}`));
|
|
14519
|
+
console.log(import_chalk21.default.gray(` ${plugin.description}`));
|
|
14520
|
+
if (plugin.author) {
|
|
14521
|
+
console.log(import_chalk21.default.gray(` Author: ${plugin.author}`));
|
|
14522
|
+
}
|
|
14523
|
+
console.log("");
|
|
14524
|
+
});
|
|
14525
|
+
}
|
|
14526
|
+
__name(handleList3, "handleList");
|
|
14527
|
+
async function handleInfo(options) {
|
|
14528
|
+
console.log(import_chalk21.default.bold("\n\u{1F50C} Plugin System Information\n"));
|
|
14529
|
+
console.log(import_chalk21.default.bold("Plugin Directory:"));
|
|
14530
|
+
console.log(import_chalk21.default.gray(" ~/.infra-cost/plugins/\n"));
|
|
14531
|
+
console.log(import_chalk21.default.bold("Plugin Structure:"));
|
|
14532
|
+
console.log(import_chalk21.default.gray(" my-plugin/"));
|
|
14533
|
+
console.log(import_chalk21.default.gray(" \u251C\u2500\u2500 package.json # Plugin metadata"));
|
|
14534
|
+
console.log(import_chalk21.default.gray(" \u2514\u2500\u2500 index.js # Plugin entry point\n"));
|
|
14535
|
+
console.log(import_chalk21.default.bold("Example package.json:"));
|
|
14536
|
+
console.log(import_chalk21.default.gray(" {"));
|
|
14537
|
+
console.log(import_chalk21.default.gray(' "name": "my-custom-plugin",'));
|
|
14538
|
+
console.log(import_chalk21.default.gray(' "version": "1.0.0",'));
|
|
14539
|
+
console.log(import_chalk21.default.gray(' "description": "Custom cost provider"'));
|
|
14540
|
+
console.log(import_chalk21.default.gray(" }\n"));
|
|
14541
|
+
console.log(import_chalk21.default.bold("Example index.js:"));
|
|
14542
|
+
console.log(import_chalk21.default.gray(" module.exports = {"));
|
|
14543
|
+
console.log(import_chalk21.default.gray(' name: "my-custom-plugin",'));
|
|
14544
|
+
console.log(import_chalk21.default.gray(' version: "1.0.0",'));
|
|
14545
|
+
console.log(import_chalk21.default.gray(' description: "Custom cost provider",'));
|
|
14546
|
+
console.log(import_chalk21.default.gray(' init: async () => { console.log("Plugin loaded!"); },'));
|
|
14547
|
+
console.log(import_chalk21.default.gray(" registerCommands: (program) => { /* ... */ }"));
|
|
14548
|
+
console.log(import_chalk21.default.gray(" };\n"));
|
|
14549
|
+
console.log(import_chalk21.default.gray("For documentation: https://github.com/codecollab-co/infra-cost#plugins"));
|
|
14550
|
+
}
|
|
14551
|
+
__name(handleInfo, "handleInfo");
|
|
14552
|
+
function registerPluginCommands(program) {
|
|
14553
|
+
const plugin = program.command("plugin").description("Manage custom plugins");
|
|
14554
|
+
plugin.command("list").description("List installed plugins").action(handleList3);
|
|
14555
|
+
plugin.command("info").description("Show plugin system information").action(handleInfo);
|
|
14556
|
+
}
|
|
14557
|
+
__name(registerPluginCommands, "registerPluginCommands");
|
|
14558
|
+
|
|
14559
|
+
// src/cli/commands/server/index.ts
|
|
14560
|
+
var import_chalk22 = __toESM(require("chalk"));
|
|
14561
|
+
var import_fs8 = require("fs");
|
|
14562
|
+
var import_path7 = require("path");
|
|
14563
|
+
var import_os7 = require("os");
|
|
14564
|
+
init_server();
|
|
14565
|
+
var CONFIG_DIR4 = (0, import_path7.join)((0, import_os7.homedir)(), ".infra-cost");
|
|
14566
|
+
var SERVER_CONFIG_PATH = (0, import_path7.join)(CONFIG_DIR4, "server-config.json");
|
|
14567
|
+
var PID_FILE2 = (0, import_path7.join)(CONFIG_DIR4, "server.pid");
|
|
14568
|
+
var DEFAULT_CONFIG3 = {
|
|
14569
|
+
port: 3e3,
|
|
14570
|
+
host: "127.0.0.1",
|
|
14571
|
+
cors: {
|
|
14572
|
+
enabled: true,
|
|
14573
|
+
origins: ["*"]
|
|
14574
|
+
},
|
|
14575
|
+
auth: {
|
|
14576
|
+
type: "none"
|
|
14577
|
+
},
|
|
14578
|
+
rateLimit: {
|
|
14579
|
+
enabled: true,
|
|
14580
|
+
windowMs: 6e4,
|
|
14581
|
+
max: 100
|
|
14582
|
+
},
|
|
14583
|
+
cache: {
|
|
14584
|
+
enabled: true,
|
|
14585
|
+
ttl: 300
|
|
14586
|
+
// 5 minutes
|
|
14587
|
+
}
|
|
14588
|
+
};
|
|
14589
|
+
function loadServerConfig() {
|
|
14590
|
+
if ((0, import_fs8.existsSync)(SERVER_CONFIG_PATH)) {
|
|
14591
|
+
const config = JSON.parse((0, import_fs8.readFileSync)(SERVER_CONFIG_PATH, "utf-8"));
|
|
14592
|
+
return { ...DEFAULT_CONFIG3, ...config };
|
|
14593
|
+
}
|
|
14594
|
+
return DEFAULT_CONFIG3;
|
|
14595
|
+
}
|
|
14596
|
+
__name(loadServerConfig, "loadServerConfig");
|
|
14597
|
+
function saveServerConfig(config) {
|
|
14598
|
+
(0, import_fs8.writeFileSync)(SERVER_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
14599
|
+
}
|
|
14600
|
+
__name(saveServerConfig, "saveServerConfig");
|
|
14601
|
+
async function handleStart2(options) {
|
|
14602
|
+
try {
|
|
14603
|
+
if ((0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14604
|
+
const pid = parseInt((0, import_fs8.readFileSync)(PID_FILE2, "utf-8").trim(), 10);
|
|
14605
|
+
try {
|
|
14606
|
+
process.kill(pid, 0);
|
|
14607
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F Server is already running"));
|
|
14608
|
+
console.log(import_chalk22.default.gray(` PID: ${pid}`));
|
|
14609
|
+
return;
|
|
14610
|
+
} catch {
|
|
14611
|
+
require("fs").unlinkSync(PID_FILE2);
|
|
14612
|
+
}
|
|
14613
|
+
}
|
|
14614
|
+
const config = loadServerConfig();
|
|
14615
|
+
if (options.port)
|
|
14616
|
+
config.port = parseInt(options.port, 10);
|
|
14617
|
+
if (options.host)
|
|
14618
|
+
config.host = options.host;
|
|
14619
|
+
if (options.apiKey) {
|
|
14620
|
+
config.auth = {
|
|
14621
|
+
type: "api-key",
|
|
14622
|
+
apiKeys: [options.apiKey]
|
|
14623
|
+
};
|
|
14624
|
+
}
|
|
14625
|
+
if (options.apiKeyRequired && !options.apiKey) {
|
|
14626
|
+
console.log(import_chalk22.default.red("\u274C --api-key is required when --api-key-required is set"));
|
|
14627
|
+
process.exit(1);
|
|
14628
|
+
}
|
|
14629
|
+
const server = new ApiServer(config);
|
|
14630
|
+
if (options.daemon) {
|
|
14631
|
+
console.log(import_chalk22.default.blue("Starting server in daemon mode..."));
|
|
14632
|
+
const { spawn } = require("child_process");
|
|
14633
|
+
const child = spawn(
|
|
14634
|
+
process.argv[0],
|
|
14635
|
+
[process.argv[1], "server", "start", "--port", config.port.toString(), "--host", config.host],
|
|
14636
|
+
{
|
|
14637
|
+
detached: true,
|
|
14638
|
+
stdio: "ignore"
|
|
14639
|
+
}
|
|
14640
|
+
);
|
|
14641
|
+
child.unref();
|
|
14642
|
+
(0, import_fs8.writeFileSync)(PID_FILE2, child.pid.toString());
|
|
14643
|
+
console.log(import_chalk22.default.green("\u2705 Server started in background"));
|
|
14644
|
+
console.log(import_chalk22.default.gray(` PID: ${child.pid}`));
|
|
14645
|
+
console.log(import_chalk22.default.gray(` URL: http://${config.host}:${config.port}`));
|
|
14646
|
+
return;
|
|
14647
|
+
}
|
|
14648
|
+
await server.start();
|
|
14649
|
+
(0, import_fs8.writeFileSync)(PID_FILE2, process.pid.toString());
|
|
14650
|
+
console.log();
|
|
14651
|
+
console.log(import_chalk22.default.bold("Server Configuration:"));
|
|
14652
|
+
console.log(import_chalk22.default.gray(` Port: ${config.port}`));
|
|
14653
|
+
console.log(import_chalk22.default.gray(` Host: ${config.host}`));
|
|
14654
|
+
console.log(import_chalk22.default.gray(` Auth: ${config.auth.type}`));
|
|
14655
|
+
console.log(import_chalk22.default.gray(` Rate Limit: ${config.rateLimit.enabled ? "enabled" : "disabled"}`));
|
|
14656
|
+
console.log(import_chalk22.default.gray(` Cache: ${config.cache.enabled ? `${config.cache.ttl}s` : "disabled"}`));
|
|
14657
|
+
console.log();
|
|
14658
|
+
if (config.auth.type === "api-key") {
|
|
14659
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F API Key Authentication Enabled"));
|
|
14660
|
+
console.log(import_chalk22.default.gray(" Use header: X-API-Key: your-api-key"));
|
|
14661
|
+
console.log();
|
|
14662
|
+
}
|
|
14663
|
+
console.log(import_chalk22.default.green("Press Ctrl+C to stop the server"));
|
|
14664
|
+
const shutdown = /* @__PURE__ */ __name(async () => {
|
|
14665
|
+
console.log(import_chalk22.default.yellow("\n\nShutting down server..."));
|
|
14666
|
+
await server.stop();
|
|
14667
|
+
if ((0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14668
|
+
require("fs").unlinkSync(PID_FILE2);
|
|
14669
|
+
}
|
|
14670
|
+
process.exit(0);
|
|
14671
|
+
}, "shutdown");
|
|
14672
|
+
process.on("SIGINT", shutdown);
|
|
14673
|
+
process.on("SIGTERM", shutdown);
|
|
14674
|
+
} catch (error) {
|
|
14675
|
+
console.error(import_chalk22.default.red("\u274C Failed to start server:"), error.message);
|
|
14676
|
+
process.exit(1);
|
|
14677
|
+
}
|
|
14678
|
+
}
|
|
14679
|
+
__name(handleStart2, "handleStart");
|
|
14680
|
+
async function handleStop2() {
|
|
14681
|
+
try {
|
|
14682
|
+
if (!(0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14683
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F Server is not running"));
|
|
14684
|
+
return;
|
|
14685
|
+
}
|
|
14686
|
+
const pid = parseInt((0, import_fs8.readFileSync)(PID_FILE2, "utf-8").trim(), 10);
|
|
14687
|
+
try {
|
|
14688
|
+
process.kill(pid, "SIGTERM");
|
|
14689
|
+
console.log(import_chalk22.default.green("\u2705 Server stopped"));
|
|
14690
|
+
setTimeout(() => {
|
|
14691
|
+
if ((0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14692
|
+
require("fs").unlinkSync(PID_FILE2);
|
|
14693
|
+
}
|
|
14694
|
+
}, 1e3);
|
|
14695
|
+
} catch {
|
|
14696
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F Server process not found"));
|
|
14697
|
+
if ((0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14698
|
+
require("fs").unlinkSync(PID_FILE2);
|
|
14699
|
+
}
|
|
14700
|
+
}
|
|
14701
|
+
} catch (error) {
|
|
14702
|
+
console.error(import_chalk22.default.red("\u274C Failed to stop server:"), error.message);
|
|
14703
|
+
process.exit(1);
|
|
14704
|
+
}
|
|
14705
|
+
}
|
|
14706
|
+
__name(handleStop2, "handleStop");
|
|
14707
|
+
async function handleStatus3() {
|
|
14708
|
+
try {
|
|
14709
|
+
if (!(0, import_fs8.existsSync)(PID_FILE2)) {
|
|
14710
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F Server is not running"));
|
|
14711
|
+
return;
|
|
14712
|
+
}
|
|
14713
|
+
const pid = parseInt((0, import_fs8.readFileSync)(PID_FILE2, "utf-8").trim(), 10);
|
|
14714
|
+
try {
|
|
14715
|
+
process.kill(pid, 0);
|
|
14716
|
+
const config = loadServerConfig();
|
|
14717
|
+
console.log(import_chalk22.default.green("\u2705 Server is running"));
|
|
14718
|
+
console.log(import_chalk22.default.gray(` PID: ${pid}`));
|
|
14719
|
+
console.log(import_chalk22.default.gray(` URL: http://${config.host}:${config.port}`));
|
|
14720
|
+
console.log(import_chalk22.default.gray(` Docs: http://${config.host}:${config.port}/api/docs`));
|
|
14721
|
+
} catch {
|
|
14722
|
+
console.log(import_chalk22.default.yellow("\u26A0\uFE0F Server is not running (stale PID file)"));
|
|
14723
|
+
require("fs").unlinkSync(PID_FILE2);
|
|
14724
|
+
}
|
|
14725
|
+
} catch (error) {
|
|
14726
|
+
console.error(import_chalk22.default.red("\u274C Failed to check status:"), error.message);
|
|
14727
|
+
process.exit(1);
|
|
14728
|
+
}
|
|
14729
|
+
}
|
|
14730
|
+
__name(handleStatus3, "handleStatus");
|
|
14731
|
+
async function handleConfigure2(options) {
|
|
14732
|
+
try {
|
|
14733
|
+
const config = loadServerConfig();
|
|
14734
|
+
if (options.port)
|
|
14735
|
+
config.port = parseInt(options.port, 10);
|
|
14736
|
+
if (options.host)
|
|
14737
|
+
config.host = options.host;
|
|
14738
|
+
if (options.enableCors !== void 0)
|
|
14739
|
+
config.cors.enabled = options.enableCors === "true";
|
|
14740
|
+
if (options.cacheEnabled !== void 0)
|
|
14741
|
+
config.cache.enabled = options.cacheEnabled === "true";
|
|
14742
|
+
if (options.cacheTtl)
|
|
14743
|
+
config.cache.ttl = parseInt(options.cacheTtl, 10);
|
|
14744
|
+
saveServerConfig(config);
|
|
14745
|
+
console.log(import_chalk22.default.green("\u2705 Server configuration updated"));
|
|
14746
|
+
console.log();
|
|
14747
|
+
console.log(JSON.stringify(config, null, 2));
|
|
14748
|
+
} catch (error) {
|
|
14749
|
+
console.error(import_chalk22.default.red("\u274C Failed to configure server:"), error.message);
|
|
14750
|
+
process.exit(1);
|
|
14751
|
+
}
|
|
14752
|
+
}
|
|
14753
|
+
__name(handleConfigure2, "handleConfigure");
|
|
14754
|
+
function registerServerCommands(program) {
|
|
14755
|
+
const server = program.command("server").description("API server mode for REST API access");
|
|
14756
|
+
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);
|
|
14757
|
+
server.command("stop").description("Stop the running API server").action(handleStop2);
|
|
14758
|
+
server.command("status").description("Check server status").action(handleStatus3);
|
|
14759
|
+
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);
|
|
14760
|
+
}
|
|
14761
|
+
__name(registerServerCommands, "registerServerCommands");
|
|
14762
|
+
|
|
13118
14763
|
// src/cli/middleware/auth.ts
|
|
13119
14764
|
async function authMiddleware(thisCommand, actionCommand) {
|
|
13120
14765
|
const isConfigCommand = actionCommand.name() === "config" || actionCommand.parent?.name() === "config";
|
|
@@ -13143,7 +14788,7 @@ async function validationMiddleware(thisCommand, actionCommand) {
|
|
|
13143
14788
|
__name(validationMiddleware, "validationMiddleware");
|
|
13144
14789
|
|
|
13145
14790
|
// src/cli/middleware/error-handler.ts
|
|
13146
|
-
var
|
|
14791
|
+
var import_chalk23 = __toESM(require("chalk"));
|
|
13147
14792
|
function errorHandler(error) {
|
|
13148
14793
|
const message = error?.message ?? String(error);
|
|
13149
14794
|
const stack = error?.stack;
|
|
@@ -13151,15 +14796,15 @@ function errorHandler(error) {
|
|
|
13151
14796
|
return;
|
|
13152
14797
|
}
|
|
13153
14798
|
console.error("");
|
|
13154
|
-
console.error(
|
|
14799
|
+
console.error(import_chalk23.default.red("\u2716"), import_chalk23.default.bold("Error:"), message);
|
|
13155
14800
|
if (process.env.DEBUG || process.env.VERBOSE) {
|
|
13156
14801
|
console.error("");
|
|
13157
14802
|
if (stack) {
|
|
13158
|
-
console.error(
|
|
14803
|
+
console.error(import_chalk23.default.gray(stack));
|
|
13159
14804
|
}
|
|
13160
14805
|
} else {
|
|
13161
14806
|
console.error("");
|
|
13162
|
-
console.error(
|
|
14807
|
+
console.error(import_chalk23.default.gray("Run with --verbose for detailed error information"));
|
|
13163
14808
|
}
|
|
13164
14809
|
console.error("");
|
|
13165
14810
|
}
|
|
@@ -13169,7 +14814,7 @@ __name(errorHandler, "errorHandler");
|
|
|
13169
14814
|
function createCLI() {
|
|
13170
14815
|
const program = new import_commander.Command();
|
|
13171
14816
|
program.exitOverride();
|
|
13172
|
-
program.name("infra-cost").description(
|
|
14817
|
+
program.name("infra-cost").description(import_package.default.description).version(import_package.default.version);
|
|
13173
14818
|
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");
|
|
13174
14819
|
registerNowCommand(program);
|
|
13175
14820
|
registerFreeTierCommand(program);
|
|
@@ -13185,6 +14830,10 @@ function createCLI() {
|
|
|
13185
14830
|
registerGitCommands(program);
|
|
13186
14831
|
registerTerraformCommand(program);
|
|
13187
14832
|
registerSchedulerCommands(program);
|
|
14833
|
+
registerRBACCommands(program);
|
|
14834
|
+
registerSSOCommands(program);
|
|
14835
|
+
registerPluginCommands(program);
|
|
14836
|
+
registerServerCommands(program);
|
|
13188
14837
|
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
13189
14838
|
const opts = thisCommand.opts();
|
|
13190
14839
|
const logLevel = opts.verbose ? "debug" : opts.quiet ? "error" : opts.logLevel;
|