plutus-scan 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 +11 -0
- package/package.json +1 -1
- package/src/sources/aws.js +9 -6
package/README.md
CHANGED
|
@@ -202,6 +202,17 @@ For one of them, gp2 volumes, Plutus can open a pull request against your Terraf
|
|
|
202
202
|
|
|
203
203
|
plutus-scan is a snapshot of one bill, taken once. [Plutus](https://plutus-cloud.com/?utm_source=plutus-scan&utm_medium=readme) tracks the same spend daily, per team and per customer, with alerts, and is where the questions this tool leaves open get answered: who owns this AI project, is this candidate actually waste, what happens tomorrow. plutus-scan has no paid tier, license key or upload step, and works fully without it. The pull-request line on the gp2 candidate, and the link in the footer of every HTML and Markdown report, are the same kind of pointer: a link you can follow or ignore, never a request plutus-scan makes. It still never contacts plutus-cloud.com.
|
|
204
204
|
|
|
205
|
+
## Releasing
|
|
206
|
+
|
|
207
|
+
Releases are built and uploaded to npm by [`.github/workflows/release.yml`](.github/workflows/release.yml) when a GitHub release is published, not from a laptop. The workflow only stages the version; a maintainer makes it public with 2FA.
|
|
208
|
+
|
|
209
|
+
1. Bump `version` in `package.json` and `package-lock.json` with `npm version 0.1.1 --no-git-tag-version`, open a pull request and merge it to `main`.
|
|
210
|
+
2. On GitHub, draft a new release from `main` with a new tag named `v` plus the version (`v0.1.1`), write the notes and publish it. A version with a suffix such as `0.2.0-rc.1` is published under the npm `next` tag instead of `latest`.
|
|
211
|
+
3. The workflow checks that the tag matches `package.json`, runs `npm ci` and `npm test`, checks that the tarball holds only `bin`, `src`, `LICENSE`, `package.json` and `README.md`, and stages it on npm with provenance (`npm stage publish`). If any check fails nothing is staged: fix it, delete the release and its tag, and publish again.
|
|
212
|
+
4. Approve it from a machine signed in to npm with 2FA: `npm stage list plutus-scan` shows the staged version and its id, `npm stage download <stage-id>` fetches the tarball if you want to inspect it, and `npm stage approve <stage-id>` publishes it. `npm stage reject <stage-id>` throws it away. The staging commands need npm 12 or later.
|
|
213
|
+
|
|
214
|
+
One-time setup: the workflow uses npm trusted publishing, so there is no npm token to store. On npmjs.com, under the package's Settings, add a trusted publisher for GitHub Actions with organization `plutus-cloud`, repository `plutus-scan`, workflow `release.yml` and environment `npm`, and leave the options to publish directly and to manage dist-tags unchecked, so the workflow can only stage. Then set the package's publishing access to require two-factor authentication and disallow tokens.
|
|
215
|
+
|
|
205
216
|
## License
|
|
206
217
|
|
|
207
218
|
MIT. See `LICENSE`. The FOCUS sample used as a test fixture is CC BY 4.0, credited in `test/fixtures/focus/official-sample.NOTICE.md`.
|
package/package.json
CHANGED
package/src/sources/aws.js
CHANGED
|
@@ -44,6 +44,9 @@ const COMMITMENT_TYPES = new Set(COMMITMENT_RECORD_TYPES.map(norm));
|
|
|
44
44
|
const RESERVATION_USAGE = /(^|[-:])HeavyUsage|ReservedHostUsage/;
|
|
45
45
|
/** Cost Explorer expression that leaves out the commitment record types. */
|
|
46
46
|
const NOT_COMMITMENT = { Not: { Dimensions: { Key: 'RECORD_TYPE', Values: COMMITMENT_RECORD_TYPES } } };
|
|
47
|
+
/** The commitment fees plus the record types that never carry a tag. */
|
|
48
|
+
const NOT_TAGGABLE = { Not: { Dimensions: { Key: 'RECORD_TYPE',
|
|
49
|
+
Values: [...COMMITMENT_RECORD_TYPES, 'SavingsPlanNegation', 'Tax', 'Credit', 'Refund'] } } };
|
|
47
50
|
|
|
48
51
|
/**
|
|
49
52
|
* Kind and readable service label for one daily SERVICE x RECORD_TYPE group. Savings Plan fees keep
|
|
@@ -82,13 +85,13 @@ export function shellQuote(arg) {
|
|
|
82
85
|
const period = (p) => `Start=${p.start},End=${addDays(p.end, 1)}`;
|
|
83
86
|
const dim = (key) => `Type=DIMENSION,Key=${key}`;
|
|
84
87
|
|
|
85
|
-
/** @param {{start:string,end:string}} p @param {'DAILY'|'MONTHLY'} granularity @param {string[]} groupBy @param {string|null} [filter] @param {string|null} [token] */
|
|
86
|
-
function costArgs(p, granularity, groupBy, filter = null, token = null) {
|
|
88
|
+
/** @param {{start:string,end:string}} p @param {'DAILY'|'MONTHLY'} granularity @param {string[]} groupBy @param {string|null} [filter] @param {string|null} [token] @param {'UnblendedCost'|'AmortizedCost'} [metric] */
|
|
89
|
+
function costArgs(p, granularity, groupBy, filter = null, token = null, metric = 'UnblendedCost') {
|
|
87
90
|
return [
|
|
88
91
|
'ce', 'get-cost-and-usage',
|
|
89
92
|
'--time-period', period(p),
|
|
90
93
|
'--granularity', granularity,
|
|
91
|
-
'--metrics',
|
|
94
|
+
'--metrics', metric,
|
|
92
95
|
...(filter ? ['--filter', filter] : []),
|
|
93
96
|
'--group-by', ...groupBy,
|
|
94
97
|
...(token ? ['--next-page-token', token] : []),
|
|
@@ -113,9 +116,9 @@ export const candidateArgs = (/** @type {Window} */ w, token = null) =>
|
|
|
113
116
|
|
|
114
117
|
export const tagListArgs = () => ['ce', 'list-cost-allocation-tags', '--status', 'Active', '--type', 'UserDefined'];
|
|
115
118
|
|
|
116
|
-
/** Usage only,
|
|
119
|
+
/** Usage only, and AmortizedCost unlike every other query: see docs/design.md, step 5. */
|
|
117
120
|
export const tagArgs = (/** @type {Window} */ w, /** @type {string} */ key, token = null) =>
|
|
118
|
-
costArgs(candidatePeriod(w), 'MONTHLY', [`Type=TAG,Key=${key}`], JSON.stringify(
|
|
121
|
+
costArgs(candidatePeriod(w), 'MONTHLY', [`Type=TAG,Key=${key}`], JSON.stringify(NOT_TAGGABLE), token, 'AmortizedCost');
|
|
119
122
|
|
|
120
123
|
/** @param {Window} w */
|
|
121
124
|
const clampStart = (w, n) => {
|
|
@@ -244,7 +247,7 @@ export async function fetchAws(transport, { window, maxTagKeys = 5, maxRequests
|
|
|
244
247
|
|
|
245
248
|
/** @param {any} g @returns {{ cost: number, currency: string }|null} null for a zero or missing amount */
|
|
246
249
|
const amount = (g) => {
|
|
247
|
-
const m = g.Metrics?.UnblendedCost;
|
|
250
|
+
const m = g.Metrics?.UnblendedCost ?? g.Metrics?.AmortizedCost;
|
|
248
251
|
const cost = Number(m?.Amount);
|
|
249
252
|
return !Number.isFinite(cost) || cost === 0 ? null : { cost, currency: currencyCode(m.Unit || 'USD') };
|
|
250
253
|
};
|