Thank you for contributing. Coverage extensions (new instance types, regions, or a second cloud provider) are the fastest PRs to merge.
- Local Setup
- Running Tests
- Working with Fixtures
- Adding Instance Types
- Adding a New Region
- Adding a New Cloud Provider
- Commit Convention
- Code of Conduct
Requirements: Node.js ≥ 20 and npm.
git clone https://github.com/omrdev1/greenops-cli.git
cd greenops-cli
npm ciBuild the CLI:
npm run build
node dist/index.cjs --version
node dist/index.cjs --coverageTypecheck:
npx tsc --noEmit# Full test suite (116 tests across engine, extractor, formatters, policy, serverless, node groups)
npm test
# Run a single test file
npx tsx --test engine.test.tsThe test runner is the Node.js native test runner (no Jest, no Vitest). Tests use node:assert and node:test.
Verify the build matches committed dist:
npm run build
git diff --exit-code dist/index.cjs # must be cleanThe CI release pipeline enforces this check. A stale or manually-patched bundle will fail.
The fixtures/ directory contains synthetic Terraform plan JSON files used for E2E testing and local development. All fixtures are fully synthetic. None contain real account IDs, ARNs, or IP addresses.
| File | Provider | Purpose |
|---|---|---|
tfplan.e2e.json |
AWS | E2E CI fixture, 2 instances, produces recommendations |
tfplan.azure.e2e.json |
Azure | E2E CI fixture, tests Azure extraction |
tfplan.gcp.e2e.json |
GCP | E2E CI fixture, tests GCP extraction |
tfplan.eks.e2e.json |
AWS | E2E CI fixture, EKS node group with autoscaling, tests node count scaling |
tfplan.saas-demo.json |
AWS | Larger 10-resource demo (web, API, DB, workers) for manual testing |
Run all four E2E fixtures locally:
npm run build
# AWS
node dist/index.cjs diff fixtures/tfplan.e2e.json --format table
# Azure
node dist/index.cjs diff fixtures/tfplan.azure.e2e.json --format table
# GCP
node dist/index.cjs diff fixtures/tfplan.gcp.e2e.json --format table
# AWS EKS node group
node dist/index.cjs diff fixtures/tfplan.eks.e2e.json --format table
# Larger AWS demo (useful for testing recommendation output)
node dist/index.cjs diff fixtures/tfplan.saas-demo.json --format tableRun the markdown formatter (simulates the GitHub PR comment):
node dist/index.cjs diff fixtures/tfplan.e2e.json --format markdown --show-upgrade-prompt=falseAll compute factors live in factors.json. Each provider has an instances object.
Step 1: Find power data
Source idle and max TDP from the Cloud Carbon Footprint coefficients spreadsheet. For ARM instances apply the 0.80 embodied multiplier.
Step 2: Add to factors.json
// AWS example, add under aws.instances
"m7g.large": {
"architecture": "arm64",
"vcpus": 2,
"memory_gb": 8,
"power_watts": { "idle": 3.05, "max": 7.66 },
"embodied_co2e_grams_per_month": 14221
}Embodied formula (documented in METHODOLOGY.md):
embodied_g/month = (1,200,000g / 35,040h / 48 vCPUs) × vcpus × 730h × arm_discount
Step 3: Add pricing under aws.pricing_usd_per_hour for each region where the instance is available. Prices from the AWS pricing page.
Step 4: Add a test case in engine.test.ts with a math trace comment:
it('m7g.large in eu-west-1: ARM64 baseline', () => {
// idle=3.05W, max=7.66W, util=0.50, mem=8GB
// cpu_watts = 3.05 + (7.66-3.05)*0.50 = 5.355W
// mem_watts = 8 * 0.392 = 3.136W
// total_w = 8.491W
// energy = 8.491 * 1.13 * 730 / 1000 = 7.013 kWh
// co2e = 7.013 * 233 = 1634g
const result = calculateBaseline({ resourceId: 'x', instanceType: 'm7g.large', region: 'eu-west-1', provider: 'aws' });
assert.ok(Math.abs(result.totalCo2eGramsPerMonth - 1634) < 50);
});Step 5: Verify:
npm run build && node dist/index.cjs --coverage
npm testThe same pattern applies to Azure (azure.instances) and GCP (gcp.instances); the field names are identical.
Step 1: Find grid intensity
Use Electricity Maps annual average CO2 intensity (gCO2e/kWh) for the grid zone corresponding to the data centre region.
Step 2: Find WUE (water intensity)
From the provider's annual sustainability report:
- AWS: AWS Sustainability Report
- Azure: Microsoft Sustainability Report
- GCP: Google Environmental Report
Step 3: Add to factors.json under the provider's regions object:
// AWS example
"ap-northeast-1": {
"location": "Asia Pacific (Tokyo)",
"grid_intensity_gco2e_per_kwh": 463,
"pue": 1.13,
"water_intensity_litres_per_kwh": 0.18
}PUE values: AWS 1.13, Azure 1.125, GCP 1.10 (provider-wide averages; per-region data is not publicly disclosed).
Step 4: Add pricing for all existing instances in that region under pricing_usd_per_hour.
Step 5: Verify:
npm run build && node dist/index.cjs --coverageThe new region should appear in the count.
Adding a fourth provider (e.g. Oracle Cloud, Alibaba Cloud) requires changes across four files:
Add the new provider to the CloudProvider union:
export type CloudProvider = 'aws' | 'azure' | 'gcp' | 'oci';Add a top-level provider block following the same schema as aws, azure, gcp:
{
"oci": {
"regions": { ... },
"instances": { ... },
"pricing_usd_per_hour": { ... }
}
}Add to SUPPORTED_TYPES:
oci: ['oci_core_instance'],Add a detectProvider branch:
if (resourceType.startsWith('oci_')) return 'oci';Add region resolution logic (follow the resolveAwsRegion / resolveAzureRegion pattern).
Add instance type extraction logic (follow extractAwsInstanceType / extractGcpInstanceType pattern).
Add to ARM_UPGRADE_MAP:
oci: {
'VM.Standard.E4.Flex': 'VM.Standard.A1.Flex',
}Add test cases in extractor.test.ts (plan extraction), engine.test.ts (CO2e calculation), and add an E2E fixture in fixtures/tfplan.oci.e2e.json.
Add a step for the new fixture.
This repo uses Conventional Commits:
feat: add m7g.xlarge to AWS eu-west-1
fix: correct r6g embodied CO2e calculation
chore: bump to v0.9.0
docs: update METHODOLOGY.md water intensity sources
test: add extractor test for known_after_apply instance_type
Types: feat, fix, docs, test, chore, refactor, perf
Commit messages that touch factors.json should name the specific instance type or region added.
This project follows the Contributor Covenant v2.1. By participating, you agree to uphold this code. Please report unacceptable behaviour to security@getgreenops.com.