@scalequality/check 0.1.1 → 0.2.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.
Files changed (2) hide show
  1. package/bin/check.mjs +46 -7
  2. package/package.json +1 -1
package/bin/check.mjs CHANGED
@@ -1,7 +1,13 @@
1
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.
2
+ // ScaleQuality continuous check. Uploads THIS CI checkout to ScaleQuality (re-measures
3
+ // the code the runner already has, including .git so durability still runs) and exits
4
+ // non-zero when maturity is below the configured cutoff, so a CI step can fail the
5
+ // build. Pure Node 18+ (global fetch/FormData/Blob), no npm dependencies.
6
+
7
+ import { tmpdir } from "node:os"
8
+ import { join } from "node:path"
9
+ import { spawnSync } from "node:child_process"
10
+ import { readFileSync, existsSync, statSync, rmSync } from "node:fs"
5
11
 
6
12
  const args = process.argv.slice(2)
7
13
  const flag = (name) => {
@@ -45,10 +51,37 @@ if (!token || !project) {
45
51
 
46
52
  async function main() {
47
53
  const auth = { Authorization: `Bearer ${token}` }
48
- const r = await fetch(`${base}/report`, {
54
+
55
+ // Upload THIS checkout (the runner already has the code) instead of asking the server
56
+ // to clone the repo from outside — that needed a stored provider credential and failed
57
+ // with no_repo_reachable on private repos. Keep .git so the durability domain (git blame)
58
+ // still runs; node_modules + build output are excluded to stay small.
59
+ const archivePath = join(tmpdir(), `sq-checkout-${process.pid}.tar.gz`)
60
+ // Portable flags only (GNU/BSD/busybox tar all accept -czf, --exclude, -C). We do NOT
61
+ // rely on tar's exit code (some tars exit 1 on a benign "file changed as we read it"
62
+ // during a build); instead we require the archive to exist and be non-empty.
63
+ const tr = spawnSync("tar", [
64
+ "-czf", archivePath,
65
+ "--exclude=./node_modules", "--exclude=./dist", "--exclude=./build",
66
+ "--exclude=./.next", "--exclude=./out", "--exclude=./target",
67
+ "--exclude=./.venv", "--exclude=./venv", "--exclude=./coverage", "--exclude=./.turbo",
68
+ "-C", process.cwd(), ".",
69
+ ], { encoding: "utf8" })
70
+ if (!(existsSync(archivePath) && statSync(archivePath).size > 0)) {
71
+ fail(`could not archive the checkout (tar unavailable or failed). ${tr.error?.message || (tr.stderr || "").trim()}`)
72
+ }
73
+ const buf = readFileSync(archivePath)
74
+ rmSync(archivePath, { force: true })
75
+ const sizeMB = buf.length / (1024 * 1024)
76
+ if (sizeMB > 60) fail(`checkout archive too large (${sizeMB.toFixed(0)}MB > 60MB limit). Reduce clone depth or exclude build output.`)
77
+
78
+ const form = new FormData()
79
+ form.append("project", project)
80
+ form.append("file", new Blob([buf]), "checkout.tar.gz")
81
+ const r = await fetch(`${base}/report/upload`, {
49
82
  method: "POST",
50
- headers: { ...auth, "Content-Type": "application/json" },
51
- body: JSON.stringify({ project }),
83
+ headers: auth,
84
+ body: form,
52
85
  }).catch((e) => fail(`could not reach ${host} (${e.message})`))
53
86
  if (!r.ok) {
54
87
  const body = await r.text().catch(() => "")
@@ -61,10 +94,16 @@ async function main() {
61
94
  if (!runId) fail("no run id returned.")
62
95
  log(`ScaleQuality: measuring ${project} …`)
63
96
 
97
+ // The poll interval is part of the measured gate time, not just politeness. A flat
98
+ // 5s quantizes every verdict to a 5s boundary: an analysis that finishes in 7s is
99
+ // only reported at 10s, and the pipeline pays 3s of pure waiting. Start tight and
100
+ // back off, so a fast repo answers fast and a slow one still polls cheaply.
101
+ const POLL_MS = [1500, 1500, 2000, 2000, 3000]
64
102
  const deadline = Date.now() + timeoutMs
65
103
  let last
104
+ let poll = 0
66
105
  while (Date.now() < deadline) {
67
- await sleep(5000)
106
+ await sleep(POLL_MS[Math.min(poll++, POLL_MS.length - 1)])
68
107
  const sr = await fetch(`${base}/report/${runId}`, { headers: auth }).catch((e) => fail(`status check failed (${e.message})`))
69
108
  if (!sr.ok) { const b = await sr.text().catch(() => ""); fail(`status failed (${sr.status}) ${b}`) }
70
109
  last = await sr.json()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalequality/check",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
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
5
  "type": "module",
6
6
  "bin": {