@cellajs/cli 0.4.1 → 0.4.2
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 +16 -1
- package/dist/index.js +231 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
|
|
3
3
|
// src/cella-cli.ts
|
|
4
|
-
import { existsSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { existsSync as existsSync13, statSync as statSync3 } from "fs";
|
|
5
|
+
import { join as join19, resolve as resolve7 } from "path";
|
|
6
6
|
import process9 from "process";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
@@ -219,7 +219,7 @@ import process4 from "process";
|
|
|
219
219
|
// package.json
|
|
220
220
|
var package_default = {
|
|
221
221
|
name: "@cellajs/cli",
|
|
222
|
-
version: "0.4.
|
|
222
|
+
version: "0.4.2",
|
|
223
223
|
private: false,
|
|
224
224
|
license: "MIT",
|
|
225
225
|
description: "CLI for managing your monorepo and syncing with upstream Cella.",
|
|
@@ -2872,13 +2872,18 @@ async function runAnalyze(config) {
|
|
|
2872
2872
|
|
|
2873
2873
|
// src/services/audit.ts
|
|
2874
2874
|
import { readFileSync as readFileSync4 } from "fs";
|
|
2875
|
-
import { basename as basename4, join as
|
|
2875
|
+
import { basename as basename4, join as join13, relative as relative3 } from "path";
|
|
2876
2876
|
import { checkbox, Separator } from "@inquirer/prompts";
|
|
2877
2877
|
|
|
2878
2878
|
// src/utils/audit-utils.ts
|
|
2879
2879
|
import { execFile as execFile2 } from "child_process";
|
|
2880
2880
|
import { promisify as promisify2 } from "util";
|
|
2881
2881
|
var execFileAsync2 = promisify2(execFile2);
|
|
2882
|
+
function outdatedPackageName(key) {
|
|
2883
|
+
const name = key.split(" ")[0];
|
|
2884
|
+
const at = name.indexOf("@", 1);
|
|
2885
|
+
return at === -1 ? name : name.slice(0, at);
|
|
2886
|
+
}
|
|
2882
2887
|
function getRepoUrl(repositoryUrl) {
|
|
2883
2888
|
if (!repositoryUrl) return null;
|
|
2884
2889
|
let url = repositoryUrl;
|
|
@@ -2973,6 +2978,13 @@ function formatDependents(dependents) {
|
|
|
2973
2978
|
return `${dependents[0]} +${dependents.length - 1}`;
|
|
2974
2979
|
}
|
|
2975
2980
|
|
|
2981
|
+
// src/utils/local-tools.ts
|
|
2982
|
+
import { execFile as execFile3 } from "child_process";
|
|
2983
|
+
import { existsSync as existsSync7, realpathSync as realpathSync2 } from "fs";
|
|
2984
|
+
import { tmpdir } from "os";
|
|
2985
|
+
import { delimiter, dirname as dirname6, join as join11, sep as sep2 } from "path";
|
|
2986
|
+
import { promisify as promisify3 } from "util";
|
|
2987
|
+
|
|
2976
2988
|
// src/utils/runtimes.ts
|
|
2977
2989
|
import { readFileSync as readFileSync2 } from "fs";
|
|
2978
2990
|
import { basename as basename3, dirname as dirname5, join as join10 } from "path";
|
|
@@ -3094,24 +3106,23 @@ async function fetchImageTags(pin) {
|
|
|
3094
3106
|
if (!Array.isArray(results)) return null;
|
|
3095
3107
|
return { pinned: hubTag(pinned), tags: results.flatMap((entry) => hubTag(entry) ?? []) };
|
|
3096
3108
|
}
|
|
3109
|
+
async function fetchPnpmAdvisories(version) {
|
|
3110
|
+
const answer = await fetchJson("https://registry.npmjs.org/-/npm/v1/security/advisories/bulk", {
|
|
3111
|
+
method: "POST",
|
|
3112
|
+
headers: { "content-type": "application/json" },
|
|
3113
|
+
body: JSON.stringify({ pnpm: [version] })
|
|
3114
|
+
});
|
|
3115
|
+
return answer ? answer.pnpm ?? [] : null;
|
|
3116
|
+
}
|
|
3097
3117
|
async function fetchRuntimeFeeds(pins, cwd, now) {
|
|
3098
3118
|
const nodeMajor = pins.node ? parseComparableVersion(pins.node)?.major : void 0;
|
|
3099
3119
|
const [node, published, advisories, images] = await Promise.all([
|
|
3100
3120
|
nodeMajor === void 0 ? null : fetchNodeReleases(nodeMajor, now),
|
|
3101
3121
|
pins.pnpm ? fetchPublishTimes("pnpm", cwd) : null,
|
|
3102
|
-
pins.pnpm ?
|
|
3103
|
-
method: "POST",
|
|
3104
|
-
headers: { "content-type": "application/json" },
|
|
3105
|
-
body: JSON.stringify({ pnpm: [pins.pnpm] })
|
|
3106
|
-
}) : null,
|
|
3122
|
+
pins.pnpm ? fetchPnpmAdvisories(pins.pnpm) : null,
|
|
3107
3123
|
Promise.all(pins.images.map(fetchImageTags))
|
|
3108
3124
|
]);
|
|
3109
|
-
|
|
3110
|
-
return {
|
|
3111
|
-
node,
|
|
3112
|
-
pnpm: published && advisories ? { published, advisories: pnpmAdvisories ?? [] } : null,
|
|
3113
|
-
images
|
|
3114
|
-
};
|
|
3125
|
+
return { node, pnpm: published && advisories ? { published, advisories } : null, images };
|
|
3115
3126
|
}
|
|
3116
3127
|
var count = (n, noun, plural2 = `${noun}s`) => `${n} ${n === 1 ? noun : plural2}`;
|
|
3117
3128
|
var monthYear = (time) => new Date(time).toLocaleString("en", { month: "short", year: "numeric", timeZone: "UTC" }).toLowerCase();
|
|
@@ -3341,6 +3352,130 @@ function liveExcludes(input) {
|
|
|
3341
3352
|
});
|
|
3342
3353
|
}
|
|
3343
3354
|
|
|
3355
|
+
// src/utils/local-tools.ts
|
|
3356
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
3357
|
+
function findOnPath(name, skip = []) {
|
|
3358
|
+
for (const dir of (getEnv("PATH") ?? "").split(delimiter)) {
|
|
3359
|
+
if (!dir || skip.includes(dir) || dir.endsWith(`node_modules${sep2}.bin`)) continue;
|
|
3360
|
+
if (existsSync7(join11(dir, name))) return join11(dir, name);
|
|
3361
|
+
}
|
|
3362
|
+
return null;
|
|
3363
|
+
}
|
|
3364
|
+
function installerOf(file) {
|
|
3365
|
+
let real = file;
|
|
3366
|
+
try {
|
|
3367
|
+
real = realpathSync2(file);
|
|
3368
|
+
} catch {
|
|
3369
|
+
}
|
|
3370
|
+
if (real.includes(`${sep2}.volta${sep2}`)) return "volta";
|
|
3371
|
+
return /[\\/](Cellar|homebrew|linuxbrew)[\\/]/.test(real) ? "brew" : null;
|
|
3372
|
+
}
|
|
3373
|
+
async function versionOf(file, cwd) {
|
|
3374
|
+
try {
|
|
3375
|
+
const { stdout } = await execFileAsync3(file, ["--version"], { encoding: "utf8", timeout: 1e4, cwd });
|
|
3376
|
+
return stdout.match(/\d+\.\d+\.\d+/)?.[0] ?? null;
|
|
3377
|
+
} catch {
|
|
3378
|
+
return null;
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
async function readNode(now) {
|
|
3382
|
+
const version = process.version.slice(1);
|
|
3383
|
+
const major = parseComparableVersion(version)?.major ?? 0;
|
|
3384
|
+
return { version, installer: installerOf(process.execPath), releases: await fetchNodeReleases(major, now) };
|
|
3385
|
+
}
|
|
3386
|
+
async function readPnpm(cwd) {
|
|
3387
|
+
const running = getEnv("npm_execpath");
|
|
3388
|
+
const file = findOnPath("pnpm", running ? [dirname6(running)] : []);
|
|
3389
|
+
const version = file && await versionOf(file, tmpdir());
|
|
3390
|
+
if (!file || !version) return null;
|
|
3391
|
+
const [published, advisories] = await Promise.all([fetchPublishTimes("pnpm", cwd), fetchPnpmAdvisories(version)]);
|
|
3392
|
+
return { version, installer: installerOf(file), published, advisories };
|
|
3393
|
+
}
|
|
3394
|
+
async function fetchOsvSeverities(module, version) {
|
|
3395
|
+
const answer = await fetchJson("https://api.osv.dev/v1/query", {
|
|
3396
|
+
method: "POST",
|
|
3397
|
+
body: JSON.stringify({ package: { name: module, ecosystem: "Go" }, version })
|
|
3398
|
+
});
|
|
3399
|
+
if (!answer) return null;
|
|
3400
|
+
const byGhsa = /* @__PURE__ */ new Map();
|
|
3401
|
+
for (const vuln of answer.vulns ?? []) {
|
|
3402
|
+
const id = [vuln.id, ...vuln.aliases ?? []].find((alias) => alias.startsWith("GHSA-")) ?? vuln.id;
|
|
3403
|
+
const severity = SEVERITIES.find((level) => level === vuln.database_specific?.severity?.toLowerCase());
|
|
3404
|
+
if (severity || !byGhsa.has(id)) byGhsa.set(id, severity ?? "moderate");
|
|
3405
|
+
}
|
|
3406
|
+
return [...byGhsa.values()];
|
|
3407
|
+
}
|
|
3408
|
+
async function readGh() {
|
|
3409
|
+
const file = findOnPath("gh");
|
|
3410
|
+
const version = file && await versionOf(file);
|
|
3411
|
+
if (!file || !version) return null;
|
|
3412
|
+
const [list, advisories] = await Promise.all([
|
|
3413
|
+
fetchJson("https://api.github.com/repos/cli/cli/releases?per_page=30"),
|
|
3414
|
+
fetchOsvSeverities("github.com/cli/cli/v2", version)
|
|
3415
|
+
]);
|
|
3416
|
+
const releases = Array.isArray(list) ? list.flatMap((entry) => {
|
|
3417
|
+
const { tag_name, published_at } = entry;
|
|
3418
|
+
const publishedAt = Date.parse(published_at ?? "");
|
|
3419
|
+
return tag_name && !Number.isNaN(publishedAt) ? [{ version: tag_name.replace(/^v/, ""), publishedAt }] : [];
|
|
3420
|
+
}) : null;
|
|
3421
|
+
return { version, installer: installerOf(file), releases, advisories };
|
|
3422
|
+
}
|
|
3423
|
+
async function readLocalTools(cwd, now) {
|
|
3424
|
+
const [node, pnpm, gh] = await Promise.all([readNode(now), readPnpm(cwd), readGh()]);
|
|
3425
|
+
return { node, pnpm, gh };
|
|
3426
|
+
}
|
|
3427
|
+
function upgradeCommand(name, installer, target) {
|
|
3428
|
+
if (installer === "brew") return `brew upgrade ${name}`;
|
|
3429
|
+
if (!target) return "no fixed release older than the release age yet";
|
|
3430
|
+
if (installer === "volta") return `volta install ${name}@${target}`;
|
|
3431
|
+
return name === "pnpm" ? `pnpm self-update ${target}` : `update to ${target}`;
|
|
3432
|
+
}
|
|
3433
|
+
function toCheck(name, tool, missed, target, noun) {
|
|
3434
|
+
if (!missed)
|
|
3435
|
+
return { name, version: tool.version, severity: null, note: "not checked, no answer from the feed", fix: "" };
|
|
3436
|
+
if (missed.length === 0) return { name, version: tool.version, severity: null, note: "", fix: "" };
|
|
3437
|
+
const high = missed.filter((severity) => severity === "critical" || severity === "high").length;
|
|
3438
|
+
return {
|
|
3439
|
+
name,
|
|
3440
|
+
version: tool.version,
|
|
3441
|
+
severity: SEVERITIES.find((severity) => missed.includes(severity)) ?? null,
|
|
3442
|
+
note: `${plural(missed.length, ...noun)}${high > 0 && high < missed.length ? `, ${high} high` : ""}`,
|
|
3443
|
+
fix: upgradeCommand(name, tool.installer, target)
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
function planLocalTools(tools, options) {
|
|
3447
|
+
const cutoff = options.now - options.releaseAgeMinutes * 60 * 1e3;
|
|
3448
|
+
const { node, pnpm, gh } = tools;
|
|
3449
|
+
const nodeTarget = node.releases ? newestAged(node.version, node.releases, cutoff) : void 0;
|
|
3450
|
+
const securityReleases = (node.releases ?? []).filter(
|
|
3451
|
+
(release) => release.security && nodeTarget && compareVersionStrings(release.version, node.version) > 0 && compareVersionStrings(release.version, nodeTarget.version) <= 0
|
|
3452
|
+
);
|
|
3453
|
+
const checks = [
|
|
3454
|
+
toCheck(
|
|
3455
|
+
"node",
|
|
3456
|
+
node,
|
|
3457
|
+
node.releases && securityReleases.map(() => "high"),
|
|
3458
|
+
nodeTarget?.version,
|
|
3459
|
+
["security release", "security releases"]
|
|
3460
|
+
)
|
|
3461
|
+
];
|
|
3462
|
+
if (pnpm) {
|
|
3463
|
+
const affecting = (version) => (pnpm.advisories ?? []).filter((advisory) => satisfiesComparators(version, advisory.vulnerable_versions));
|
|
3464
|
+
const fixed = Object.entries(pnpm.published ?? {}).map(([version, time]) => ({ version, publishedAt: Date.parse(time) })).filter(
|
|
3465
|
+
(release) => parseComparableVersion(release.version) && !isPrerelease(release.version) && release.publishedAt <= cutoff && compareVersionStrings(release.version, pnpm.version) > 0 && affecting(release.version).length === 0
|
|
3466
|
+
).sort((a, b) => compareVersionStrings(b.version, a.version));
|
|
3467
|
+
const major = parseComparableVersion(pnpm.version)?.major;
|
|
3468
|
+
const target = fixed.find((release) => parseComparableVersion(release.version)?.major === major) ?? fixed[0];
|
|
3469
|
+
const missed = pnpm.advisories && pnpm.published && affecting(pnpm.version).map((advisory) => advisory.severity);
|
|
3470
|
+
checks.push(toCheck("pnpm", pnpm, missed, target?.version, ["advisory", "advisories"]));
|
|
3471
|
+
}
|
|
3472
|
+
if (gh) {
|
|
3473
|
+
const target = gh.releases ? newestAged(gh.version, gh.releases, cutoff) : void 0;
|
|
3474
|
+
checks.push(toCheck("gh", gh, gh.advisories, target?.version, ["advisory", "advisories"]));
|
|
3475
|
+
}
|
|
3476
|
+
return checks;
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3344
3479
|
// src/utils/workspace-yaml.ts
|
|
3345
3480
|
import { isMap, isScalar, isSeq, parseDocument } from "yaml";
|
|
3346
3481
|
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -3421,37 +3556,37 @@ function writeAuditSettings(content, next) {
|
|
|
3421
3556
|
|
|
3422
3557
|
// src/services/audit-security.ts
|
|
3423
3558
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3424
|
-
import { cpSync, existsSync as
|
|
3425
|
-
import { tmpdir } from "os";
|
|
3426
|
-
import { dirname as
|
|
3559
|
+
import { cpSync, existsSync as existsSync8, mkdirSync as mkdirSync2, mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
3560
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
3561
|
+
import { dirname as dirname7, join as join12, relative as relative2 } from "path";
|
|
3427
3562
|
import { confirm, select as select2 } from "@inquirer/prompts";
|
|
3428
3563
|
var WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
3429
3564
|
var LOCK_FILE = "pnpm-lock.yaml";
|
|
3430
3565
|
var RESOLUTION_FILES = ["package.json", LOCK_FILE, ".npmrc", ".pnpmfile.cjs"];
|
|
3431
|
-
var readOr = (path, fallback = "") =>
|
|
3566
|
+
var readOr = (path, fallback = "") => existsSync8(path) ? readFileSync3(path, "utf8") : fallback;
|
|
3432
3567
|
var count2 = (n, noun, plural2 = `${noun}s`) => `${n} ${n === 1 ? noun : plural2}`;
|
|
3433
3568
|
var isBreaking = (item) => item.remedy.kind !== "none" && item.remedy.breaking;
|
|
3434
3569
|
var needsDecision = (item) => item.standing === "new" && (item.remedy.kind !== "override" || item.remedy.breaking || item.direct);
|
|
3435
3570
|
var sameEntries = (a, b) => Object.keys(a).length === Object.keys(b).length && Object.entries(a).every(([key, value]) => b[key] === value);
|
|
3436
3571
|
async function createStaging(forkPath, workspaceYaml) {
|
|
3437
|
-
const staging = mkdtempSync(
|
|
3572
|
+
const staging = mkdtempSync(join12(tmpdir2(), "cella-audit-"));
|
|
3438
3573
|
const projects = await runPnpmJson(["ls", "-r", "--depth", "-1", "--json"], forkPath) ?? [];
|
|
3439
3574
|
const files = [
|
|
3440
3575
|
...RESOLUTION_FILES,
|
|
3441
3576
|
...readPatchFiles(workspaceYaml),
|
|
3442
|
-
...projects.map((project) =>
|
|
3577
|
+
...projects.map((project) => join12(relative2(forkPath, project.path), "package.json"))
|
|
3443
3578
|
];
|
|
3444
3579
|
for (const file of new Set(files)) {
|
|
3445
|
-
if (file.startsWith("..") || !
|
|
3446
|
-
mkdirSync2(
|
|
3447
|
-
cpSync(
|
|
3580
|
+
if (file.startsWith("..") || !existsSync8(join12(forkPath, file))) continue;
|
|
3581
|
+
mkdirSync2(dirname7(join12(staging, file)), { recursive: true });
|
|
3582
|
+
cpSync(join12(forkPath, file), join12(staging, file));
|
|
3448
3583
|
}
|
|
3449
3584
|
return staging;
|
|
3450
3585
|
}
|
|
3451
3586
|
async function resolveStaging(staging, workspaceYaml, settings) {
|
|
3452
|
-
writeFileSync2(
|
|
3587
|
+
writeFileSync2(join12(staging, WORKSPACE_FILE), writeAuditSettings(workspaceYaml, settings));
|
|
3453
3588
|
const result = await runPnpm(["install", "--lockfile-only", "--no-frozen-lockfile", "--ignore-scripts"], staging);
|
|
3454
|
-
return result.ok ? { lock: readOr(
|
|
3589
|
+
return result.ok ? { lock: readOr(join12(staging, LOCK_FILE)) } : { error: `${result.stdout}${result.stderr}`.trim() };
|
|
3455
3590
|
}
|
|
3456
3591
|
function lockedVersions(lock, name) {
|
|
3457
3592
|
const escaped = name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
@@ -3555,8 +3690,8 @@ function changeLine(label, before, after) {
|
|
|
3555
3690
|
var NO_EDITS = { manifests: {}, files: {} };
|
|
3556
3691
|
async function runSecurityPass(forkPath, options) {
|
|
3557
3692
|
const { releaseAgeMinutes, edits = NO_EDITS } = options;
|
|
3558
|
-
const workspaceYaml = readOr(
|
|
3559
|
-
const workspaceLock = readOr(
|
|
3693
|
+
const workspaceYaml = readOr(join12(forkPath, WORKSPACE_FILE));
|
|
3694
|
+
const workspaceLock = readOr(join12(forkPath, LOCK_FILE));
|
|
3560
3695
|
let previous;
|
|
3561
3696
|
try {
|
|
3562
3697
|
previous = readAuditSettings(workspaceYaml);
|
|
@@ -3582,7 +3717,7 @@ async function runSecurityPass(forkPath, options) {
|
|
|
3582
3717
|
};
|
|
3583
3718
|
const staging = await createStaging(forkPath, workspaceYaml);
|
|
3584
3719
|
try {
|
|
3585
|
-
for (const [file, content] of Object.entries(edits.manifests)) writeFileSync2(
|
|
3720
|
+
for (const [file, content] of Object.entries(edits.manifests)) writeFileSync2(join12(staging, file), content);
|
|
3586
3721
|
const bare = await resolveStaging(staging, workspaceYaml, { ...previous, overrides: lasting, ignoredGhsas: [] });
|
|
3587
3722
|
if ("error" in bare) {
|
|
3588
3723
|
const without = updatesManifests ? "with the selected updates" : "without its audit overrides";
|
|
@@ -3634,7 +3769,7 @@ async function runSecurityPass(forkPath, options) {
|
|
|
3634
3769
|
const settingsChanged = nextYaml !== workspaceYaml;
|
|
3635
3770
|
const otherFiles = Object.keys(edits.files);
|
|
3636
3771
|
const writeFiles = () => {
|
|
3637
|
-
for (const [file, content] of Object.entries(edits.files)) writeFileSync2(
|
|
3772
|
+
for (const [file, content] of Object.entries(edits.files)) writeFileSync2(join12(forkPath, file), content);
|
|
3638
3773
|
if (otherFiles.length > 0) console.info(`${checkMark} ${otherFiles.join(", ")} updated`);
|
|
3639
3774
|
};
|
|
3640
3775
|
if (!settingsChanged && !updatesManifests) {
|
|
@@ -3654,7 +3789,7 @@ async function runSecurityPass(forkPath, options) {
|
|
|
3654
3789
|
};
|
|
3655
3790
|
if (!updatesManifests && sameEntries(next.overrides, previous.overrides)) {
|
|
3656
3791
|
writeFiles();
|
|
3657
|
-
writeFileSync2(
|
|
3792
|
+
writeFileSync2(join12(forkPath, WORKSPACE_FILE), nextYaml);
|
|
3658
3793
|
console.info(`${checkMark} ${WORKSPACE_FILE} updated ${colors_default.dim("\xB7 nothing to install")}`);
|
|
3659
3794
|
printOpen(open);
|
|
3660
3795
|
summarize();
|
|
@@ -3665,14 +3800,14 @@ async function runSecurityPass(forkPath, options) {
|
|
|
3665
3800
|
stagingSpinner.stop();
|
|
3666
3801
|
if ("error" in staged) return fail("could not resolve the workspace with the new settings", staged.error);
|
|
3667
3802
|
const written = { ...edits.files, ...edits.manifests, [WORKSPACE_FILE]: nextYaml, [LOCK_FILE]: staged.lock };
|
|
3668
|
-
const before = Object.fromEntries(Object.keys(written).map((file) => [file, readOr(
|
|
3803
|
+
const before = Object.fromEntries(Object.keys(written).map((file) => [file, readOr(join12(forkPath, file))]));
|
|
3669
3804
|
before[WORKSPACE_FILE] = workspaceYaml;
|
|
3670
3805
|
before[LOCK_FILE] = workspaceLock;
|
|
3671
|
-
for (const [file, content] of Object.entries(written)) writeFileSync2(
|
|
3806
|
+
for (const [file, content] of Object.entries(written)) writeFileSync2(join12(forkPath, file), content);
|
|
3672
3807
|
const install = spawnSync2("pnpm", ["install"], { cwd: forkPath, stdio: "inherit" });
|
|
3673
3808
|
console.info();
|
|
3674
3809
|
if (install.status !== 0) {
|
|
3675
|
-
for (const [file, content] of Object.entries(before)) writeFileSync2(
|
|
3810
|
+
for (const [file, content] of Object.entries(before)) writeFileSync2(join12(forkPath, file), content);
|
|
3676
3811
|
console.error(`${colors_default.red("\u2717")} pnpm install failed (exit code ${install.status})`);
|
|
3677
3812
|
console.info(colors_default.dim(` ${Object.keys(before).join(", ")} are back as they were; run pnpm install to match`));
|
|
3678
3813
|
console.info();
|
|
@@ -3741,7 +3876,7 @@ function createDependentPkgReader() {
|
|
|
3741
3876
|
let cached = cache.get(location);
|
|
3742
3877
|
if (cached === void 0) {
|
|
3743
3878
|
try {
|
|
3744
|
-
cached = JSON.parse(readFileSync4(
|
|
3879
|
+
cached = JSON.parse(readFileSync4(join13(location, "package.json"), "utf8"));
|
|
3745
3880
|
} catch {
|
|
3746
3881
|
cached = null;
|
|
3747
3882
|
}
|
|
@@ -3756,11 +3891,12 @@ async function runAudit(config) {
|
|
|
3756
3891
|
try {
|
|
3757
3892
|
const pins = await readRuntimePins(forkPath);
|
|
3758
3893
|
const now = Date.now();
|
|
3759
|
-
const [outdated, auditResult, releaseAgeMinutes, feeds] = await Promise.all([
|
|
3894
|
+
const [outdated, auditResult, releaseAgeMinutes, feeds, machine] = await Promise.all([
|
|
3760
3895
|
runPnpmJson(["-r", "outdated", "--json", "--long"], forkPath),
|
|
3761
3896
|
runPnpmAudit(forkPath),
|
|
3762
3897
|
readReleaseAgeMinutes(forkPath),
|
|
3763
|
-
fetchRuntimeFeeds(pins, forkPath, now)
|
|
3898
|
+
fetchRuntimeFeeds(pins, forkPath, now),
|
|
3899
|
+
readLocalTools(forkPath, now)
|
|
3764
3900
|
]);
|
|
3765
3901
|
if (!outdated) {
|
|
3766
3902
|
spinnerFail("pnpm outdated gave no answer: run pnpm -r outdated to see why");
|
|
@@ -3768,6 +3904,7 @@ async function runAudit(config) {
|
|
|
3768
3904
|
return;
|
|
3769
3905
|
}
|
|
3770
3906
|
const runtimes = planRuntimeUpdates(pins, feeds, { releaseAgeMinutes, now });
|
|
3907
|
+
const localTools = planLocalTools(machine, { releaseAgeMinutes, now });
|
|
3771
3908
|
const rows = [
|
|
3772
3909
|
...packageRows(outdated, auditResult ? advisoriesFromAudit(auditResult) : []),
|
|
3773
3910
|
...runtimes.updates.map(runtimeRow)
|
|
@@ -3776,6 +3913,7 @@ async function runAudit(config) {
|
|
|
3776
3913
|
spinnerSuccess("all packages and runtimes are up to date");
|
|
3777
3914
|
printNotes(forkPath, runtimes.unchecked);
|
|
3778
3915
|
console.info();
|
|
3916
|
+
printLocalTools(localTools);
|
|
3779
3917
|
await runSecurityPass(forkPath, { apply: !config.list, releaseAgeMinutes });
|
|
3780
3918
|
return;
|
|
3781
3919
|
}
|
|
@@ -3783,6 +3921,7 @@ async function runAudit(config) {
|
|
|
3783
3921
|
printOutdatedResults(rows, forkPath);
|
|
3784
3922
|
printNotes(forkPath, runtimes.unchecked);
|
|
3785
3923
|
console.info();
|
|
3924
|
+
printLocalTools(localTools);
|
|
3786
3925
|
if (config.list) {
|
|
3787
3926
|
await runSecurityPass(forkPath, { apply: false, releaseAgeMinutes });
|
|
3788
3927
|
return;
|
|
@@ -3798,7 +3937,8 @@ async function runAudit(config) {
|
|
|
3798
3937
|
function packageRows(outdated, advisories) {
|
|
3799
3938
|
const severityOf = (name) => SEVERITIES.find((severity) => advisories.some((a) => a.module === name && a.severity === severity)) ?? null;
|
|
3800
3939
|
const readDependentPkg = createDependentPkgReader();
|
|
3801
|
-
const rows = Object.entries(outdated).map(([
|
|
3940
|
+
const rows = Object.entries(outdated).map(([key, pkg]) => {
|
|
3941
|
+
const name = outdatedPackageName(key);
|
|
3802
3942
|
const repoUrl = getRepoUrl(pkg.latestManifest?.repository?.url);
|
|
3803
3943
|
const current = pkg.current ?? "missing";
|
|
3804
3944
|
const pinnedIn = [];
|
|
@@ -3809,6 +3949,7 @@ function packageRows(outdated, advisories) {
|
|
|
3809
3949
|
}
|
|
3810
3950
|
}
|
|
3811
3951
|
return {
|
|
3952
|
+
key,
|
|
3812
3953
|
name,
|
|
3813
3954
|
current,
|
|
3814
3955
|
latest: pkg.latest,
|
|
@@ -3825,6 +3966,7 @@ function packageRows(outdated, advisories) {
|
|
|
3825
3966
|
}
|
|
3826
3967
|
function runtimeRow(update) {
|
|
3827
3968
|
return {
|
|
3969
|
+
key: `runtime:${update.file}:${update.name}`,
|
|
3828
3970
|
name: update.name,
|
|
3829
3971
|
current: update.current,
|
|
3830
3972
|
latest: update.target,
|
|
@@ -3838,7 +3980,6 @@ function runtimeRow(update) {
|
|
|
3838
3980
|
runtime: update
|
|
3839
3981
|
};
|
|
3840
3982
|
}
|
|
3841
|
-
var rowKey = (row) => row.runtime ? `runtime:${row.runtime.file}:${row.name}` : row.name;
|
|
3842
3983
|
var isRed = (row) => row.severity === "critical" || row.severity === "high";
|
|
3843
3984
|
var inText = (row) => row.runtime ? pinLocation(row.runtime.file) : formatDependents(row.dependentPackages.map((d) => d.name));
|
|
3844
3985
|
function printOutdatedResults(rows, forkPath) {
|
|
@@ -3894,7 +4035,7 @@ function printOutdatedResults(rows, forkPath) {
|
|
|
3894
4035
|
const sortedDependents = [...dependentMap.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
3895
4036
|
const maxDependentNameLen = Math.max(0, ...sortedDependents.map(([name]) => name.length));
|
|
3896
4037
|
for (const [dependentName, { location, count: count3 }] of sortedDependents) {
|
|
3897
|
-
const packageJsonPath2 =
|
|
4038
|
+
const packageJsonPath2 = join13(location, "package.json");
|
|
3898
4039
|
const relativePath = relative3(forkPath, packageJsonPath2);
|
|
3899
4040
|
const paddedName = dependentName.padEnd(maxDependentNameLen);
|
|
3900
4041
|
console.info(
|
|
@@ -3905,7 +4046,7 @@ function printOutdatedResults(rows, forkPath) {
|
|
|
3905
4046
|
function printNotes(forkPath, unchecked) {
|
|
3906
4047
|
let held = [];
|
|
3907
4048
|
try {
|
|
3908
|
-
const updateConfig = readTopLevel(readFileSync4(
|
|
4049
|
+
const updateConfig = readTopLevel(readFileSync4(join13(forkPath, "pnpm-workspace.yaml"), "utf8"), "updateConfig");
|
|
3909
4050
|
const ignored = updateConfig?.ignoreDependencies;
|
|
3910
4051
|
held = Array.isArray(ignored) ? ignored.map(String) : [];
|
|
3911
4052
|
} catch {
|
|
@@ -3914,6 +4055,16 @@ function printNotes(forkPath, unchecked) {
|
|
|
3914
4055
|
if (unchecked.length > 0)
|
|
3915
4056
|
console.info(` ${colors_default.dim(`not checked, no answer from the feed: ${unchecked.join(", ")}`)}`);
|
|
3916
4057
|
}
|
|
4058
|
+
function printLocalTools(checks) {
|
|
4059
|
+
const width = Math.max(...checks.map((tool) => `${tool.name} ${tool.version}`.length));
|
|
4060
|
+
console.info(colors_default.bold("this machine"));
|
|
4061
|
+
for (const tool of checks) {
|
|
4062
|
+
const label = `${tool.name} ${tool.version}`.padEnd(width);
|
|
4063
|
+
const detail = tool.severity ? `${tool.note} ${colors_default.dim("\xB7")} ${tool.fix}` : colors_default.dim(tool.note || "no missing security fixes");
|
|
4064
|
+
console.info(`${tool.severity ? getVulnIcon(tool.severity) : " "} ${label} ${detail}`);
|
|
4065
|
+
}
|
|
4066
|
+
console.info();
|
|
4067
|
+
}
|
|
3917
4068
|
function buildUpdateChoices(rows) {
|
|
3918
4069
|
const makeChoice = (row) => {
|
|
3919
4070
|
const devTag = row.isDev ? colors_default.dim(" (dev)") : "";
|
|
@@ -3925,7 +4076,7 @@ function buildUpdateChoices(rows) {
|
|
|
3925
4076
|
].filter(Boolean);
|
|
3926
4077
|
return {
|
|
3927
4078
|
name: `${row.name}${devTag} ${version}${notes.length > 0 ? ` ${notes.join(" ")}` : ""}`,
|
|
3928
|
-
value:
|
|
4079
|
+
value: row.key,
|
|
3929
4080
|
checked: isRed(row)
|
|
3930
4081
|
};
|
|
3931
4082
|
};
|
|
@@ -3985,17 +4136,17 @@ async function planEdits(forkPath, rows, selected, nodeImageSuffixes) {
|
|
|
3985
4136
|
const summary = /* @__PURE__ */ new Map();
|
|
3986
4137
|
const bumped = [];
|
|
3987
4138
|
const edit = (file, label, item, resolves, change) => {
|
|
3988
|
-
const entry = contents.get(file) ?? { content: readFileSync4(
|
|
4139
|
+
const entry = contents.get(file) ?? { content: readFileSync4(join13(forkPath, file), "utf8"), resolves: false };
|
|
3989
4140
|
const next = change(entry.content);
|
|
3990
4141
|
if (next === entry.content) return;
|
|
3991
4142
|
contents.set(file, { content: next, resolves: entry.resolves || resolves });
|
|
3992
4143
|
summary.set(label, [...summary.get(label) ?? [], item]);
|
|
3993
4144
|
};
|
|
3994
|
-
const chosen = rows.filter((row) => selected.includes(
|
|
4145
|
+
const chosen = rows.filter((row) => selected.includes(row.key));
|
|
3995
4146
|
for (const row of chosen) {
|
|
3996
4147
|
if (row.runtime) continue;
|
|
3997
4148
|
for (const { name: dependent, location } of row.dependentPackages) {
|
|
3998
|
-
const file = relative3(forkPath,
|
|
4149
|
+
const file = relative3(forkPath, join13(location, "package.json"));
|
|
3999
4150
|
edit(
|
|
4000
4151
|
file,
|
|
4001
4152
|
dependent || file,
|
|
@@ -4069,7 +4220,7 @@ import { select as select3 } from "@inquirer/prompts";
|
|
|
4069
4220
|
|
|
4070
4221
|
// src/services/contrib-core.ts
|
|
4071
4222
|
import { rmSync as rmSync2 } from "fs";
|
|
4072
|
-
import { dirname as
|
|
4223
|
+
import { dirname as dirname8, join as join14, resolve as resolve4 } from "path";
|
|
4073
4224
|
async function listFilesAtRef(repoPath, ref) {
|
|
4074
4225
|
const raw = await git(["ls-tree", "-r", "--name-only", ref], repoPath);
|
|
4075
4226
|
return new Set(raw.split("\n").filter(Boolean));
|
|
@@ -4085,10 +4236,10 @@ async function detectContributableFiles(repoPath, baseRef, forkRef, config, fork
|
|
|
4085
4236
|
const baseFiles = await listFilesAtRef(repoPath, baseRef);
|
|
4086
4237
|
const baseDirs = /* @__PURE__ */ new Set(["."]);
|
|
4087
4238
|
for (const f of baseFiles) {
|
|
4088
|
-
let dir =
|
|
4239
|
+
let dir = dirname8(f);
|
|
4089
4240
|
while (dir !== ".") {
|
|
4090
4241
|
baseDirs.add(dir);
|
|
4091
|
-
dir =
|
|
4242
|
+
dir = dirname8(dir);
|
|
4092
4243
|
}
|
|
4093
4244
|
}
|
|
4094
4245
|
const files = [];
|
|
@@ -4101,7 +4252,7 @@ async function detectContributableFiles(repoPath, baseRef, forkRef, config, fork
|
|
|
4101
4252
|
if (inCella && !inFork) {
|
|
4102
4253
|
kind = "deleted";
|
|
4103
4254
|
} else if (!inCella && inFork) {
|
|
4104
|
-
if (!baseDirs.has(
|
|
4255
|
+
if (!baseDirs.has(dirname8(file.path))) continue;
|
|
4105
4256
|
kind = "created";
|
|
4106
4257
|
} else {
|
|
4107
4258
|
kind = "modified";
|
|
@@ -4125,7 +4276,7 @@ async function readBlobInfo(repoPath, ref, filePath) {
|
|
|
4125
4276
|
async function buildContribBranch(repoPath, baseRef, forkRef, files, forkName) {
|
|
4126
4277
|
const branch = `contrib/${forkName}`;
|
|
4127
4278
|
const gitDir = resolve4(repoPath, await git(["rev-parse", "--git-dir"], repoPath));
|
|
4128
|
-
const tmpIndex =
|
|
4279
|
+
const tmpIndex = join14(gitDir, `tmp-contrib-index-${forkName.replace(/[^\w.-]/g, "_")}`);
|
|
4129
4280
|
const indexEnv = { GIT_INDEX_FILE: tmpIndex };
|
|
4130
4281
|
try {
|
|
4131
4282
|
await git(["read-tree", baseRef], repoPath, { env: indexEnv });
|
|
@@ -4160,20 +4311,20 @@ ${commitBody}`;
|
|
|
4160
4311
|
}
|
|
4161
4312
|
|
|
4162
4313
|
// src/services/fork-utils.ts
|
|
4163
|
-
import { existsSync as
|
|
4164
|
-
import { dirname as
|
|
4314
|
+
import { existsSync as existsSync9 } from "fs";
|
|
4315
|
+
import { dirname as dirname9, resolve as resolve5 } from "path";
|
|
4165
4316
|
async function resolveForkBasePath(repoPath) {
|
|
4166
4317
|
const commonDir = await git(["rev-parse", "--git-common-dir"], repoPath, { ignoreErrors: true });
|
|
4167
|
-
return commonDir ?
|
|
4318
|
+
return commonDir ? dirname9(resolve5(repoPath, commonDir)) : repoPath;
|
|
4168
4319
|
}
|
|
4169
4320
|
function validateForkPath(fork, basePath, requireConfig = false) {
|
|
4170
4321
|
const resolvedPath = resolve5(basePath, fork.localPath);
|
|
4171
4322
|
if (!resolvedPath.startsWith(resolve5(basePath, ".."))) {
|
|
4172
4323
|
return { fork, resolvedPath, valid: false, error: "path resolves outside parent directory" };
|
|
4173
4324
|
}
|
|
4174
|
-
if (!
|
|
4175
|
-
if (!
|
|
4176
|
-
if (requireConfig && !
|
|
4325
|
+
if (!existsSync9(resolvedPath)) return { fork, resolvedPath, valid: false, error: "path does not exist" };
|
|
4326
|
+
if (!existsSync9(`${resolvedPath}/.git`)) return { fork, resolvedPath, valid: false, error: "not a git repository" };
|
|
4327
|
+
if (requireConfig && !existsSync9(`${resolvedPath}/${CONFIG_FILE}`)) {
|
|
4177
4328
|
return { fork, resolvedPath, valid: false, error: `missing ${CONFIG_FILE}` };
|
|
4178
4329
|
}
|
|
4179
4330
|
return { fork, resolvedPath, valid: true };
|
|
@@ -4524,12 +4675,12 @@ function closePr(cwd, ref) {
|
|
|
4524
4675
|
}
|
|
4525
4676
|
|
|
4526
4677
|
// src/services/packages.ts
|
|
4527
|
-
import { existsSync as
|
|
4528
|
-
import { join as
|
|
4678
|
+
import { existsSync as existsSync11, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
4679
|
+
import { join as join16 } from "path";
|
|
4529
4680
|
|
|
4530
4681
|
// src/services/package-usage.ts
|
|
4531
|
-
import { existsSync as
|
|
4532
|
-
import { join as
|
|
4682
|
+
import { existsSync as existsSync10, readFileSync as readFileSync5, statSync } from "fs";
|
|
4683
|
+
import { join as join15 } from "path";
|
|
4533
4684
|
var MAX_FILE_BYTES = 1024 * 1024;
|
|
4534
4685
|
function escapeRegExp(value) {
|
|
4535
4686
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -4545,7 +4696,7 @@ function readSearchable(filePath) {
|
|
|
4545
4696
|
}
|
|
4546
4697
|
function fileSource(forkPath, path) {
|
|
4547
4698
|
let text;
|
|
4548
|
-
return { path, label: path, text: () => text ??= readSearchable(
|
|
4699
|
+
return { path, label: path, text: () => text ??= readSearchable(join15(forkPath, path)) };
|
|
4549
4700
|
}
|
|
4550
4701
|
async function readScriptsAtRef(forkPath, ref, path) {
|
|
4551
4702
|
try {
|
|
@@ -4557,7 +4708,7 @@ async function readScriptsAtRef(forkPath, ref, path) {
|
|
|
4557
4708
|
async function scriptSources(forkPath, upstreamRef, baseRef, path) {
|
|
4558
4709
|
let forkScripts;
|
|
4559
4710
|
try {
|
|
4560
|
-
forkScripts = JSON.parse(readFileSync5(
|
|
4711
|
+
forkScripts = JSON.parse(readFileSync5(join15(forkPath, path), "utf8")).scripts ?? {};
|
|
4561
4712
|
} catch {
|
|
4562
4713
|
return [];
|
|
4563
4714
|
}
|
|
@@ -4567,9 +4718,9 @@ async function scriptSources(forkPath, upstreamRef, baseRef, path) {
|
|
|
4567
4718
|
}
|
|
4568
4719
|
function binNames(forkPath, location, name) {
|
|
4569
4720
|
const bareName = name.split("/").pop();
|
|
4570
|
-
for (const dir of [
|
|
4571
|
-
const manifest =
|
|
4572
|
-
if (!
|
|
4721
|
+
for (const dir of [join15(forkPath, location), forkPath]) {
|
|
4722
|
+
const manifest = join15(dir, "node_modules", name, "package.json");
|
|
4723
|
+
if (!existsSync10(manifest)) continue;
|
|
4573
4724
|
try {
|
|
4574
4725
|
const { bin } = JSON.parse(readFileSync5(manifest, "utf8"));
|
|
4575
4726
|
if (typeof bin === "string") return [bareName];
|
|
@@ -4750,7 +4901,7 @@ function setTopLevelKey(pkg, key, value) {
|
|
|
4750
4901
|
Object.assign(pkg, Object.fromEntries(entries));
|
|
4751
4902
|
}
|
|
4752
4903
|
function readPackageJson(filePath) {
|
|
4753
|
-
if (!
|
|
4904
|
+
if (!existsSync11(filePath)) return null;
|
|
4754
4905
|
try {
|
|
4755
4906
|
return JSON.parse(readFileSync6(filePath, "utf8"));
|
|
4756
4907
|
} catch (error) {
|
|
@@ -4784,13 +4935,13 @@ async function syncPackageJson(config, baseRef, usage, relativePath, keysToSync)
|
|
|
4784
4935
|
const changes = [];
|
|
4785
4936
|
const kept = [];
|
|
4786
4937
|
const pkgRelPath = packageJsonPath(relativePath);
|
|
4787
|
-
const pkgPath =
|
|
4938
|
+
const pkgPath = join16(forkPath, pkgRelPath);
|
|
4788
4939
|
const forkPkg = readPackageJson(pkgPath);
|
|
4789
4940
|
const upstream = await readPackageJsonAtRef(forkPath, upstreamRef, relativePath);
|
|
4790
4941
|
const basePkg = baseRef ? (await readPackageJsonAtRef(forkPath, baseRef, relativePath))?.data : void 0;
|
|
4791
4942
|
if (!upstream) return { updated: false, changes, kept };
|
|
4792
4943
|
if (!forkPkg) {
|
|
4793
|
-
const workspaceArrived = relativePath !== "" &&
|
|
4944
|
+
const workspaceArrived = relativePath !== "" && existsSync11(join16(forkPath, relativePath));
|
|
4794
4945
|
if (basePkg || !workspaceArrived || isIgnored(pkgRelPath, config)) return { updated: false, changes, kept };
|
|
4795
4946
|
writeFileSync3(pkgPath, upstream.raw.endsWith("\n") ? upstream.raw : `${upstream.raw}
|
|
4796
4947
|
`, "utf8");
|
|
@@ -5417,11 +5568,11 @@ async function runForks(config) {
|
|
|
5417
5568
|
|
|
5418
5569
|
// src/services/stats.ts
|
|
5419
5570
|
import { readFile as readFile3 } from "fs/promises";
|
|
5420
|
-
import { join as
|
|
5571
|
+
import { join as join18 } from "path";
|
|
5421
5572
|
|
|
5422
5573
|
// src/utils/coverage-utils.ts
|
|
5423
|
-
import { existsSync as
|
|
5424
|
-
import { join as
|
|
5574
|
+
import { existsSync as existsSync12, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
|
|
5575
|
+
import { join as join17, relative as relative4, sep as sep3 } from "path";
|
|
5425
5576
|
var STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
5426
5577
|
function colorPct(pct) {
|
|
5427
5578
|
if (typeof pct !== "number" || !Number.isFinite(pct)) return colors_default.dim("n/a");
|
|
@@ -5439,11 +5590,11 @@ function formatAge(ms) {
|
|
|
5439
5590
|
return `${mins} minute${mins === 1 ? "" : "s"} ago`;
|
|
5440
5591
|
}
|
|
5441
5592
|
function printCoverageSummary(forkPath) {
|
|
5442
|
-
const file =
|
|
5593
|
+
const file = join17(forkPath, ".coverage", "coverage-summary.json");
|
|
5443
5594
|
let summary;
|
|
5444
5595
|
let mtimeMs = 0;
|
|
5445
5596
|
let problem = "no coverage data found";
|
|
5446
|
-
if (
|
|
5597
|
+
if (existsSync12(file)) {
|
|
5447
5598
|
try {
|
|
5448
5599
|
mtimeMs = statSync2(file).mtimeMs;
|
|
5449
5600
|
summary = JSON.parse(readFileSync7(file, "utf8"));
|
|
@@ -5464,7 +5615,7 @@ function printCoverageSummary(forkPath) {
|
|
|
5464
5615
|
if (key === "total") continue;
|
|
5465
5616
|
const rel = relative4(forkPath, key);
|
|
5466
5617
|
if (rel.startsWith("..")) continue;
|
|
5467
|
-
const pkg = rel.split(
|
|
5618
|
+
const pkg = rel.split(sep3)[0];
|
|
5468
5619
|
const acc = perPackage.get(pkg) ?? { covered: 0, total: 0 };
|
|
5469
5620
|
acc.covered += entry.lines.covered;
|
|
5470
5621
|
acc.total += entry.lines.total;
|
|
@@ -5490,7 +5641,7 @@ function printCoverageSummary(forkPath) {
|
|
|
5490
5641
|
// src/services/stats.ts
|
|
5491
5642
|
var sourceExtensions = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mjs", "cjs", "json", "jsonc", "css", "html"]);
|
|
5492
5643
|
async function getWorkspacePackages(forkPath) {
|
|
5493
|
-
const content = await readFile3(
|
|
5644
|
+
const content = await readFile3(join18(forkPath, "pnpm-workspace.yaml"), "utf8");
|
|
5494
5645
|
const patterns = readTopLevel(content, "packages");
|
|
5495
5646
|
return (Array.isArray(patterns) ? patterns.map(String) : []).map((pattern) => {
|
|
5496
5647
|
if (pattern.endsWith("/*")) {
|
|
@@ -5538,9 +5689,9 @@ async function collectStats(forkPath) {
|
|
|
5538
5689
|
const entries = [];
|
|
5539
5690
|
for (const line of output.split("\n")) {
|
|
5540
5691
|
if (!line) continue;
|
|
5541
|
-
const
|
|
5542
|
-
if (
|
|
5543
|
-
entries.push({ path: line.slice(0,
|
|
5692
|
+
const sep4 = line.lastIndexOf(":");
|
|
5693
|
+
if (sep4 === -1) continue;
|
|
5694
|
+
entries.push({ path: line.slice(0, sep4), loc: Number.parseInt(line.slice(sep4 + 1), 10) });
|
|
5544
5695
|
}
|
|
5545
5696
|
const emptyCat = () => ({
|
|
5546
5697
|
test: { files: 0, loc: 0 },
|
|
@@ -5843,7 +5994,7 @@ function getForkPath() {
|
|
|
5843
5994
|
const envPath = getEnv("CELLA_FORK_PATH");
|
|
5844
5995
|
if (envPath) {
|
|
5845
5996
|
const resolved = resolve7(envPath);
|
|
5846
|
-
if (!
|
|
5997
|
+
if (!existsSync13(resolved) || !statSync3(resolved).isDirectory()) {
|
|
5847
5998
|
throw new Error(`CELLA_FORK_PATH is not a valid directory: ${resolved}`);
|
|
5848
5999
|
}
|
|
5849
6000
|
return resolved;
|
|
@@ -5855,7 +6006,7 @@ function getForkPath() {
|
|
|
5855
6006
|
return cwd;
|
|
5856
6007
|
}
|
|
5857
6008
|
function preflight(forkPath) {
|
|
5858
|
-
if (!
|
|
6009
|
+
if (!existsSync13(join19(forkPath, ".git"))) {
|
|
5859
6010
|
throw new Error(`not a git repository: ${forkPath}`);
|
|
5860
6011
|
}
|
|
5861
6012
|
}
|