@deployanyway/ship-it-meter 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/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
- # Changelog
2
-
3
- ## 0.1.0 — Unreleased
4
-
5
- - Deterministic readiness score with explanations and technical blocker caps.
6
- - CLI, documented scoring rules, tests, linting, formatting, and Node 22/24 CI.
1
+ # Changelog
2
+
3
+ ## 0.1.1 — 2026-10-08
4
+
5
+ - Correct npm installation and npx documentation after the initial publication.
6
+ - Add a searchable, humorous package description and relevant npm keywords.
7
+ - No API, CLI behavior, or dependency changes.
8
+
9
+ ## 0.1.0 — 2026-10-08
10
+
11
+ - Deterministic readiness score with explanations and technical blocker caps.
12
+ - CLI, documented scoring rules, tests, linting, formatting, and Node 22/24 CI.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Deploy Anyway
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Deploy Anyway
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,151 +1,152 @@
1
- # ship-it-meter
2
-
3
- A humorous, deterministic deployment readiness score that shows its homework.
4
-
5
- ```text
6
- 59/100 — Questionable
7
- - 2 of 125 tests are failing (-21)
8
- - Coverage is 82% (-4)
9
- - Build passed
10
- - Friday: the weekend would like a quiet entrance (-3)
11
- - Failing tests cap the score at 59
12
- ```
13
-
14
- ## Installation
15
-
16
- Version 0.1.0 is not published to npm. Try from source with Node 22 or 24:
17
-
18
- ```sh
19
- git clone https://github.com/DeployAnyway/ship-it-meter.git
20
- cd ship-it-meter
21
- git checkout feature/initial-mvp
22
- npm ci
23
- node examples/basic.js
24
- ```
25
-
26
- After an approved release: `npm install @deployanyway/ship-it-meter`.
27
-
28
- ## Quick start
29
-
30
- ```js
31
- import { shipIt } from "@deployanyway/ship-it-meter";
32
-
33
- const result = shipIt({
34
- tests: 125,
35
- failingTests: 2,
36
- coverage: 82,
37
- day: "Friday",
38
- branch: "main",
39
- build: true,
40
- });
41
- console.log(result); // { score: 59, verdict: 'Questionable', reasons: [...] }
42
- ```
43
-
44
- ## CLI example
45
-
46
- ```sh
47
- node bin/cli.js --tests 125 --failing 2 --coverage 82 --build pass --day Friday
48
- ```
49
-
50
- After publication: `npx @deployanyway/ship-it-meter --tests 125 --coverage 82 --build pass`.
51
-
52
- ## API and inputs
53
-
54
- `shipIt(input = {})` returns `{ score, verdict, reasons }`. It never reads the
55
- filesystem, branch, clock, tests, CI, or issue tracker; all evidence is supplied
56
- by the caller. It never logs or exits. Inputs are not mutated and results are fresh.
57
-
58
- | Input | Meaning and default |
59
- | -------------------- | ----------------------------------------------------------------- |
60
- | `tests` | Nonnegative safe integer; missing/zero penalized |
61
- | `failingTests` | Nonnegative safe integer, default 0; cannot exceed tests |
62
- | `coverage` | Finite percentage 0–100; missing penalized |
63
- | `build` | Boolean; missing penalized |
64
- | `criticalIssues` | Nonnegative safe integer, default 0 |
65
- | `lintFailures` | Nonnegative safe integer, default 0 |
66
- | `uncommittedChanges` | Boolean, default false |
67
- | `branch` | Nonempty string; omitted means no branch penalty |
68
- | `day` | Full weekday name, case insensitive; omitted means no day penalty |
69
-
70
- Positive failingTests requires tests. Numeric strings are not library numbers.
71
- Invalid types or unknown keys throw TypeError; invalid ranges, counts, day names,
72
- or inconsistent test totals throw RangeError. Unknown optional issue/lint/dirty
73
- data is treated as no reported problems, not verified clean evidence.
74
-
75
- ## Scoring algorithm (0.1.0)
76
-
77
- Start at 100. Subtract the following, then clamp to 0–100 and apply blocker caps:
78
-
79
- | Condition | Penalty |
80
- | ---------------------------------------------------------------- | --------------------------------------------- |
81
- | Missing test count or zero tests | 25 |
82
- | Failing tests | min(50, 20 + ceil(30 × failingTests / tests)) |
83
- | Missing coverage | 15 |
84
- | Known coverage | ceil((100 − coverage) × 0.2) |
85
- | Missing build status | 15 |
86
- | Failed build | 40 |
87
- | Critical issues | min(50, 25 × count) |
88
- | Lint failures | min(20, 5 × count) |
89
- | Uncommitted changes | 10 |
90
- | Supplied branch other than main/master (case sensitive, trimmed) | 5 |
91
- | Friday | 3 |
92
-
93
- Failed builds or any critical issue cap the final score at **29**. Otherwise,
94
- any failing test caps it at **59**. Friday is only a small joke; technical blockers
95
- always dominate. Every applied penalty/cap appears in reasons, alongside reported
96
- passing tests/build. Caps are ceilings, not additional deductions.
97
-
98
- | Final score | Verdict |
99
- | ----------- | ------------------ |
100
- | 0–29 | Absolutely Not |
101
- | 30–59 | Questionable |
102
- | 60–79 | Probably Fine |
103
- | 80–94 | Ship It |
104
- | 95–100 | Suspiciously Ready |
105
-
106
- `shipIt()` scores 45 (Questionable) because key evidence is missing. A passing
107
- build, nonzero tests with no failures, and 100% coverage score 100. This is an
108
- illustrative heuristic, not a deployment gate or a guarantee of reliability.
109
- Its assumptions and scoring may evolve between versions; pin versions for repeatable reports.
110
-
111
- ## CLI reference
112
-
113
- Flags: `--tests`, `--failing`, `--coverage`, `--critical`, `--lint` take numbers.
114
- `--build pass|fail`, `--day weekday`, and `--branch name` take text. `--dirty`
115
- reports uncommitted changes. `--json` emits the structured result. `--help`/`-h`
116
- and `--version`/`-v` show help/version. No positional arguments or stdin support.
117
-
118
- Exit 0 means scoring succeeded, even for Absolutely Not. Exit 2 means invalid
119
- arguments. Consumers choose their own deployment thresholds.
120
-
121
- ## Development and examples
122
-
123
- ```sh
124
- npm ci
125
- node examples/basic.js
126
- npm test
127
- npm run lint
128
- npm run format:check
129
- npm pack --dry-run
130
- ```
131
-
132
- ES modules, zero production dependencies, Node's test runner. CI runs Node 22/24;
133
- development tooling requires Node 22.13+ or 24.
134
-
135
- ## Contributing
136
-
137
- See [CONTRIBUTING.md](CONTRIBUTING.md).
138
-
139
- ## License
140
-
141
- [MIT](LICENSE).
142
-
143
- ## More from DeployAnyway
144
-
145
- **Tools for developers who probably know better.**
146
-
147
- - [error-translator](https://github.com/DeployAnyway/error-translator)
148
- - [excuse-js](https://github.com/DeployAnyway/excuse-js)
149
- - [doggo-log](https://github.com/DeployAnyway/doggo-log)
150
- - [ship-it-meter](https://github.com/DeployAnyway/ship-it-meter)
151
- - [bro-say](https://github.com/DeployAnyway/bro-say)
1
+ # ship-it-meter
2
+
3
+ Explainable deployment readiness scoring for tests, coverage, and builds. Turns questionable confidence into a number.
4
+
5
+ ```text
6
+ 59/100 — Questionable
7
+ - 2 of 125 tests are failing (-21)
8
+ - Coverage is 82% (-4)
9
+ - Build passed
10
+ - Friday: the weekend would like a quiet entrance (-3)
11
+ - Failing tests cap the score at 59
12
+ ```
13
+
14
+ ## Installation
15
+
16
+ Available on npm. Requires Node 22 or later.
17
+ You can also run from source with Node 22 or 24:
18
+
19
+ ```sh
20
+ git clone https://github.com/DeployAnyway/ship-it-meter.git
21
+ cd ship-it-meter
22
+ git checkout main
23
+ npm ci
24
+ node examples/basic.js
25
+ ```
26
+
27
+ Install from npm: `npm install @deployanyway/ship-it-meter`.
28
+
29
+ ## Quick start
30
+
31
+ ```js
32
+ import { shipIt } from "@deployanyway/ship-it-meter";
33
+
34
+ const result = shipIt({
35
+ tests: 125,
36
+ failingTests: 2,
37
+ coverage: 82,
38
+ day: "Friday",
39
+ branch: "main",
40
+ build: true,
41
+ });
42
+ console.log(result); // { score: 59, verdict: 'Questionable', reasons: [...] }
43
+ ```
44
+
45
+ ## CLI example
46
+
47
+ ```sh
48
+ node bin/cli.js --tests 125 --failing 2 --coverage 82 --build pass --day Friday
49
+ ```
50
+
51
+ Run with npx: `npx @deployanyway/ship-it-meter --tests 125 --coverage 82 --build pass`.
52
+
53
+ ## API and inputs
54
+
55
+ `shipIt(input = {})` returns `{ score, verdict, reasons }`. It never reads the
56
+ filesystem, branch, clock, tests, CI, or issue tracker; all evidence is supplied
57
+ by the caller. It never logs or exits. Inputs are not mutated and results are fresh.
58
+
59
+ | Input | Meaning and default |
60
+ | -------------------- | ----------------------------------------------------------------- |
61
+ | `tests` | Nonnegative safe integer; missing/zero penalized |
62
+ | `failingTests` | Nonnegative safe integer, default 0; cannot exceed tests |
63
+ | `coverage` | Finite percentage 0–100; missing penalized |
64
+ | `build` | Boolean; missing penalized |
65
+ | `criticalIssues` | Nonnegative safe integer, default 0 |
66
+ | `lintFailures` | Nonnegative safe integer, default 0 |
67
+ | `uncommittedChanges` | Boolean, default false |
68
+ | `branch` | Nonempty string; omitted means no branch penalty |
69
+ | `day` | Full weekday name, case insensitive; omitted means no day penalty |
70
+
71
+ Positive failingTests requires tests. Numeric strings are not library numbers.
72
+ Invalid types or unknown keys throw TypeError; invalid ranges, counts, day names,
73
+ or inconsistent test totals throw RangeError. Unknown optional issue/lint/dirty
74
+ data is treated as no reported problems, not verified clean evidence.
75
+
76
+ ## Scoring algorithm (0.1.0)
77
+
78
+ Start at 100. Subtract the following, then clamp to 0–100 and apply blocker caps:
79
+
80
+ | Condition | Penalty |
81
+ | ---------------------------------------------------------------- | --------------------------------------------- |
82
+ | Missing test count or zero tests | 25 |
83
+ | Failing tests | min(50, 20 + ceil(30 × failingTests / tests)) |
84
+ | Missing coverage | 15 |
85
+ | Known coverage | ceil((100 − coverage) × 0.2) |
86
+ | Missing build status | 15 |
87
+ | Failed build | 40 |
88
+ | Critical issues | min(50, 25 × count) |
89
+ | Lint failures | min(20, 5 × count) |
90
+ | Uncommitted changes | 10 |
91
+ | Supplied branch other than main/master (case sensitive, trimmed) | 5 |
92
+ | Friday | 3 |
93
+
94
+ Failed builds or any critical issue cap the final score at **29**. Otherwise,
95
+ any failing test caps it at **59**. Friday is only a small joke; technical blockers
96
+ always dominate. Every applied penalty/cap appears in reasons, alongside reported
97
+ passing tests/build. Caps are ceilings, not additional deductions.
98
+
99
+ | Final score | Verdict |
100
+ | ----------- | ------------------ |
101
+ | 0–29 | Absolutely Not |
102
+ | 30–59 | Questionable |
103
+ | 60–79 | Probably Fine |
104
+ | 80–94 | Ship It |
105
+ | 95–100 | Suspiciously Ready |
106
+
107
+ `shipIt()` scores 45 (Questionable) because key evidence is missing. A passing
108
+ build, nonzero tests with no failures, and 100% coverage score 100. This is an
109
+ illustrative heuristic, not a deployment gate or a guarantee of reliability.
110
+ Its assumptions and scoring may evolve between versions; pin versions for repeatable reports.
111
+
112
+ ## CLI reference
113
+
114
+ Flags: `--tests`, `--failing`, `--coverage`, `--critical`, `--lint` take numbers.
115
+ `--build pass|fail`, `--day weekday`, and `--branch name` take text. `--dirty`
116
+ reports uncommitted changes. `--json` emits the structured result. `--help`/`-h`
117
+ and `--version`/`-v` show help/version. No positional arguments or stdin support.
118
+
119
+ Exit 0 means scoring succeeded, even for Absolutely Not. Exit 2 means invalid
120
+ arguments. Consumers choose their own deployment thresholds.
121
+
122
+ ## Development and examples
123
+
124
+ ```sh
125
+ npm ci
126
+ node examples/basic.js
127
+ npm test
128
+ npm run lint
129
+ npm run format:check
130
+ npm pack --dry-run
131
+ ```
132
+
133
+ ES modules, zero production dependencies, Node's test runner. CI runs Node 22/24;
134
+ development tooling requires Node 22.13+ or 24.
135
+
136
+ ## Contributing
137
+
138
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
139
+
140
+ ## License
141
+
142
+ [MIT](LICENSE).
143
+
144
+ ## More from DeployAnyway
145
+
146
+ **Tools for developers who probably know better.**
147
+
148
+ - [error-translator](https://github.com/DeployAnyway/error-translator)
149
+ - [excuse-js](https://github.com/DeployAnyway/excuse-js)
150
+ - [doggo-log](https://github.com/DeployAnyway/doggo-log)
151
+ - [ship-it-meter](https://github.com/DeployAnyway/ship-it-meter)
152
+ - [bro-say](https://github.com/DeployAnyway/bro-say)
package/bin/cli.js CHANGED
@@ -1,67 +1,67 @@
1
- #!/usr/bin/env node
2
- import { parseArgs } from "node:util";
3
- import { readFileSync } from "node:fs";
4
- import { URL } from "node:url";
5
- import { shipIt } from "../src/index.js";
6
-
7
- try {
8
- const { values } = parseArgs({
9
- options: {
10
- help: { type: "boolean", short: "h" },
11
- version: { type: "boolean", short: "v" },
12
- json: { type: "boolean" },
13
- tests: { type: "string" },
14
- failing: { type: "string" },
15
- coverage: { type: "string" },
16
- day: { type: "string" },
17
- branch: { type: "string" },
18
- critical: { type: "string" },
19
- lint: { type: "string" },
20
- build: { type: "string" },
21
- dirty: { type: "boolean" },
22
- },
23
- });
24
- if (values.help) {
25
- console.log(
26
- "Usage: ship-it-meter [options]\n\nOptions:\n --tests number Total tests\n --failing number Failing tests\n --coverage number Coverage percent\n --build pass|fail Build result\n --critical number Open critical issues\n --lint number Lint failures\n --dirty Uncommitted changes\n --branch name Branch name\n --day weekday Full weekday name\n --json Structured output\n -h, --help Help\n -v, --version Version\n\nMissing test, coverage, and build evidence reduces readiness.\nExit codes: 0 scored successfully; 2 invalid arguments.",
27
- );
28
- } else if (values.version) {
29
- console.log(
30
- JSON.parse(
31
- readFileSync(new URL("../package.json", import.meta.url), "utf8"),
32
- ).version,
33
- );
34
- } else {
35
- const input = {};
36
- for (const [flag, key] of Object.entries({
37
- tests: "tests",
38
- failing: "failingTests",
39
- coverage: "coverage",
40
- critical: "criticalIssues",
41
- lint: "lintFailures",
42
- })) {
43
- if (values[flag] !== undefined) {
44
- if (!/^\d+(?:\.\d+)?$/.test(values[flag]))
45
- throw new TypeError(`${flag} must be a nonnegative decimal number.`);
46
- input[key] = Number(values[flag]);
47
- }
48
- }
49
- if (values.build !== undefined) {
50
- if (!["pass", "fail"].includes(values.build))
51
- throw new RangeError("--build must be pass or fail.");
52
- input.build = values.build === "pass";
53
- }
54
- for (const key of ["day", "branch"])
55
- if (values[key] !== undefined) input[key] = values[key];
56
- if (values.dirty) input.uncommittedChanges = true;
57
- const result = shipIt(input);
58
- console.log(
59
- values.json
60
- ? JSON.stringify(result, null, 2)
61
- : `${result.score}/100 — ${result.verdict}\n${result.reasons.map((reason) => `- ${reason}`).join("\n")}`,
62
- );
63
- }
64
- } catch (error) {
65
- console.error(`ship-it-meter: ${error.message}\nRun with --help for usage.`);
66
- process.exitCode = 2;
67
- }
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from "node:util";
3
+ import { readFileSync } from "node:fs";
4
+ import { URL } from "node:url";
5
+ import { shipIt } from "../src/index.js";
6
+
7
+ try {
8
+ const { values } = parseArgs({
9
+ options: {
10
+ help: { type: "boolean", short: "h" },
11
+ version: { type: "boolean", short: "v" },
12
+ json: { type: "boolean" },
13
+ tests: { type: "string" },
14
+ failing: { type: "string" },
15
+ coverage: { type: "string" },
16
+ day: { type: "string" },
17
+ branch: { type: "string" },
18
+ critical: { type: "string" },
19
+ lint: { type: "string" },
20
+ build: { type: "string" },
21
+ dirty: { type: "boolean" },
22
+ },
23
+ });
24
+ if (values.help) {
25
+ console.log(
26
+ "Usage: ship-it-meter [options]\n\nOptions:\n --tests number Total tests\n --failing number Failing tests\n --coverage number Coverage percent\n --build pass|fail Build result\n --critical number Open critical issues\n --lint number Lint failures\n --dirty Uncommitted changes\n --branch name Branch name\n --day weekday Full weekday name\n --json Structured output\n -h, --help Help\n -v, --version Version\n\nMissing test, coverage, and build evidence reduces readiness.\nExit codes: 0 scored successfully; 2 invalid arguments.",
27
+ );
28
+ } else if (values.version) {
29
+ console.log(
30
+ JSON.parse(
31
+ readFileSync(new URL("../package.json", import.meta.url), "utf8"),
32
+ ).version,
33
+ );
34
+ } else {
35
+ const input = {};
36
+ for (const [flag, key] of Object.entries({
37
+ tests: "tests",
38
+ failing: "failingTests",
39
+ coverage: "coverage",
40
+ critical: "criticalIssues",
41
+ lint: "lintFailures",
42
+ })) {
43
+ if (values[flag] !== undefined) {
44
+ if (!/^\d+(?:\.\d+)?$/.test(values[flag]))
45
+ throw new TypeError(`${flag} must be a nonnegative decimal number.`);
46
+ input[key] = Number(values[flag]);
47
+ }
48
+ }
49
+ if (values.build !== undefined) {
50
+ if (!["pass", "fail"].includes(values.build))
51
+ throw new RangeError("--build must be pass or fail.");
52
+ input.build = values.build === "pass";
53
+ }
54
+ for (const key of ["day", "branch"])
55
+ if (values[key] !== undefined) input[key] = values[key];
56
+ if (values.dirty) input.uncommittedChanges = true;
57
+ const result = shipIt(input);
58
+ console.log(
59
+ values.json
60
+ ? JSON.stringify(result, null, 2)
61
+ : `${result.score}/100 — ${result.verdict}\n${result.reasons.map((reason) => `- ${reason}`).join("\n")}`,
62
+ );
63
+ }
64
+ } catch (error) {
65
+ console.error(`ship-it-meter: ${error.message}\nRun with --help for usage.`);
66
+ process.exitCode = 2;
67
+ }
package/package.json CHANGED
@@ -1,41 +1,52 @@
1
- {
2
- "name": "@deployanyway/ship-it-meter",
3
- "version": "0.1.0",
4
- "description": "A humorous, deterministic and explainable deployment readiness score.",
5
- "type": "module",
6
- "exports": "./src/index.js",
7
- "bin": {
8
- "ship-it-meter": "./bin/cli.js"
9
- },
10
- "files": [
11
- "src",
12
- "bin",
13
- "README.md",
14
- "LICENSE",
15
- "CHANGELOG.md"
16
- ],
17
- "engines": {
18
- "node": ">=22"
19
- },
20
- "scripts": {
21
- "test": "node --test",
22
- "lint": "eslint .",
23
- "format": "prettier --write .",
24
- "format:check": "prettier --check ."
25
- },
26
- "repository": {
27
- "type": "git",
28
- "url": "git+https://github.com/DeployAnyway/ship-it-meter.git"
29
- },
30
- "bugs": {
31
- "url": "https://github.com/DeployAnyway/ship-it-meter/issues"
32
- },
33
- "license": "MIT",
34
- "publishConfig": {
35
- "access": "public"
36
- },
37
- "devDependencies": {
38
- "eslint": "^10.12.0",
39
- "prettier": "^3.6.2"
40
- }
41
- }
1
+ {
2
+ "name": "@deployanyway/ship-it-meter",
3
+ "version": "0.1.1",
4
+ "description": "Explainable deployment readiness scoring for tests, coverage, and builds. Turns questionable confidence into a number.",
5
+ "type": "module",
6
+ "exports": "./src/index.js",
7
+ "bin": {
8
+ "ship-it-meter": "./bin/cli.js"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "bin",
13
+ "README.md",
14
+ "LICENSE",
15
+ "CHANGELOG.md"
16
+ ],
17
+ "engines": {
18
+ "node": ">=22"
19
+ },
20
+ "scripts": {
21
+ "test": "node --test",
22
+ "lint": "eslint .",
23
+ "format": "prettier --write .",
24
+ "format:check": "prettier --check ."
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/DeployAnyway/ship-it-meter.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/DeployAnyway/ship-it-meter/issues"
32
+ },
33
+ "license": "MIT",
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "devDependencies": {
38
+ "eslint": "^10.12.0",
39
+ "prettier": "^3.6.2"
40
+ },
41
+ "keywords": [
42
+ "nodejs",
43
+ "javascript",
44
+ "cli",
45
+ "developer-tools",
46
+ "deployment",
47
+ "readiness",
48
+ "test-coverage",
49
+ "build",
50
+ "scoring"
51
+ ]
52
+ }
package/src/index.js CHANGED
@@ -1,125 +1,125 @@
1
- const days = [
2
- "monday",
3
- "tuesday",
4
- "wednesday",
5
- "thursday",
6
- "friday",
7
- "saturday",
8
- "sunday",
9
- ];
10
- const counts = ["tests", "failingTests", "criticalIssues", "lintFailures"];
11
- const allowed = [
12
- ...counts,
13
- "coverage",
14
- "build",
15
- "uncommittedChanges",
16
- "branch",
17
- "day",
18
- ];
19
-
20
- /**
21
- * Calculate a stateless readiness score. Missing evidence reduces the score.
22
- * @param {{tests?: number, failingTests?: number, coverage?: number, build?: boolean, uncommittedChanges?: boolean, branch?: string, day?: string, criticalIssues?: number, lintFailures?: number}} [input]
23
- * @returns {{score: number, verdict: string, reasons: string[]}}
24
- */
25
- export function shipIt(input = {}) {
26
- if (!input || typeof input !== "object" || Array.isArray(input))
27
- throw new TypeError("Input must be an object.");
28
- for (const key of Object.keys(input))
29
- if (!allowed.includes(key)) throw new TypeError(`Unknown input: ${key}.`);
30
- for (const key of counts) {
31
- if (
32
- input[key] !== undefined &&
33
- (!Number.isSafeInteger(input[key]) || input[key] < 0)
34
- )
35
- throw new RangeError(`${key} must be a nonnegative safe integer.`);
36
- }
37
- if (
38
- input.coverage !== undefined &&
39
- (typeof input.coverage !== "number" ||
40
- !Number.isFinite(input.coverage) ||
41
- input.coverage < 0 ||
42
- input.coverage > 100)
43
- )
44
- throw new RangeError("coverage must be between 0 and 100.");
45
- for (const key of ["build", "uncommittedChanges"])
46
- if (input[key] !== undefined && typeof input[key] !== "boolean")
47
- throw new TypeError(`${key} must be a boolean.`);
48
- for (const key of ["branch", "day"])
49
- if (
50
- input[key] !== undefined &&
51
- (typeof input[key] !== "string" || !input[key].trim())
52
- )
53
- throw new TypeError(`${key} must be a nonempty string.`);
54
- const day = input.day?.trim().toLowerCase();
55
- if (day !== undefined && !days.includes(day))
56
- throw new RangeError("day must be a full weekday name.");
57
- const failing = input.failingTests ?? 0;
58
- if (failing > 0 && input.tests === undefined)
59
- throw new RangeError("tests is required when failingTests is positive.");
60
- if (input.tests !== undefined && failing > input.tests)
61
- throw new RangeError("failingTests cannot exceed tests.");
62
- let score = 100;
63
- const reasons = [];
64
- const deduct = (points, reason) => {
65
- score -= points;
66
- reasons.push(`${reason} (-${points})`);
67
- };
68
- if (!input.tests)
69
- deduct(
70
- 25,
71
- input.tests === 0 ? "No tests reported" : "Test count not provided",
72
- );
73
- else if (failing)
74
- deduct(
75
- Math.min(50, 20 + Math.ceil(30 * (failing / input.tests))),
76
- `${failing} of ${input.tests} tests are failing`,
77
- );
78
- else reasons.push(`${input.tests} tests reported passing`);
79
- if (input.coverage === undefined) deduct(15, "Coverage not provided");
80
- else {
81
- const penalty = Math.ceil((100 - input.coverage) * 0.2);
82
- if (penalty) deduct(penalty, `Coverage is ${input.coverage}%`);
83
- else reasons.push("Coverage is 100%");
84
- }
85
- if (input.build === undefined) deduct(15, "Build status not provided");
86
- else if (!input.build) deduct(40, "Build failed");
87
- else reasons.push("Build passed");
88
- if (input.criticalIssues)
89
- deduct(
90
- Math.min(50, 25 * input.criticalIssues),
91
- `${input.criticalIssues} open critical issues`,
92
- );
93
- if (input.lintFailures)
94
- deduct(
95
- Math.min(20, 5 * input.lintFailures),
96
- `${input.lintFailures} lint failures`,
97
- );
98
- if (input.uncommittedChanges) deduct(10, "Uncommitted changes reported");
99
- if (
100
- input.branch !== undefined &&
101
- !["main", "master"].includes(input.branch.trim())
102
- )
103
- deduct(5, `Branch is ${input.branch.trim()}`);
104
- if (day === "friday")
105
- deduct(3, "Friday: the weekend would like a quiet entrance");
106
- score = Math.max(0, Math.min(100, score));
107
- if (input.build === false || input.criticalIssues > 0) {
108
- score = Math.min(score, 29);
109
- reasons.push("Failed build or critical issues cap the score at 29");
110
- } else if (failing > 0) {
111
- score = Math.min(score, 59);
112
- reasons.push("Failing tests cap the score at 59");
113
- }
114
- const verdict =
115
- score < 30
116
- ? "Absolutely Not"
117
- : score < 60
118
- ? "Questionable"
119
- : score < 80
120
- ? "Probably Fine"
121
- : score < 95
122
- ? "Ship It"
123
- : "Suspiciously Ready";
124
- return { score, verdict, reasons };
125
- }
1
+ const days = [
2
+ "monday",
3
+ "tuesday",
4
+ "wednesday",
5
+ "thursday",
6
+ "friday",
7
+ "saturday",
8
+ "sunday",
9
+ ];
10
+ const counts = ["tests", "failingTests", "criticalIssues", "lintFailures"];
11
+ const allowed = [
12
+ ...counts,
13
+ "coverage",
14
+ "build",
15
+ "uncommittedChanges",
16
+ "branch",
17
+ "day",
18
+ ];
19
+
20
+ /**
21
+ * Calculate a stateless readiness score. Missing evidence reduces the score.
22
+ * @param {{tests?: number, failingTests?: number, coverage?: number, build?: boolean, uncommittedChanges?: boolean, branch?: string, day?: string, criticalIssues?: number, lintFailures?: number}} [input]
23
+ * @returns {{score: number, verdict: string, reasons: string[]}}
24
+ */
25
+ export function shipIt(input = {}) {
26
+ if (!input || typeof input !== "object" || Array.isArray(input))
27
+ throw new TypeError("Input must be an object.");
28
+ for (const key of Object.keys(input))
29
+ if (!allowed.includes(key)) throw new TypeError(`Unknown input: ${key}.`);
30
+ for (const key of counts) {
31
+ if (
32
+ input[key] !== undefined &&
33
+ (!Number.isSafeInteger(input[key]) || input[key] < 0)
34
+ )
35
+ throw new RangeError(`${key} must be a nonnegative safe integer.`);
36
+ }
37
+ if (
38
+ input.coverage !== undefined &&
39
+ (typeof input.coverage !== "number" ||
40
+ !Number.isFinite(input.coverage) ||
41
+ input.coverage < 0 ||
42
+ input.coverage > 100)
43
+ )
44
+ throw new RangeError("coverage must be between 0 and 100.");
45
+ for (const key of ["build", "uncommittedChanges"])
46
+ if (input[key] !== undefined && typeof input[key] !== "boolean")
47
+ throw new TypeError(`${key} must be a boolean.`);
48
+ for (const key of ["branch", "day"])
49
+ if (
50
+ input[key] !== undefined &&
51
+ (typeof input[key] !== "string" || !input[key].trim())
52
+ )
53
+ throw new TypeError(`${key} must be a nonempty string.`);
54
+ const day = input.day?.trim().toLowerCase();
55
+ if (day !== undefined && !days.includes(day))
56
+ throw new RangeError("day must be a full weekday name.");
57
+ const failing = input.failingTests ?? 0;
58
+ if (failing > 0 && input.tests === undefined)
59
+ throw new RangeError("tests is required when failingTests is positive.");
60
+ if (input.tests !== undefined && failing > input.tests)
61
+ throw new RangeError("failingTests cannot exceed tests.");
62
+ let score = 100;
63
+ const reasons = [];
64
+ const deduct = (points, reason) => {
65
+ score -= points;
66
+ reasons.push(`${reason} (-${points})`);
67
+ };
68
+ if (!input.tests)
69
+ deduct(
70
+ 25,
71
+ input.tests === 0 ? "No tests reported" : "Test count not provided",
72
+ );
73
+ else if (failing)
74
+ deduct(
75
+ Math.min(50, 20 + Math.ceil(30 * (failing / input.tests))),
76
+ `${failing} of ${input.tests} tests are failing`,
77
+ );
78
+ else reasons.push(`${input.tests} tests reported passing`);
79
+ if (input.coverage === undefined) deduct(15, "Coverage not provided");
80
+ else {
81
+ const penalty = Math.ceil((100 - input.coverage) * 0.2);
82
+ if (penalty) deduct(penalty, `Coverage is ${input.coverage}%`);
83
+ else reasons.push("Coverage is 100%");
84
+ }
85
+ if (input.build === undefined) deduct(15, "Build status not provided");
86
+ else if (!input.build) deduct(40, "Build failed");
87
+ else reasons.push("Build passed");
88
+ if (input.criticalIssues)
89
+ deduct(
90
+ Math.min(50, 25 * input.criticalIssues),
91
+ `${input.criticalIssues} open critical issues`,
92
+ );
93
+ if (input.lintFailures)
94
+ deduct(
95
+ Math.min(20, 5 * input.lintFailures),
96
+ `${input.lintFailures} lint failures`,
97
+ );
98
+ if (input.uncommittedChanges) deduct(10, "Uncommitted changes reported");
99
+ if (
100
+ input.branch !== undefined &&
101
+ !["main", "master"].includes(input.branch.trim())
102
+ )
103
+ deduct(5, `Branch is ${input.branch.trim()}`);
104
+ if (day === "friday")
105
+ deduct(3, "Friday: the weekend would like a quiet entrance");
106
+ score = Math.max(0, Math.min(100, score));
107
+ if (input.build === false || input.criticalIssues > 0) {
108
+ score = Math.min(score, 29);
109
+ reasons.push("Failed build or critical issues cap the score at 29");
110
+ } else if (failing > 0) {
111
+ score = Math.min(score, 59);
112
+ reasons.push("Failing tests cap the score at 59");
113
+ }
114
+ const verdict =
115
+ score < 30
116
+ ? "Absolutely Not"
117
+ : score < 60
118
+ ? "Questionable"
119
+ : score < 80
120
+ ? "Probably Fine"
121
+ : score < 95
122
+ ? "Ship It"
123
+ : "Suspiciously Ready";
124
+ return { score, verdict, reasons };
125
+ }