getadvantage 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 +62 -0
- package/brief.mjs +634 -0
- package/checks-runner.mjs +136 -0
- package/checks.mjs +327 -0
- package/deploy.mjs +203 -0
- package/handoff.mjs +272 -0
- package/index.mjs +181 -0
- package/overviews.mjs +536 -0
- package/package.json +38 -0
- package/util.mjs +142 -0
package/util.mjs
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Ship-Safe — shared helpers (ANSI color, git wrappers, formatting).
|
|
2
|
+
// Node built-ins only. No npm deps.
|
|
3
|
+
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
// --- ANSI color, degrading gracefully ---------------------------------------
|
|
9
|
+
// Honour NO_COLOR (https://no-color.org/) and a non-TTY stdout (piped/redirected),
|
|
10
|
+
// so the output stays clean when captured to a file.
|
|
11
|
+
const COLOR_ON =
|
|
12
|
+
!process.env.NO_COLOR &&
|
|
13
|
+
process.env.TERM !== "dumb" &&
|
|
14
|
+
(process.stdout.isTTY ?? false);
|
|
15
|
+
|
|
16
|
+
function wrap(code) {
|
|
17
|
+
return (s) => (COLOR_ON ? `[${code}m${s}[0m` : String(s));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const c = {
|
|
21
|
+
green: wrap("32"),
|
|
22
|
+
yellow: wrap("33"),
|
|
23
|
+
red: wrap("31"),
|
|
24
|
+
cyan: wrap("36"),
|
|
25
|
+
gray: wrap("90"),
|
|
26
|
+
bold: wrap("1"),
|
|
27
|
+
dim: wrap("2"),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Status glyphs. ✓ pass · ⚠ warn · ✗ fail (block).
|
|
31
|
+
export const GLYPH = {
|
|
32
|
+
pass: c.green("✓"),
|
|
33
|
+
warn: c.yellow("⚠"),
|
|
34
|
+
fail: c.red("✗"),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** A single check result. status ∈ pass|warn|fail. fail ⇒ NO-GO. */
|
|
38
|
+
export function result(status, label, detail, extra = []) {
|
|
39
|
+
return { status, label, detail, extra };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Print one check line + any indented extra lines. */
|
|
43
|
+
export function printResult(r) {
|
|
44
|
+
console.log(` ${GLYPH[r.status]} ${c.bold(r.label)} — ${r.detail}`);
|
|
45
|
+
for (const line of r.extra) console.log(` ${c.gray(line)}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --- git helpers (synchronous; the CLI is short-lived) ----------------------
|
|
49
|
+
|
|
50
|
+
/** Run a git command, returning trimmed stdout. Throws on non-zero exit.
|
|
51
|
+
* NOTE: trims — do NOT use for `status --porcelain`, whose leading status
|
|
52
|
+
* columns are space-significant (use gitRaw for that). */
|
|
53
|
+
export function git(args, opts = {}) {
|
|
54
|
+
return gitRaw(args, opts).trim();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Like git() but returns stdout UNtrimmed — required for porcelain parsing
|
|
58
|
+
* where a leading space in the XY status field is meaningful. */
|
|
59
|
+
export function gitRaw(args, opts = {}) {
|
|
60
|
+
return execFileSync("git", args, {
|
|
61
|
+
encoding: "utf8",
|
|
62
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
63
|
+
// git can emit a lot on a large diff; give it room.
|
|
64
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Run git but never throw — returns "" on any failure (best-effort probes). */
|
|
69
|
+
export function gitSafe(args, opts = {}) {
|
|
70
|
+
try {
|
|
71
|
+
return git(args, opts);
|
|
72
|
+
} catch {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Repo root (absolute). */
|
|
78
|
+
export function repoRoot(cwd = process.cwd()) {
|
|
79
|
+
return git(["rev-parse", "--show-toplevel"], { cwd });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Mask a matched secret to a recognisable fingerprint — NEVER echo the full
|
|
83
|
+
* value. Mirrors app/lib/safety.ts `fingerprint()`. */
|
|
84
|
+
export function fingerprint(match) {
|
|
85
|
+
const head = match.slice(0, 6);
|
|
86
|
+
const tail = match.length > 14 ? match.slice(-4) : "";
|
|
87
|
+
return `${head}…${tail} (${match.length} chars)`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Section header. */
|
|
91
|
+
export function section(title) {
|
|
92
|
+
console.log("\n" + c.bold(c.cyan(title)));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- filesystem walk (read-only) --------------------------------------------
|
|
96
|
+
|
|
97
|
+
/** Directories we never descend into when walking the tree for source scans. */
|
|
98
|
+
const WALK_SKIP_DIR = new Set([
|
|
99
|
+
".git", "node_modules", ".next", ".vercel", ".data", "dist", "build", "coverage", ".turbo",
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Recursively collect file paths under `dir`, skipping vendored/generated dirs.
|
|
104
|
+
* Returns ABSOLUTE paths. Never throws on an unreadable entry (best-effort).
|
|
105
|
+
* @param {string} dir absolute directory to walk
|
|
106
|
+
* @param {(rel: string) => boolean} [keep] optional filter on the path's basename+ext
|
|
107
|
+
*/
|
|
108
|
+
export function walkFiles(dir, keep) {
|
|
109
|
+
const out = [];
|
|
110
|
+
let entries;
|
|
111
|
+
try {
|
|
112
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
113
|
+
} catch {
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
for (const ent of entries) {
|
|
117
|
+
const abs = path.join(dir, ent.name);
|
|
118
|
+
if (ent.isDirectory()) {
|
|
119
|
+
if (WALK_SKIP_DIR.has(ent.name)) continue;
|
|
120
|
+
out.push(...walkFiles(abs, keep));
|
|
121
|
+
} else if (ent.isFile()) {
|
|
122
|
+
if (!keep || keep(abs)) out.push(abs);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Read a UTF-8 text file, returning "" on any error (best-effort probe). */
|
|
129
|
+
export function readText(abs) {
|
|
130
|
+
try {
|
|
131
|
+
const st = statSync(abs);
|
|
132
|
+
if (!st.isFile() || st.size > 4_000_000) return "";
|
|
133
|
+
return readFileSync(abs, "utf8");
|
|
134
|
+
} catch {
|
|
135
|
+
return "";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Forward-slash a path and make it relative to the repo root for display. */
|
|
140
|
+
export function relPath(abs, cwd) {
|
|
141
|
+
return path.relative(cwd, abs).split(path.sep).join("/");
|
|
142
|
+
}
|