Skip to content
Merged
9 changes: 8 additions & 1 deletion .configs/vitest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ export default defineConfig({
exclude: ["node_modules", "reference/**"],
environment: "node",
testTimeout: 30000,
server: { deps: { inline: [/@cldmv\/slothlet/] } },
coverage: {
provider: "v8",
include: ["src/**"],
exclude: ["**/*.json", "tests/**"],
exclude: [
"**/*.json",
"tests/**",
// Windows-only elevation helpers β€” cannot execute on the Linux coverage runner.
"src/api/link/elevate-windows.mjs",
"src/lib/elevate-windows-child.mjs"
],
reporter: ["text", "html", "json-summary", "json"]
}
}
Expand Down
20 changes: 20 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@
"scripts": {
"start": "node bin/git-embedded.mjs",
"build:ci": "echo 'βœ“ no build step'",
"test": "vitest run --config .configs/vitest.config.mjs",
"test": "node tests/run-vitest.mjs",
"test:watch": "vitest --config .configs/vitest.config.mjs",
"coverage": "vitest run --coverage --config .configs/vitest.config.mjs",
"coverage": "node tests/run-vitest.mjs --coverage-quiet",
"ci:coverage": "npm run coverage",
"lint": "eslint --config .configs/eslint.config.mjs .",
"lint:fix": "eslint --config .configs/eslint.config.mjs . --fix",
Expand All @@ -86,6 +86,7 @@
"marked-terminal": "^7.3.0"
},
"devDependencies": {
"@cldmv/vitest-runner": "^1.2.0",
"@eslint/js": "^9.18.0",
"@eslint/json": "^0.10.0",
"@eslint/markdown": "^6.2.2",
Expand Down
1 change: 1 addition & 0 deletions src/api/cli/init.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export async function run(opts = {}) {
if (cfg.status === 0) {
self.report.success("Silenced 'embedded git repository' advice (git config advice.addEmbeddedRepo=false).");
} else {
/* v8 ignore next -- git normally writes config failures to stderr; the `|| stdout` fallback covers an empty-stderr failure (e.g. signal kill) β€” real, just not reproducible in the suite */
self.report.warn(`Could not set git config advice.addEmbeddedRepo: ${cfg.stderr || cfg.stdout}`);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/api/cli/install-hooks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ async function bootstrapAndInstall(result, opts) {
if (out.fallbackToCopy.length > 0) self.report.warn(`Filesystem fallback to copy for ${out.fallbackToCopy.length} entries`);
const gitConfig = context.spawnSync("git", ["config", "--global", "core.hooksPath", dir], { encoding: "utf8" });
if (gitConfig.status !== 0) {
/* v8 ignore next -- git normally writes config failures to stderr; the `|| stdout` fallback covers an empty-stderr failure (e.g. signal kill) β€” real, just not reproducible in the suite */
self.report.error(`git config --global core.hooksPath failed: ${gitConfig.stderr || gitConfig.stdout}`);
process.exit(1);
}
Expand Down
2 changes: 2 additions & 0 deletions src/api/cli/link.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ export function run(localPath, remoteUrl) {
const clone = spawnSync("git", ["clone", "--", remoteUrl, localPath], { stdio: "inherit" });
if (clone.status !== 0) {
self.report.error(`git clone exited with status ${clone.status}`);
/* v8 ignore next -- spawnSync returns a null status on spawn failure (git not on PATH) or signal termination β€” the real case `|| 1` guards β€” but the suite always has git present, so it can't be reproduced here (ignored, not tested) */
process.exit(clone.status || 1);
}

const add = spawnSync("git", ["add", "--", localPath], { stdio: "inherit" });
if (add.status !== 0) {
self.report.error(`git add ${localPath} exited with status ${add.status}`);
/* v8 ignore next -- spawnSync returns a null status on spawn failure (git not on PATH) or signal termination β€” the real case `|| 1` guards β€” but the suite always has git present, so it can't be reproduced here (ignored, not tested) */
process.exit(add.status || 1);
}

Expand Down
8 changes: 7 additions & 1 deletion src/api/commander/custom-help.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,11 @@ export function makeCustomHelp(HelpClass, deps) {
for (const ex of examples) {
const colored = ex.replace(argPattern, (match, p1, p2, p3, p4) => {
if (p2) return color(chalk.magenta, match);
/* v8 ignore else -- defensive: argPattern's two alternatives each
require 1+ chars in their capture group, so a successful match
always sets p2 or p4; the else has no reachable real input. */
if (p4) return color(chalk.yellow, match);
return match;
else return match;
});
output.push(` ${colored}`);
}
Expand Down Expand Up @@ -197,6 +200,9 @@ function getFullCommandChain(cmd) {
function wrapTextWithHangingIndent(text, indent, label, labelColor, width) {
const pad = " ".repeat(indent);
const prefix = "- ";
/* v8 ignore next -- defensive: both call sites (Aliases/Description below)
always pass a non-empty literal label, and this private helper has no
other caller, so the empty-label fallback has no reachable real input. */
const labelStr = label ? label + ": " : "";
const hangingPad = pad + " ".repeat(prefix.length + labelStr.length);
const maxWidth = (width || process.stdout.columns || 80) - (pad.length + prefix.length + labelStr.length);
Expand Down
5 changes: 5 additions & 0 deletions src/api/embedded/gitlinks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,13 @@ export default function gitlinks(cwd = process.cwd()) {
if (!line) continue;
// <mode> SP <type> SP <sha> TAB <path>
const tab = line.indexOf("\t");
/* v8 ignore next -- defensive: `git ls-tree -r HEAD` always emits
`<mode> SP <type> SP <sha> TAB <path>`, so a non-empty line with no tab is
unreachable (the empty-line case is already handled above). */
if (tab < 0) continue;
const meta = line.slice(0, tab).split(/\s+/);
/* v8 ignore next -- defensive: the pre-tab field is always exactly
`<mode> <type> <sha>` (3 tokens) for `-r HEAD`, so fewer than 3 is unreachable. */
if (meta.length < 3) continue;
const [mode, type, sha] = meta;
if (mode !== "160000" || type !== "commit") continue;
Expand Down
13 changes: 12 additions & 1 deletion src/api/embedded/restore.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export default function restore(opts = {}) {
// assumed absent β€” otherwise we could clone into, and later removeClone
// against, a pre-existing path we can't even stat.
if (err.code !== "ENOENT") {
/* v8 ignore next -- defensive: an fs error object always carries a `.code`, so the `|| err.message` fallback is unreachable. */
results.push({ ...record, outcome: "unresolved", note: `target unreadable (${err.code || err.message}) β€” refusing to touch it` });
continue;
}
Expand All @@ -125,6 +126,7 @@ export default function restore(opts = {}) {
try {
if (fs.readdirSync(absChild).length > 0) refuse = "target directory is not empty";
} catch (err) {
/* v8 ignore next -- defensive: an fs error object always carries a `.code`, so the `|| err.message` fallback is unreachable. */
refuse = `target unreadable (${err.code || err.message})`;
}
}
Expand Down Expand Up @@ -166,6 +168,7 @@ export default function restore(opts = {}) {
const clone = git(["clone", "--quiet", "--", resolved.url, absChild], { cwd: root });
if (clone.code !== 0) {
if (fs.existsSync(absChild)) removeClone(absChild, existedBefore);
/* v8 ignore next -- git normally writes to stderr on a clone failure; the empty-stderr `|| exit N` fallback covers a stderr-less failure (e.g. signal kill) β€” real, just not reproducible in the suite */
results.push({ ...record, outcome: "unresolved", note: `clone failed: ${clone.stderr || `exit ${clone.code}`}` });
continue;
}
Expand All @@ -177,6 +180,9 @@ export default function restore(opts = {}) {
let fetchErr = null;
if (!present) {
const fetch = git(["-C", absChild, "fetch", "--quiet", "origin"]);
/* v8 ignore next -- the clone above just succeeded from this same origin (git
stores it as an absolute path), so the follow-up fetch normally succeeds; a
mid-call network failure (remote unreachable) is real but not reproducible in the suite. */
if (fetch.code !== 0) fetchErr = fetch.stderr || `git fetch exited ${fetch.code}`;
present = git(["-C", absChild, "cat-file", "-e", `${sha}^{commit}`]).code === 0;
}
Expand All @@ -185,7 +191,8 @@ export default function restore(opts = {}) {
// A failed fetch (auth/network) is not the same as "wrong repo" β€” surface
// it so a pinned-mismatch isn't misread as a bad convention guess.
const why = fetchErr
? `fetch from ${resolved.source} repo failed (${fetchErr})`
? /* v8 ignore next -- fetchErr is set only by the fetch-failure path above β€” a mid-call network failure, real but not reproducible in the suite */
`fetch from ${resolved.source} repo failed (${fetchErr})`
: `pinned ${sha.slice(0, 12)} absent in ${resolved.source} repo`;
results.push({
...record,
Expand All @@ -208,6 +215,9 @@ export default function restore(opts = {}) {
record.branch = attached ? branch : null;
if (!attached) {
const checkout = git(["-C", absChild, "checkout", "--quiet", "--detach", sha]);
/* v8 ignore start -- the pin was just SHA-verified present in this fresh clone,
so the detached checkout normally succeeds; an I/O error or mid-call corruption
is real but not reproducible in the suite. */
if (checkout.code !== 0) {
removeClone(absChild, existedBefore);
results.push({
Expand All @@ -217,6 +227,7 @@ export default function restore(opts = {}) {
});
continue;
}
/* v8 ignore stop */
}

// Persist the resolved URL (and the branch the child ended on) so day-2
Expand Down
14 changes: 14 additions & 0 deletions src/api/embedded/sync.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { self, context } from "@cldmv/slothlet/runtime";

function git(args, opts = {}) {
const res = context.spawnSync("git", args, { encoding: "utf8", ...opts });
/* v8 ignore next -- res.status is null on spawn failure (git not on PATH) or signal kill β€”
the real case `?? 1` maps to exit 1. git() runs after gitlinks() so git is normally
spawnable, but a later spawn can still fail; it just isn't reproducible in the suite. */
return { code: res.status ?? 1, stdout: (res.stdout || "").trim(), stderr: (res.stderr || "").trim() };
}

Expand Down Expand Up @@ -84,6 +87,7 @@ export default function sync(opts = {}) {
// on an existing path) is a real failure, not an absent child β€” surface
// it rather than silently proceeding.
if (err.code !== "ENOENT") {
/* v8 ignore next -- defensive: an fs error object always carries a `.code`, so the `|| err.message` fallback is unreachable. */
results.push({ ...record, outcome: "sync-failed", note: `gitlink path unreadable (${err.code || err.message})` });
continue;
}
Expand All @@ -106,6 +110,7 @@ export default function sync(opts = {}) {
results.push({
...record,
outcome: "sync-failed",
/* v8 ignore next -- git normally writes to stderr on a rev-parse failure; the empty-stderr `|| exit N` fallback covers a stderr-less failure (e.g. signal kill) β€” real, just not reproducible in the suite */
note: `could not read HEAD: ${headRes.stderr || `git rev-parse exited ${headRes.code}`}`
});
continue;
Expand All @@ -121,6 +126,7 @@ export default function sync(opts = {}) {
// non-zero and stderr surfaces, instead of mislabeling it dirty.
const status = git(["-C", absChild, "status", "--porcelain"]);
if (status.code !== 0) {
/* v8 ignore next -- git normally writes to stderr on a status failure; the empty-stderr `|| exit N` fallback covers a stderr-less failure (e.g. signal kill) β€” real, just not reproducible in the suite */
results.push({ ...record, outcome: "sync-failed", note: `git status failed: ${status.stderr || `exit ${status.code}`}` });
continue;
}
Expand All @@ -138,6 +144,7 @@ export default function sync(opts = {}) {
if (fetch.code !== 0) {
// A failed fetch (auth/network) is a real error, not "pin genuinely
// absent" β€” report sync-failed with stderr so it's actionable.
/* v8 ignore next -- git normally writes to stderr on a fetch failure; the empty-stderr `|| exit N` fallback covers a stderr-less failure (e.g. signal kill) β€” real, just not reproducible in the suite */
results.push({ ...record, outcome: "sync-failed", note: `git fetch origin failed: ${fetch.stderr || `exit ${fetch.code}`}` });
continue;
}
Expand All @@ -150,6 +157,10 @@ export default function sync(opts = {}) {
if (!pinPresent && dryRun) record.note = "pin not in the local object store β€” a real run would fetch origin first";

const branchRes = git(["-C", absChild, "branch", "--show-current"]);
/* v8 ignore start -- `git branch --show-current` normally succeeds here β€” HEAD
(rev-parse), the worktree (status), and the pin (cat-file) already succeeded, and
it neither locks the index nor inflates objects (an index.lock leaves it exit 0). A
hard failure (I/O error, signal kill) is real but not reproducible in the suite. */
if (branchRes.code !== 0) {
results.push({
...record,
Expand All @@ -158,6 +169,7 @@ export default function sync(opts = {}) {
});
continue;
}
/* v8 ignore stop */
const branch = branchRes.stdout || null;
const registered = self.embedded.registry.getBranch(childPath, root);

Expand Down Expand Up @@ -190,6 +202,7 @@ export default function sync(opts = {}) {
...record,
branch,
outcome: "sync-failed",
/* v8 ignore next -- git normally writes to stderr on a merge-base error; the empty-stderr `|| exit N` fallback covers a stderr-less failure (e.g. signal kill) β€” real, just not reproducible in the suite */
note: `could not test ancestry: ${anc.stderr || `merge-base --is-ancestor exited ${anc.code}`}`
});
continue;
Expand Down Expand Up @@ -229,6 +242,7 @@ export default function sync(opts = {}) {
results.push({
...record,
outcome: "sync-failed",
/* v8 ignore next -- git normally writes to stderr on a checkout failure; the empty-stderr `|| exit N` fallback covers a stderr-less failure (e.g. signal kill) β€” real, just not reproducible in the suite */
note: `could not check out ${sha.slice(0, 12)}: ${checkout.stderr || `git checkout exited ${checkout.code}`}`
});
continue;
Expand Down
3 changes: 3 additions & 0 deletions src/api/git.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export function getEffectiveHooksPath(cwd = process.cwd()) {
const { path, os } = context;
const res = context.spawnSync("git", ["config", "--get", "core.hooksPath"], { cwd, encoding: "utf8" });
if (res.status !== 0) return null;
/* v8 ignore next -- defensive: a successful `git config --get` (status 0) always
writes the value plus a trailing newline, so res.stdout is never falsy here;
real git can't produce this fallback (verified empirically). */
const raw = (res.stdout || "").trim();
if (!raw) return null;
const expanded = raw.startsWith("~") ? path.join(os.homedir(), raw.slice(1)) : raw;
Expand Down
3 changes: 3 additions & 0 deletions src/api/report.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export function detectionHeader(result) {
export function message(kind) {
const body = self.messages.load(kind);
const rendered = context.renderMarkdown(body);
/* v8 ignore next -- every messages/*.md file (plus empty/whitespace-only input) renders
with a trailing newline (verified), so the append branch isn't reached by the current
message set; marked doesn't guarantee a trailing newline universally, so the guard stays. */
process.stdout.write(rendered.endsWith("\n") ? rendered : rendered + "\n");
}

Expand Down
Loading
Loading