@scalequality/check 0.1.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/README.md +47 -0
- package/bin/check.mjs +90 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @scalequality/check
|
|
2
|
+
|
|
3
|
+
Continuous code-maturity gate for CI. On every build it asks ScaleQuality to
|
|
4
|
+
re-measure your repo and **fails the pipeline when maturity drops below your line**,
|
|
5
|
+
so quality regressions are caught before they merge.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @scalequality/check --token "$SQ_TOKEN" --project <projectId>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Get your token and project id from ScaleQuality: open a connected repo's diagnosis
|
|
12
|
+
and **Turn on continuous measurement**. Store the token as a CI secret named
|
|
13
|
+
`SQ_TOKEN`.
|
|
14
|
+
|
|
15
|
+
## Options
|
|
16
|
+
|
|
17
|
+
| Flag | Env | Default | Meaning |
|
|
18
|
+
|------|-----|---------|---------|
|
|
19
|
+
| `--token` | `SQ_TOKEN` | — | Your SQ token (required) |
|
|
20
|
+
| `--project` | `SQ_PROJECT` | — | ScaleQuality project id (required) |
|
|
21
|
+
| `--host` | `SQ_HOST` | `https://app.scalequality.io` | API host |
|
|
22
|
+
| `--timeout` | — | `600` | Max seconds to wait for the measurement |
|
|
23
|
+
| `--json` | — | off | Print the raw result as JSON |
|
|
24
|
+
|
|
25
|
+
Exit codes: `0` passed (or track-only), `1` below the cutoff, `2` error.
|
|
26
|
+
|
|
27
|
+
## CI examples
|
|
28
|
+
|
|
29
|
+
GitHub Actions (a step in your job):
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
- run: npx @scalequality/check --token "$SQ_TOKEN" --project <projectId>
|
|
33
|
+
env:
|
|
34
|
+
SQ_TOKEN: ${{ secrets.SQ_TOKEN }}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
GitLab CI:
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
scalequality:
|
|
41
|
+
image: node:20
|
|
42
|
+
script:
|
|
43
|
+
- npx @scalequality/check --token "$SQ_TOKEN" --project <projectId>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Any other CI: run the same `npx @scalequality/check` command, with `SQ_TOKEN`
|
|
47
|
+
exposed as a secret/variable.
|
package/bin/check.mjs
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ScaleQuality continuous check. Reports the project to ScaleQuality (re-measures
|
|
3
|
+
// the repo) and exits non-zero when maturity is below the configured cutoff, so a
|
|
4
|
+
// CI step can fail the build. Pure Node 18+ (global fetch), no dependencies.
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2)
|
|
7
|
+
const flag = (name) => {
|
|
8
|
+
const i = args.indexOf(name)
|
|
9
|
+
return i >= 0 ? args[i + 1] : undefined
|
|
10
|
+
}
|
|
11
|
+
const has = (name) => args.includes(name)
|
|
12
|
+
|
|
13
|
+
if (has("--help") || has("-h")) {
|
|
14
|
+
console.log(`scalequality check — continuous code-maturity gate for CI
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
npx @scalequality/check --token <SQ_TOKEN> --project <projectId> [options]
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--token <t> SQ token (or env SQ_TOKEN)
|
|
21
|
+
--project <id> ScaleQuality project id (or env SQ_PROJECT)
|
|
22
|
+
--host <url> API host (default https://app.scalequality.io, or env SQ_HOST)
|
|
23
|
+
--timeout <s> Max seconds to wait for the measurement (default 600)
|
|
24
|
+
--json Print the raw result as JSON
|
|
25
|
+
-h, --help Show this help
|
|
26
|
+
|
|
27
|
+
Exit codes: 0 passed (or track-only), 1 below the cutoff, 2 error.`)
|
|
28
|
+
process.exit(0)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const token = flag("--token") || process.env.SQ_TOKEN
|
|
32
|
+
const project = flag("--project") || process.env.SQ_PROJECT
|
|
33
|
+
const host = (flag("--host") || process.env.SQ_HOST || "https://app.scalequality.io").replace(/\/+$/, "")
|
|
34
|
+
const jsonOut = has("--json")
|
|
35
|
+
const timeoutMs = Math.max(30, Number(flag("--timeout") || 600)) * 1000
|
|
36
|
+
|
|
37
|
+
const base = `${host}/api/insights/continuous`
|
|
38
|
+
const log = (m) => { if (!jsonOut) console.log(m) }
|
|
39
|
+
const fail = (m) => { console.error(`ScaleQuality: ${m}`); process.exit(2) }
|
|
40
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
41
|
+
|
|
42
|
+
if (!token || !project) {
|
|
43
|
+
fail("--token and --project are required (or env SQ_TOKEN / SQ_PROJECT). See --help.")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function main() {
|
|
47
|
+
const auth = { Authorization: `Bearer ${token}` }
|
|
48
|
+
const r = await fetch(`${base}/report`, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { ...auth, "Content-Type": "application/json" },
|
|
51
|
+
body: JSON.stringify({ project }),
|
|
52
|
+
}).catch((e) => fail(`could not reach ${host} (${e.message})`))
|
|
53
|
+
if (!r.ok) {
|
|
54
|
+
const body = await r.text().catch(() => "")
|
|
55
|
+
if (r.status === 401) fail("invalid token.")
|
|
56
|
+
if (r.status === 402) fail("continuous measurement needs an active subscription.")
|
|
57
|
+
if (r.status === 409) fail("continuous is not enabled for this project. Turn it on in ScaleQuality.")
|
|
58
|
+
fail(`report failed (${r.status}) ${body}`)
|
|
59
|
+
}
|
|
60
|
+
const { runId } = await r.json()
|
|
61
|
+
if (!runId) fail("no run id returned.")
|
|
62
|
+
log(`ScaleQuality: measuring ${project} …`)
|
|
63
|
+
|
|
64
|
+
const deadline = Date.now() + timeoutMs
|
|
65
|
+
let last
|
|
66
|
+
while (Date.now() < deadline) {
|
|
67
|
+
await sleep(5000)
|
|
68
|
+
const sr = await fetch(`${base}/report/${runId}`, { headers: auth }).catch((e) => fail(`status check failed (${e.message})`))
|
|
69
|
+
if (!sr.ok) { const b = await sr.text().catch(() => ""); fail(`status failed (${sr.status}) ${b}`) }
|
|
70
|
+
last = await sr.json()
|
|
71
|
+
if (last.status === "COMPLETED" || last.status === "FAILED") break
|
|
72
|
+
if (!jsonOut) process.stdout.write(".")
|
|
73
|
+
}
|
|
74
|
+
if (!jsonOut) process.stdout.write("\n")
|
|
75
|
+
|
|
76
|
+
if (!last || (last.status !== "COMPLETED" && last.status !== "FAILED")) fail("timed out waiting for the measurement.")
|
|
77
|
+
if (last.status === "FAILED") fail(`measurement failed: ${last.failureReason || "unknown"}`)
|
|
78
|
+
|
|
79
|
+
if (jsonOut) {
|
|
80
|
+
console.log(JSON.stringify(last))
|
|
81
|
+
} else {
|
|
82
|
+
const cut = last.cutoff != null ? ` (cutoff ${last.cutoff})` : " (track only)"
|
|
83
|
+
log(`ScaleQuality: maturity ${last.maturity ?? "—"}${cut}`)
|
|
84
|
+
if (last.passed === false) console.error("ScaleQuality: below the line — failing the build.")
|
|
85
|
+
else log("ScaleQuality: passed.")
|
|
86
|
+
}
|
|
87
|
+
process.exit(last.passed === false ? 1 : 0)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
main().catch((e) => fail(e?.message || String(e)))
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scalequality/check",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ScaleQuality continuous code-maturity check for CI. Re-measures your repo on every build and fails the pipeline below your maturity line.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"check": "bin/check.mjs",
|
|
8
|
+
"scalequality-check": "bin/check.mjs"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"scalequality",
|
|
19
|
+
"code-quality",
|
|
20
|
+
"code-maturity",
|
|
21
|
+
"ci",
|
|
22
|
+
"continuous-measurement",
|
|
23
|
+
"ai-code"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"homepage": "https://scalequality.io",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
}
|
|
30
|
+
}
|