cloud-cost-cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # cloud-cost-cli
2
2
 
3
+ [![npm version](https://badge.fury.io/js/cloud-cost-cli.svg)](https://www.npmjs.com/package/cloud-cost-cli)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
3
6
  **Optimize your cloud spend in seconds.**
4
7
 
5
8
  A command-line tool that analyzes your AWS, GCP, and Azure bills to identify cost-saving opportunities — idle resources, oversized instances, unattached volumes, and more.
@@ -17,11 +17,11 @@ async function scanCommand(options) {
17
17
  (0, logger_1.error)(`Provider "${options.provider}" not yet supported. Use --provider aws`);
18
18
  process.exit(1);
19
19
  }
20
- (0, logger_1.info)(`Scanning AWS account (profile: ${options.profile || 'default'}, region: ${options.region || 'us-east-1'})...`);
21
20
  const client = new client_1.AWSClient({
22
21
  region: options.region,
23
22
  profile: options.profile,
24
23
  });
24
+ (0, logger_1.info)(`Scanning AWS account (profile: ${options.profile || 'default'}, region: ${client.region})...`);
25
25
  // Run analyzers in parallel
26
26
  (0, logger_1.info)('Analyzing EC2 instances...');
27
27
  const ec2Promise = (0, ec2_1.analyzeEC2Instances)(client);
@@ -18,6 +18,7 @@ export declare class AWSClient {
18
18
  region: string;
19
19
  profile: string;
20
20
  constructor(options: AWSClientOptions);
21
+ private getProfileRegion;
21
22
  getEC2Client(): EC2Client;
22
23
  getCloudWatchClient(): CloudWatchClient;
23
24
  getCostExplorerClient(): CostExplorerClient;
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.AWSClient = void 0;
4
37
  const client_ec2_1 = require("@aws-sdk/client-ec2");
@@ -8,6 +41,9 @@ const client_rds_1 = require("@aws-sdk/client-rds");
8
41
  const client_s3_1 = require("@aws-sdk/client-s3");
9
42
  const client_elastic_load_balancing_v2_1 = require("@aws-sdk/client-elastic-load-balancing-v2");
10
43
  const credential_providers_1 = require("@aws-sdk/credential-providers");
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const ini = __importStar(require("ini"));
11
47
  class AWSClient {
12
48
  ec2;
13
49
  cloudwatch;
@@ -18,8 +54,9 @@ class AWSClient {
18
54
  region;
19
55
  profile;
20
56
  constructor(options) {
21
- this.region = options.region || 'us-east-1';
22
57
  this.profile = options.profile || 'default';
58
+ // Determine region: CLI option > profile config > default
59
+ this.region = options.region || this.getProfileRegion(this.profile) || 'us-east-1';
23
60
  // Use environment credentials if available, otherwise fall back to profile
24
61
  const credentials = process.env.AWS_ACCESS_KEY_ID
25
62
  ? (0, credential_providers_1.fromEnv)()
@@ -31,6 +68,28 @@ class AWSClient {
31
68
  this.s3 = new client_s3_1.S3Client({ region: this.region, credentials });
32
69
  this.elb = new client_elastic_load_balancing_v2_1.ElasticLoadBalancingV2Client({ region: this.region, credentials });
33
70
  }
71
+ getProfileRegion(profileName) {
72
+ try {
73
+ const configPath = path.join(process.env.HOME || process.env.USERPROFILE || '', '.aws', 'config');
74
+ if (!fs.existsSync(configPath)) {
75
+ return null;
76
+ }
77
+ const configContent = fs.readFileSync(configPath, 'utf-8');
78
+ const config = ini.parse(configContent);
79
+ // Profile names in config are prefixed with "profile " except for "default"
80
+ const profileKey = profileName === 'default' ? 'default' : `profile ${profileName}`;
81
+ const profileConfig = config[profileKey];
82
+ // Handle both regular objects and null prototype objects
83
+ if (profileConfig && typeof profileConfig === 'object') {
84
+ return profileConfig.region || null;
85
+ }
86
+ return null;
87
+ }
88
+ catch (error) {
89
+ // If we can't read the config, just return null
90
+ return null;
91
+ }
92
+ }
34
93
  getEC2Client() {
35
94
  return this.ec2;
36
95
  }
package/docs/RELEASE.md CHANGED
@@ -16,16 +16,19 @@ npm now uses "Granular Access Tokens" instead of automation tokens.
16
16
  - **Expiration**: 1 year (or No expiration)
17
17
  - **Packages and scopes**: Select packages → Choose `cloud-cost-cli`
18
18
  - **Permissions**: Read and write
19
+ - **⚠️ IMPORTANT**: Under "Additional options" → **Disable "Require two-factor authentication for this token"**
20
+ - This allows GitHub Actions to publish without OTP
21
+ - The token itself is still secure (stored in GitHub Secrets)
19
22
  4. Click **"Generate Token"**
20
23
  5. Copy the token (starts with `npm_...`)
21
24
 
22
25
  ### 2. Add Token to GitHub Secrets
23
26
 
24
27
  1. Go to https://github.com/vuhp/cloud-cost-cli/settings/secrets/actions
25
- 2. Click **"New repository secret"**
28
+ 2. Click **"New repository secret"** (or edit existing `NPM_TOKEN`)
26
29
  3. Name: `NPM_TOKEN`
27
- 4. Value: (paste the `npm_...` token)
28
- 5. Click **"Add secret"**
30
+ 4. Value: (paste the `npm_...` token with 2FA disabled)
31
+ 5. Click **"Add secret"** or **"Update secret"**
29
32
 
30
33
  ## Steps to Release
31
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloud-cost-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Optimize your cloud spend in seconds",
5
5
  "main": "dist/index.js",
6
6
  "bin": {