@cellajs/cli 0.4.0 → 0.4.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.
- package/README.md +74 -25
- package/dist/config.js.map +1 -1
- package/dist/index.js +821 -402
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cella-cli.ts
|
|
4
4
|
import { existsSync as existsSync12, statSync as statSync3 } from "fs";
|
|
5
|
-
import { join as
|
|
6
|
-
import
|
|
5
|
+
import { join as join18, resolve as resolve7 } from "path";
|
|
6
|
+
import process9 from "process";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import process5 from "process";
|
|
@@ -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.1",
|
|
223
223
|
private: false,
|
|
224
224
|
license: "MIT",
|
|
225
225
|
description: "CLI for managing your monorepo and syncing with upstream Cella.",
|
|
@@ -300,7 +300,7 @@ var package_default = {
|
|
|
300
300
|
"sync",
|
|
301
301
|
"template"
|
|
302
302
|
],
|
|
303
|
-
packageManager: "pnpm@11.
|
|
303
|
+
packageManager: "pnpm@11.28.7",
|
|
304
304
|
commitlint: {
|
|
305
305
|
extends: [
|
|
306
306
|
"@commitlint/config-conventional"
|
|
@@ -536,16 +536,18 @@ function printFileSection(files, title, linkOptions, options) {
|
|
|
536
536
|
}
|
|
537
537
|
}
|
|
538
538
|
}
|
|
539
|
+
function filesWithStatus(files, status) {
|
|
540
|
+
return files.filter((f) => f.status === status && !isManagedFile(f.path));
|
|
541
|
+
}
|
|
539
542
|
function printFileGroup(files, status, linkOptions, options) {
|
|
540
|
-
const filtered = files.filter((f) => f.status === status && !isManagedFile(f.path));
|
|
541
543
|
const config = statusConfig[status];
|
|
542
544
|
const title = options?.title ?? `${config.icon} ${config.label}`;
|
|
543
|
-
printFileSection(
|
|
545
|
+
printFileSection(filesWithStatus(files, status), title, linkOptions, { ...options, icon: config.icon });
|
|
544
546
|
}
|
|
545
547
|
function findProtectedBehind(files) {
|
|
546
548
|
return files.filter((file) => file.upstreamChanged && !isManagedFile(file.path));
|
|
547
549
|
}
|
|
548
|
-
var PROTECTED_BEHIND_HINT = "pinned/ignored files where upstream also changed since the last sync; the fork side wins on conflict, so
|
|
550
|
+
var PROTECTED_BEHIND_HINT = "pinned/ignored files where upstream also changed since the last sync; the fork side wins on conflict, so review what upstream changed and adopt what you need.";
|
|
549
551
|
function protectionIcon(file) {
|
|
550
552
|
return file.isIgnored ? statusConfig.ignored.icon : statusConfig.pinned.icon;
|
|
551
553
|
}
|
|
@@ -553,22 +555,11 @@ function formatUpstreamChangedLines(file) {
|
|
|
553
555
|
if (file.upstreamChangedLines === void 0) return "";
|
|
554
556
|
return colors_default.yellow(` \xB7 ${plural(file.upstreamChangedLines, "line")} changed upstream`);
|
|
555
557
|
}
|
|
556
|
-
function protectedDiffHints(source, paths) {
|
|
557
|
-
const range = source?.upstreamDiffRange;
|
|
558
|
-
if (!range) return [];
|
|
559
|
-
const listed = new Set(paths);
|
|
560
|
-
const groups = (source?.protectedUpstreamChanges ?? []).filter((group) => group.paths.some((p) => listed.has(p)));
|
|
561
|
-
if (groups.length === 0) return [];
|
|
562
|
-
return [
|
|
563
|
-
"what upstream changed since the last sync, per pinned/ignored entry:",
|
|
564
|
-
...groups.map((group) => ` git diff ${range} -- ${shellPath(group.entry)}`)
|
|
565
|
-
];
|
|
566
|
-
}
|
|
567
558
|
function formatUpstreamLinesAbsent(file) {
|
|
568
559
|
const n = file.upstreamLinesAbsent ?? 0;
|
|
569
560
|
return n > 0 ? colors_default.yellow(` \xB7 ${plural(n, "upstream line")} absent`) : "";
|
|
570
561
|
}
|
|
571
|
-
function printAnalysisFileGroups(files, linkOptions
|
|
562
|
+
function printAnalysisFileGroups(files, linkOptions) {
|
|
572
563
|
const protectedBehind = findProtectedBehind(files);
|
|
573
564
|
printFileGroup(files, "behind", linkOptions, {
|
|
574
565
|
title: colors_default.cyan("\u2193 behind on upstream")
|
|
@@ -585,13 +576,7 @@ function printAnalysisFileGroups(files, linkOptions, diffSource) {
|
|
|
585
576
|
icon: protectionIcon,
|
|
586
577
|
suffix: formatUpstreamChangedLines,
|
|
587
578
|
dateSource: "upstream",
|
|
588
|
-
hint:
|
|
589
|
-
PROTECTED_BEHIND_HINT,
|
|
590
|
-
...protectedDiffHints(
|
|
591
|
-
diffSource,
|
|
592
|
-
protectedBehind.map((file) => file.path)
|
|
593
|
-
)
|
|
594
|
-
]
|
|
579
|
+
hint: PROTECTED_BEHIND_HINT
|
|
595
580
|
});
|
|
596
581
|
printFileGroup(files, "drifted", linkOptions, {
|
|
597
582
|
title: `${warningMark} ${colors_default.yellow("drifted from upstream")}`,
|
|
@@ -636,18 +621,15 @@ function printProtectedConflicts(result) {
|
|
|
636
621
|
const icon = file ? protectionIcon(file) : statusConfig.pinned.icon;
|
|
637
622
|
console.info(` ${icon} ${path}${file ? formatUpstreamChangedLines(file) : ""}`);
|
|
638
623
|
}
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
function plural(count2, singular, pluralForm = `${singular}s`) {
|
|
642
|
-
return `${count2} ${count2 === 1 ? singular : pluralForm}`;
|
|
624
|
+
console.info(colors_default.dim(` ${PROTECTED_BEHIND_HINT}`));
|
|
643
625
|
}
|
|
644
|
-
function
|
|
645
|
-
return
|
|
626
|
+
function plural(count3, singular, pluralForm = `${singular}s`) {
|
|
627
|
+
return `${count3} ${count3 === 1 ? singular : pluralForm}`;
|
|
646
628
|
}
|
|
647
629
|
function printIgnoredUpstreamChanges(result) {
|
|
648
630
|
const groups = result.ignoredUpstreamChanges ?? [];
|
|
649
631
|
if (groups.length === 0) return;
|
|
650
|
-
const total = groups.reduce((
|
|
632
|
+
const total = groups.reduce((count3, group) => count3 + group.paths.length, 0);
|
|
651
633
|
console.info();
|
|
652
634
|
console.info(
|
|
653
635
|
`${warningMark} ${colors_default.yellow(`ignored paths changed upstream \xB7 ${plural(total, "file")} under ${plural(groups.length, "entry", "entries")}`)}`
|
|
@@ -657,11 +639,8 @@ function printIgnoredUpstreamChanges(result) {
|
|
|
657
639
|
if (group.added > 0) counts.push(`${group.added} new`);
|
|
658
640
|
if (group.deleted > 0) counts.push(`${group.deleted} deleted`);
|
|
659
641
|
console.info(` ${statusConfig.ignored.icon} ${group.entry} ${colors_default.dim(`\xB7 ${counts.join(", ")}`)}`);
|
|
660
|
-
if (result.upstreamDiffRange) {
|
|
661
|
-
console.info(colors_default.dim(` git diff ${result.upstreamDiffRange} -- ${shellPath(group.entry)}`));
|
|
662
|
-
}
|
|
663
642
|
}
|
|
664
|
-
console.info(colors_default.dim(" the fork left these untouched and ignored paths never sync:
|
|
643
|
+
console.info(colors_default.dim(" the fork left these untouched and ignored paths never sync: review and adopt what you need."));
|
|
665
644
|
}
|
|
666
645
|
function printUpstreamOverrideChanges(result) {
|
|
667
646
|
const report = result.upstreamOverrides;
|
|
@@ -1393,18 +1372,6 @@ function groupIgnoredUpstreamChanges(files, ignored) {
|
|
|
1393
1372
|
for (const group of groups.values()) group.paths.sort();
|
|
1394
1373
|
return entries.flatMap((entry) => groups.get(entry) ?? []);
|
|
1395
1374
|
}
|
|
1396
|
-
function groupProtectedUpstreamChanges(paths, config) {
|
|
1397
|
-
const [, findEntry] = entryMatcher([...config.overrides?.pinned ?? [], ...config.overrides?.ignored ?? []]);
|
|
1398
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1399
|
-
for (const path of paths) {
|
|
1400
|
-
const entry = findEntry(path) ?? path;
|
|
1401
|
-
const group = groups.get(entry) ?? { entry, paths: [] };
|
|
1402
|
-
group.paths.push(path);
|
|
1403
|
-
groups.set(entry, group);
|
|
1404
|
-
}
|
|
1405
|
-
for (const group of groups.values()) group.paths.sort();
|
|
1406
|
-
return [...groups.values()];
|
|
1407
|
-
}
|
|
1408
1375
|
async function validateOverrides(config, forkPath, upstreamRef) {
|
|
1409
1376
|
const warnings = [];
|
|
1410
1377
|
const ignored = config.overrides?.ignored ?? [];
|
|
@@ -1524,7 +1491,7 @@ var serviceDefinitions = [
|
|
|
1524
1491
|
},
|
|
1525
1492
|
{
|
|
1526
1493
|
name: "audit",
|
|
1527
|
-
description: "update outdated packages, then rebuild the security overrides from a fresh audit",
|
|
1494
|
+
description: "update outdated packages and runtime pins, then rebuild the security overrides from a fresh audit",
|
|
1528
1495
|
options: [["--list", "report only: no prompts, and nothing is written"]]
|
|
1529
1496
|
},
|
|
1530
1497
|
{
|
|
@@ -1845,8 +1812,8 @@ async function compareUpstreamOverrides(repoPath, mergeBaseRef, incomingRef, for
|
|
|
1845
1812
|
packageJsonSync: forkConfig.settings.packageJsonSync ?? DEFAULT_PACKAGE_JSON_SYNC
|
|
1846
1813
|
};
|
|
1847
1814
|
const changes = diffOverrideLists(base, incoming, fork);
|
|
1848
|
-
const
|
|
1849
|
-
return
|
|
1815
|
+
const count3 = Object.values(changes).reduce((n, list) => n + list.added.length + list.removed.length, 0);
|
|
1816
|
+
return count3 > 0 ? { kind: "changes", ...changes } : void 0;
|
|
1850
1817
|
}
|
|
1851
1818
|
|
|
1852
1819
|
// src/services/analyze-core.ts
|
|
@@ -2281,7 +2248,7 @@ function resultMeta(config, ctx) {
|
|
|
2281
2248
|
upstreamCommit: ctx.upstreamCommit
|
|
2282
2249
|
};
|
|
2283
2250
|
}
|
|
2284
|
-
async function upstreamOnlyReports(config, ctx, files
|
|
2251
|
+
async function upstreamOnlyReports(config, ctx, files) {
|
|
2285
2252
|
const { forkPath } = config;
|
|
2286
2253
|
try {
|
|
2287
2254
|
const [baseSha, upstreamSha, upstreamOverrides] = await Promise.all([
|
|
@@ -2293,8 +2260,7 @@ async function upstreamOnlyReports(config, ctx, files, protectedConflicts) {
|
|
|
2293
2260
|
return {
|
|
2294
2261
|
upstreamDiffRange: `${baseSha}..${upstreamSha}`,
|
|
2295
2262
|
upstreamOverrides,
|
|
2296
|
-
ignoredUpstreamChanges: groupIgnoredUpstreamChanges(files, config.overrides?.ignored ?? [])
|
|
2297
|
-
protectedUpstreamChanges: groupProtectedUpstreamChanges(protectedConflicts, config)
|
|
2263
|
+
ignoredUpstreamChanges: groupIgnoredUpstreamChanges(files, config.overrides?.ignored ?? [])
|
|
2298
2264
|
};
|
|
2299
2265
|
} catch {
|
|
2300
2266
|
return {};
|
|
@@ -2379,7 +2345,7 @@ async function runSyncMerge(config, ctx, onProgress, onStep) {
|
|
|
2379
2345
|
autoMergedFiles,
|
|
2380
2346
|
protectedConflicts,
|
|
2381
2347
|
...resultMeta(config, ctx),
|
|
2382
|
-
...await upstreamOnlyReports(config, ctx, analyzedFiles
|
|
2348
|
+
...await upstreamOnlyReports(config, ctx, analyzedFiles),
|
|
2383
2349
|
...notesResult(notes)
|
|
2384
2350
|
};
|
|
2385
2351
|
}
|
|
@@ -2397,7 +2363,7 @@ async function runAnalyzePreview(config, ctx, onProgress, onStep) {
|
|
|
2397
2363
|
);
|
|
2398
2364
|
await enrichChangeInfo(forkPath, analyzedFiles, ctx.mergeBase, "HEAD", ctx.upstreamRef);
|
|
2399
2365
|
const protectedConflicts = analyzedFiles.filter((f) => f.upstreamChanged).map((f) => f.path);
|
|
2400
|
-
const reports = await upstreamOnlyReports(config, ctx, analyzedFiles
|
|
2366
|
+
const reports = await upstreamOnlyReports(config, ctx, analyzedFiles);
|
|
2401
2367
|
onStep?.("analysis complete", `${analyzedFiles.length} files analyzed, dry run, no changes applied`);
|
|
2402
2368
|
return {
|
|
2403
2369
|
success: true,
|
|
@@ -2618,6 +2584,234 @@ async function runMigrate(config) {
|
|
|
2618
2584
|
}
|
|
2619
2585
|
}
|
|
2620
2586
|
|
|
2587
|
+
// src/services/review-page.ts
|
|
2588
|
+
import { basename as basename2 } from "path";
|
|
2589
|
+
import process7 from "process";
|
|
2590
|
+
import { pathToFileURL } from "url";
|
|
2591
|
+
|
|
2592
|
+
// src/utils/diff.ts
|
|
2593
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
2594
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
2595
|
+
import { join as join9 } from "path";
|
|
2596
|
+
import process6 from "process";
|
|
2597
|
+
function gitDiffFile(cwd, range, filePath) {
|
|
2598
|
+
try {
|
|
2599
|
+
return execFileSync2("git", ["diff", "--no-color", range, "--", filePath], { cwd, maxBuffer: MAX_BUFFER });
|
|
2600
|
+
} catch (error) {
|
|
2601
|
+
const stderr = error.stderr?.toString().trim();
|
|
2602
|
+
throw new Error(stderr || `failed to diff ${filePath}`);
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
function escapeHtml(text) {
|
|
2606
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2607
|
+
}
|
|
2608
|
+
async function renderDiffPage(sections, meta) {
|
|
2609
|
+
const { preloadPatchDiff } = await import("@pierre/diffs/ssr");
|
|
2610
|
+
const renderFile = async (file) => {
|
|
2611
|
+
const { prerenderedHTML } = await preloadPatchDiff({
|
|
2612
|
+
patch: file.patch,
|
|
2613
|
+
options: {
|
|
2614
|
+
diffStyle: "split",
|
|
2615
|
+
themeType: "system",
|
|
2616
|
+
lineDiffType: "word-alt",
|
|
2617
|
+
hunkSeparators: "line-info",
|
|
2618
|
+
overflow: "wrap"
|
|
2619
|
+
}
|
|
2620
|
+
});
|
|
2621
|
+
return `<div class="diff-shell"><template shadowrootmode="open">
|
|
2622
|
+
${prerenderedHTML}
|
|
2623
|
+
</template></div>`;
|
|
2624
|
+
};
|
|
2625
|
+
const blocks = [];
|
|
2626
|
+
for (const [index2, section] of sections.entries()) {
|
|
2627
|
+
const files = [];
|
|
2628
|
+
for (const file of section.files) files.push(await renderFile(file));
|
|
2629
|
+
const count3 = section.files.length + (section.omitted?.length ?? 0);
|
|
2630
|
+
const title = section.title ? `<h2>${escapeHtml(section.title)} <span class="count">\xB7 ${count3} ${count3 === 1 ? "file" : "files"}</span></h2>` : "";
|
|
2631
|
+
const hint = section.hint ? `<p class="hint">${escapeHtml(section.hint)}</p>` : "";
|
|
2632
|
+
const omitted = section.omitted?.length ? `<p class="hint">Not shown here (binary, too large, or past the page limit):</p><ul class="omitted">${section.omitted.map((path) => `<li><code>${escapeHtml(path)}</code></li>`).join("")}</ul>` : "";
|
|
2633
|
+
blocks.push(`<section id="section-${index2 + 1}">${title}${hint}${files.join("\n")}${omitted}</section>`);
|
|
2634
|
+
}
|
|
2635
|
+
const titled = sections.filter((section) => section.title);
|
|
2636
|
+
const index = titled.length > 1 ? `<nav><ol>${sections.map(
|
|
2637
|
+
(section, i) => section.title ? `<li><a href="#section-${i + 1}">${escapeHtml(section.title)}</a> <span class="count">\xB7 ${section.files.length + (section.omitted?.length ?? 0)}</span></li>` : ""
|
|
2638
|
+
).join("")}</ol></nav>` : "";
|
|
2639
|
+
const note = meta.note ? `<span class="note">${escapeHtml(meta.note)}</span>` : "";
|
|
2640
|
+
return `<!doctype html>
|
|
2641
|
+
<html lang="en">
|
|
2642
|
+
<head>
|
|
2643
|
+
<meta charset="utf-8">
|
|
2644
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2645
|
+
<title>${escapeHtml(meta.title)}</title>
|
|
2646
|
+
<style>
|
|
2647
|
+
:root { color-scheme: light dark; }
|
|
2648
|
+
* { box-sizing: border-box; }
|
|
2649
|
+
body {
|
|
2650
|
+
margin: 0;
|
|
2651
|
+
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
2652
|
+
background: light-dark(#fafafa, #111);
|
|
2653
|
+
color: light-dark(#111, #eee);
|
|
2654
|
+
}
|
|
2655
|
+
header {
|
|
2656
|
+
display: flex; align-items: baseline; gap: 0.75rem; flex-wrap: wrap;
|
|
2657
|
+
padding: 0.9rem 1.25rem;
|
|
2658
|
+
border-bottom: 1px solid light-dark(#e2e2e2, #2a2a2a);
|
|
2659
|
+
position: sticky; top: 0; z-index: 1;
|
|
2660
|
+
background: inherit;
|
|
2661
|
+
}
|
|
2662
|
+
header .brand { font-weight: 600; color: light-dark(#0891b2, #22d3ee); }
|
|
2663
|
+
header code { font-family: ui-monospace, monospace; font-size: 0.9rem; }
|
|
2664
|
+
header .note { margin-left: auto; font-size: 0.8rem; opacity: 0.7; }
|
|
2665
|
+
main { max-width: 1400px; margin: 1rem auto; padding: 0 1rem 2rem; }
|
|
2666
|
+
nav ol { margin: 0 0 1.5rem; padding-left: 1.25rem; line-height: 1.7; }
|
|
2667
|
+
nav a { color: inherit; }
|
|
2668
|
+
section { scroll-margin-top: 4rem; }
|
|
2669
|
+
h2 { font-size: 1.05rem; margin: 2rem 0 0.25rem; }
|
|
2670
|
+
.count { font-weight: normal; opacity: 0.6; }
|
|
2671
|
+
.hint { margin: 0 0 1rem; font-size: 0.9rem; opacity: 0.75; max-width: 80ch; }
|
|
2672
|
+
.diff-shell {
|
|
2673
|
+
border: 1px solid light-dark(#e2e2e2, #2a2a2a);
|
|
2674
|
+
border-radius: 8px; overflow: hidden;
|
|
2675
|
+
background: light-dark(#fff, #161616);
|
|
2676
|
+
margin-bottom: 1rem;
|
|
2677
|
+
}
|
|
2678
|
+
.omitted { font-size: 0.85rem; opacity: 0.8; }
|
|
2679
|
+
.omitted code { font-family: ui-monospace, monospace; }
|
|
2680
|
+
</style>
|
|
2681
|
+
</head>
|
|
2682
|
+
<body>
|
|
2683
|
+
<header>
|
|
2684
|
+
<span class="brand">\u29C8 cella diff</span>
|
|
2685
|
+
<code>${escapeHtml(meta.title)}</code>
|
|
2686
|
+
${note}
|
|
2687
|
+
</header>
|
|
2688
|
+
<main>
|
|
2689
|
+
${index}
|
|
2690
|
+
${blocks.join("\n")}
|
|
2691
|
+
</main>
|
|
2692
|
+
</body>
|
|
2693
|
+
</html>`;
|
|
2694
|
+
}
|
|
2695
|
+
async function writeDiffPage(sections, meta, repoPath, name) {
|
|
2696
|
+
const html = await renderDiffPage(sections, meta);
|
|
2697
|
+
const gitDir = execFileSync2("git", ["rev-parse", "--absolute-git-dir"], { cwd: repoPath, encoding: "utf8" }).trim();
|
|
2698
|
+
const dir = join9(gitDir, "cella-diff");
|
|
2699
|
+
mkdirSync(dir, { recursive: true });
|
|
2700
|
+
const outPath = join9(dir, `${name.replace(/[^\w.-]+/g, "-")}.html`);
|
|
2701
|
+
writeFileSync(outPath, html);
|
|
2702
|
+
return outPath;
|
|
2703
|
+
}
|
|
2704
|
+
function openInBrowser(target) {
|
|
2705
|
+
try {
|
|
2706
|
+
execFileSync2(process6.platform === "darwin" ? "open" : "xdg-open", [target], { stdio: "ignore" });
|
|
2707
|
+
return true;
|
|
2708
|
+
} catch {
|
|
2709
|
+
return false;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
// src/services/review-page.ts
|
|
2714
|
+
var MAX_PAGE_FILES = 50;
|
|
2715
|
+
var MAX_PATCH_BYTES = 512 * 1024;
|
|
2716
|
+
function reviewSections(result, command) {
|
|
2717
|
+
const [base, upstream] = result.upstreamDiffRange?.split("..") ?? [];
|
|
2718
|
+
if (!base || !upstream) return [];
|
|
2719
|
+
const sinceSync = `${base}..${upstream}`;
|
|
2720
|
+
const dropped = command === "sync" ? {
|
|
2721
|
+
title: "protected files kept the fork version",
|
|
2722
|
+
hint: "What upstream changed in these files since the last sync. The fork kept its own version, so none of it arrived: adopt what you need.",
|
|
2723
|
+
range: sinceSync,
|
|
2724
|
+
paths: result.protectedConflicts ?? []
|
|
2725
|
+
} : {
|
|
2726
|
+
title: "protected but behind upstream",
|
|
2727
|
+
hint: "What upstream changed in these files since the last sync. They are pinned or ignored, so the sync keeps the fork version and none of it arrives: adopt what you need.",
|
|
2728
|
+
range: sinceSync,
|
|
2729
|
+
paths: findProtectedBehind(result.files).map((file) => file.path)
|
|
2730
|
+
};
|
|
2731
|
+
const sections = [
|
|
2732
|
+
dropped,
|
|
2733
|
+
{
|
|
2734
|
+
title: "ignored paths changed upstream",
|
|
2735
|
+
hint: "What upstream changed under ignored paths since the last sync. The fork left these files untouched, and ignored paths never sync.",
|
|
2736
|
+
range: sinceSync,
|
|
2737
|
+
paths: (result.ignoredUpstreamChanges ?? []).flatMap((group) => group.paths)
|
|
2738
|
+
},
|
|
2739
|
+
{
|
|
2740
|
+
title: "pinned files that match the previous upstream",
|
|
2741
|
+
hint: "What upstream changed in these files since the last sync. The fork copy still equals the previous upstream, so the pin only drops these changes: unpin to take them.",
|
|
2742
|
+
range: sinceSync,
|
|
2743
|
+
paths: findMaskingPins(result.files).map((file) => file.path)
|
|
2744
|
+
}
|
|
2745
|
+
];
|
|
2746
|
+
if (command === "analyze") {
|
|
2747
|
+
sections.push(
|
|
2748
|
+
{
|
|
2749
|
+
title: "protected in fork, upstream lines absent",
|
|
2750
|
+
hint: "The fork's version against upstream's, both as they are now: added lines are upstream content the fork does not have, dropped by an earlier sync or removed on purpose.",
|
|
2751
|
+
range: `HEAD..${upstream}`,
|
|
2752
|
+
paths: filesWithStatus(result.files, "ahead").filter((file) => (file.upstreamLinesAbsent ?? 0) > 0).map((file) => file.path)
|
|
2753
|
+
},
|
|
2754
|
+
{
|
|
2755
|
+
title: "drifted from upstream",
|
|
2756
|
+
hint: "What the fork changed in these files since the last sync. They are not pinned or ignored, so upstream changes still merge into them.",
|
|
2757
|
+
range: `${base}..HEAD`,
|
|
2758
|
+
paths: filesWithStatus(result.files, "drifted").map((file) => file.path)
|
|
2759
|
+
}
|
|
2760
|
+
);
|
|
2761
|
+
}
|
|
2762
|
+
return sections.filter((section) => section.paths.length > 0);
|
|
2763
|
+
}
|
|
2764
|
+
async function writeReviewPage(forkPath, sections, meta) {
|
|
2765
|
+
const [base, upstream] = meta.range.split("..");
|
|
2766
|
+
let diffs = 0;
|
|
2767
|
+
const pageSections = [];
|
|
2768
|
+
for (const section of sections) {
|
|
2769
|
+
const pageSection = { title: section.title, hint: section.hint, files: [], omitted: [] };
|
|
2770
|
+
for (const path of section.paths) {
|
|
2771
|
+
if (diffs >= MAX_PAGE_FILES) {
|
|
2772
|
+
pageSection.omitted?.push(path);
|
|
2773
|
+
continue;
|
|
2774
|
+
}
|
|
2775
|
+
const patch = gitDiffFile(forkPath, section.range, path);
|
|
2776
|
+
if (patch.length === 0) continue;
|
|
2777
|
+
if (patch.length > MAX_PATCH_BYTES || !patch.includes("\n@@ ")) {
|
|
2778
|
+
pageSection.omitted?.push(path);
|
|
2779
|
+
continue;
|
|
2780
|
+
}
|
|
2781
|
+
pageSection.files.push({ path, patch: patch.toString() });
|
|
2782
|
+
diffs++;
|
|
2783
|
+
}
|
|
2784
|
+
if (pageSection.files.length > 0 || pageSection.omitted?.length) pageSections.push(pageSection);
|
|
2785
|
+
}
|
|
2786
|
+
if (diffs === 0) return void 0;
|
|
2787
|
+
const page = await writeDiffPage(
|
|
2788
|
+
pageSections,
|
|
2789
|
+
{ title: `${meta.command} review`, note: `${basename2(forkPath)} \xB7 last sync ${base} \u2192 upstream ${upstream}` },
|
|
2790
|
+
forkPath,
|
|
2791
|
+
`${meta.command}-review`
|
|
2792
|
+
);
|
|
2793
|
+
return { path: page, diffs };
|
|
2794
|
+
}
|
|
2795
|
+
async function reportReviewPage(forkPath, result, command) {
|
|
2796
|
+
const sections = reviewSections(result, command);
|
|
2797
|
+
if (sections.length === 0 || !result.upstreamDiffRange) return;
|
|
2798
|
+
createSpinner("writing review page...");
|
|
2799
|
+
let page;
|
|
2800
|
+
try {
|
|
2801
|
+
page = await writeReviewPage(forkPath, sections, { command, range: result.upstreamDiffRange });
|
|
2802
|
+
} catch {
|
|
2803
|
+
page = void 0;
|
|
2804
|
+
}
|
|
2805
|
+
spinnerSuccess();
|
|
2806
|
+
if (!page) return;
|
|
2807
|
+
const opened = command === "sync" && process7.stdout.isTTY === true && openInBrowser(page.path);
|
|
2808
|
+
console.info();
|
|
2809
|
+
console.info(
|
|
2810
|
+
`${colors_default.cyan("\u29C8")} review page with ${plural(page.diffs, "diff")}${opened ? ", opened in the browser" : ""}:`
|
|
2811
|
+
);
|
|
2812
|
+
console.info(colors_default.dim(` ${hyperlink(page.path, pathToFileURL(page.path).href)}`));
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2621
2815
|
// src/services/analyze.ts
|
|
2622
2816
|
var scopeStatuses = {
|
|
2623
2817
|
all: /* @__PURE__ */ new Set(["ahead", "drifted", "diverged"]),
|
|
@@ -2668,18 +2862,18 @@ async function runAnalyze(config) {
|
|
|
2668
2862
|
fileLinkMode: config.settings.fileLinkMode,
|
|
2669
2863
|
forkPath: config.forkPath
|
|
2670
2864
|
};
|
|
2671
|
-
printAnalysisFileGroups(result.files, linkOptions
|
|
2865
|
+
printAnalysisFileGroups(result.files, linkOptions);
|
|
2672
2866
|
printEngineReports(result, "analysis summary");
|
|
2867
|
+
await reportReviewPage(config.forkPath, result, "analyze");
|
|
2673
2868
|
await printMigrationNotesLine(config, result);
|
|
2674
2869
|
console.info();
|
|
2675
2870
|
return result;
|
|
2676
2871
|
}
|
|
2677
2872
|
|
|
2678
2873
|
// src/services/audit.ts
|
|
2679
|
-
import {
|
|
2680
|
-
import {
|
|
2681
|
-
import {
|
|
2682
|
-
import { checkbox, confirm as confirm2, Separator } from "@inquirer/prompts";
|
|
2874
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
2875
|
+
import { basename as basename4, join as join12, relative as relative3 } from "path";
|
|
2876
|
+
import { checkbox, Separator } from "@inquirer/prompts";
|
|
2683
2877
|
|
|
2684
2878
|
// src/utils/audit-utils.ts
|
|
2685
2879
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -2712,11 +2906,6 @@ function getReleasesUrl(repoUrl) {
|
|
|
2712
2906
|
if (!isGitHubRepoUrl(repoUrl)) return null;
|
|
2713
2907
|
return `${repoUrl}/releases`;
|
|
2714
2908
|
}
|
|
2715
|
-
function isMajorVersionChange(current, latest) {
|
|
2716
|
-
const from = parseComparableVersion(current);
|
|
2717
|
-
const to = parseComparableVersion(latest);
|
|
2718
|
-
return !!from && !!to && to.major > from.major;
|
|
2719
|
-
}
|
|
2720
2909
|
async function runPnpm(args, cwd) {
|
|
2721
2910
|
try {
|
|
2722
2911
|
const { stdout, stderr } = await execFileAsync2("pnpm", args, {
|
|
@@ -2742,9 +2931,23 @@ async function runPnpmAudit(cwd) {
|
|
|
2742
2931
|
if (!result || typeof result.advisories !== "object" || result.advisories === null) return null;
|
|
2743
2932
|
return result;
|
|
2744
2933
|
}
|
|
2934
|
+
async function readReleaseAgeMinutes(cwd) {
|
|
2935
|
+
const minutes = Number.parseInt((await runPnpm(["config", "get", "minimumReleaseAge"], cwd)).stdout, 10);
|
|
2936
|
+
return Number.isNaN(minutes) ? 0 : minutes;
|
|
2937
|
+
}
|
|
2745
2938
|
async function fetchPublishTimes(name, cwd) {
|
|
2746
2939
|
const times = await runPnpmJson(["view", name, "time", "--json"], cwd);
|
|
2747
|
-
return times && typeof times === "object" ? times : null;
|
|
2940
|
+
return times && typeof times === "object" && !("error" in times) ? times : null;
|
|
2941
|
+
}
|
|
2942
|
+
function editJsonText(content, change) {
|
|
2943
|
+
const json = JSON.parse(content);
|
|
2944
|
+
change(json);
|
|
2945
|
+
const indent = content.match(/^[ \t]+(?=")/m)?.[0] ?? " ";
|
|
2946
|
+
return `${JSON.stringify(json, null, indent)}${content.endsWith("\n") ? "\n" : ""}`;
|
|
2947
|
+
}
|
|
2948
|
+
function rewriteSpecifier(spec, latest) {
|
|
2949
|
+
const match = spec.match(/^([\^~]?)\d+(?:\.\d+){0,2}(?:-[0-9A-Za-z.-]+)?$/);
|
|
2950
|
+
return match ? `${match[1]}${latest}` : null;
|
|
2748
2951
|
}
|
|
2749
2952
|
var vulnIcons = {
|
|
2750
2953
|
critical: colors_default.red("\u25CF"),
|
|
@@ -2770,6 +2973,259 @@ function formatDependents(dependents) {
|
|
|
2770
2973
|
return `${dependents[0]} +${dependents.length - 1}`;
|
|
2771
2974
|
}
|
|
2772
2975
|
|
|
2976
|
+
// src/utils/runtimes.ts
|
|
2977
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
2978
|
+
import { basename as basename3, dirname as dirname5, join as join10 } from "path";
|
|
2979
|
+
var DAY = 24 * 60 * 60 * 1e3;
|
|
2980
|
+
var FROZEN_AFTER = 180 * DAY;
|
|
2981
|
+
var RECENT_PUSH = 30 * DAY;
|
|
2982
|
+
var EXACT_VERSION = /^\d+\.\d+\.\d+$/;
|
|
2983
|
+
var FROM_LINE = /^\s*FROM\s+(?:--platform=\S+\s+)?(\S+)(?:\s+AS\s+(\S+))?\s*$/i;
|
|
2984
|
+
var NODE_ARG_TAG = /^(?:docker\.io\/)?(?:library\/)?node:\$\{?\w+\}?([\w.-]*)$/;
|
|
2985
|
+
var MINOR_TAG = /^(\d+(?:\.\d+){1,2})(-[\w.-]+)?$/;
|
|
2986
|
+
var isDockerfile = (path) => /(^|\.)Dockerfile$|^Dockerfile\./i.test(basename3(path));
|
|
2987
|
+
function parseDockerfile(file, content) {
|
|
2988
|
+
const images = [];
|
|
2989
|
+
const nodeImageSuffixes = [];
|
|
2990
|
+
const stages = /* @__PURE__ */ new Set();
|
|
2991
|
+
content.split("\n").forEach((text, line) => {
|
|
2992
|
+
const match = text.match(FROM_LINE);
|
|
2993
|
+
if (!match) return;
|
|
2994
|
+
const [, ref, stage] = match;
|
|
2995
|
+
const fromStage = stages.has(ref.toLowerCase());
|
|
2996
|
+
if (stage) stages.add(stage.toLowerCase());
|
|
2997
|
+
if (fromStage || ref.includes("@")) return;
|
|
2998
|
+
const nodeArg = ref.match(NODE_ARG_TAG);
|
|
2999
|
+
if (nodeArg) {
|
|
3000
|
+
nodeImageSuffixes.push(nodeArg[1]);
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
if (ref.includes("$")) return;
|
|
3004
|
+
const slash = ref.lastIndexOf("/");
|
|
3005
|
+
const colon = ref.lastIndexOf(":");
|
|
3006
|
+
if (colon <= slash) return;
|
|
3007
|
+
let image = ref.slice(0, colon);
|
|
3008
|
+
const tag = ref.slice(colon + 1);
|
|
3009
|
+
const host = image.includes("/") ? image.split("/")[0] : "";
|
|
3010
|
+
if (host === "docker.io") image = image.slice(host.length + 1).replace(/^library\//, "");
|
|
3011
|
+
else if (host.includes(".") || host.includes(":") || host === "localhost") return;
|
|
3012
|
+
if (image === "scratch") return;
|
|
3013
|
+
images.push({ file, line, image, tag });
|
|
3014
|
+
});
|
|
3015
|
+
return { images, nodeImageSuffixes };
|
|
3016
|
+
}
|
|
3017
|
+
async function readRuntimePins(forkPath) {
|
|
3018
|
+
let manifest = {};
|
|
3019
|
+
try {
|
|
3020
|
+
manifest = JSON.parse(readFileSync2(join10(forkPath, "package.json"), "utf8"));
|
|
3021
|
+
} catch {
|
|
3022
|
+
}
|
|
3023
|
+
const node = manifest.engines?.node?.trim() ?? "";
|
|
3024
|
+
const pnpm = manifest.packageManager?.match(/^pnpm@(\d+\.\d+\.\d+)(?:\+.*)?$/)?.[1] ?? null;
|
|
3025
|
+
const tracked = (await git(["ls-files", "-z"], forkPath, { ignoreErrors: true })).split("\0").filter(isDockerfile);
|
|
3026
|
+
const images = [];
|
|
3027
|
+
const nodeImageSuffixes = /* @__PURE__ */ new Set();
|
|
3028
|
+
for (const file of tracked) {
|
|
3029
|
+
let content;
|
|
3030
|
+
try {
|
|
3031
|
+
content = readFileSync2(join10(forkPath, file), "utf8");
|
|
3032
|
+
} catch {
|
|
3033
|
+
continue;
|
|
3034
|
+
}
|
|
3035
|
+
const parsed = parseDockerfile(file, content);
|
|
3036
|
+
images.push(...parsed.images);
|
|
3037
|
+
for (const suffix of parsed.nodeImageSuffixes) nodeImageSuffixes.add(suffix);
|
|
3038
|
+
}
|
|
3039
|
+
return {
|
|
3040
|
+
node: EXACT_VERSION.test(node) ? node : null,
|
|
3041
|
+
pnpm,
|
|
3042
|
+
images,
|
|
3043
|
+
nodeImageSuffixes: [...nodeImageSuffixes]
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
async function fetchJson(url, init) {
|
|
3047
|
+
try {
|
|
3048
|
+
const response = await fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
3049
|
+
return response.ok ? await response.json() : null;
|
|
3050
|
+
} catch {
|
|
3051
|
+
return null;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
var hubRepository = (image) => image.includes("/") ? image : `library/${image}`;
|
|
3055
|
+
var hubTag = (entry) => {
|
|
3056
|
+
const { name, last_updated } = entry ?? {};
|
|
3057
|
+
const pushedAt = Date.parse(last_updated ?? "");
|
|
3058
|
+
return name && !Number.isNaN(pushedAt) ? { name, pushedAt } : null;
|
|
3059
|
+
};
|
|
3060
|
+
async function fetchNodeReleases(major, now) {
|
|
3061
|
+
const index = await fetchJson("https://nodejs.org/dist/index.json");
|
|
3062
|
+
if (!Array.isArray(index)) return null;
|
|
3063
|
+
const releases = index.map((entry) => entry).filter((entry) => entry.version && parseComparableVersion(entry.version)?.major === major).map((entry) => ({
|
|
3064
|
+
version: String(entry.version).replace(/^v/, ""),
|
|
3065
|
+
publishedAt: Date.parse(entry.date ?? ""),
|
|
3066
|
+
security: entry.security === true
|
|
3067
|
+
})).filter((release) => !Number.isNaN(release.publishedAt));
|
|
3068
|
+
await Promise.all(
|
|
3069
|
+
releases.filter((release) => now - release.publishedAt < 2 * DAY).map(async (release) => {
|
|
3070
|
+
try {
|
|
3071
|
+
const response = await fetch(`https://nodejs.org/dist/v${release.version}/SHASUMS256.txt`, {
|
|
3072
|
+
method: "HEAD",
|
|
3073
|
+
signal: AbortSignal.timeout(1e4)
|
|
3074
|
+
});
|
|
3075
|
+
const modified = Date.parse(response.headers.get("last-modified") ?? "");
|
|
3076
|
+
release.publishedAt = Number.isNaN(modified) ? release.publishedAt + DAY : modified;
|
|
3077
|
+
} catch {
|
|
3078
|
+
release.publishedAt += DAY;
|
|
3079
|
+
}
|
|
3080
|
+
})
|
|
3081
|
+
);
|
|
3082
|
+
return releases;
|
|
3083
|
+
}
|
|
3084
|
+
async function fetchImageTags(pin) {
|
|
3085
|
+
const shape = pin.tag.match(MINOR_TAG);
|
|
3086
|
+
if (!shape) return null;
|
|
3087
|
+
const base = `https://hub.docker.com/v2/repositories/${hubRepository(pin.image)}/tags`;
|
|
3088
|
+
const filter = shape[2] ?? `${shape[1].split(".")[0]}.`;
|
|
3089
|
+
const [list, pinned] = await Promise.all([
|
|
3090
|
+
fetchJson(`${base}?page_size=100&ordering=last_updated&name=${encodeURIComponent(filter)}`),
|
|
3091
|
+
fetchJson(`${base}/${encodeURIComponent(pin.tag)}`)
|
|
3092
|
+
]);
|
|
3093
|
+
const results = list?.results;
|
|
3094
|
+
if (!Array.isArray(results)) return null;
|
|
3095
|
+
return { pinned: hubTag(pinned), tags: results.flatMap((entry) => hubTag(entry) ?? []) };
|
|
3096
|
+
}
|
|
3097
|
+
async function fetchRuntimeFeeds(pins, cwd, now) {
|
|
3098
|
+
const nodeMajor = pins.node ? parseComparableVersion(pins.node)?.major : void 0;
|
|
3099
|
+
const [node, published, advisories, images] = await Promise.all([
|
|
3100
|
+
nodeMajor === void 0 ? null : fetchNodeReleases(nodeMajor, now),
|
|
3101
|
+
pins.pnpm ? fetchPublishTimes("pnpm", cwd) : null,
|
|
3102
|
+
pins.pnpm ? fetchJson("https://registry.npmjs.org/-/npm/v1/security/advisories/bulk", {
|
|
3103
|
+
method: "POST",
|
|
3104
|
+
headers: { "content-type": "application/json" },
|
|
3105
|
+
body: JSON.stringify({ pnpm: [pins.pnpm] })
|
|
3106
|
+
}) : null,
|
|
3107
|
+
Promise.all(pins.images.map(fetchImageTags))
|
|
3108
|
+
]);
|
|
3109
|
+
const pnpmAdvisories = advisories?.pnpm;
|
|
3110
|
+
return {
|
|
3111
|
+
node,
|
|
3112
|
+
pnpm: published && advisories ? { published, advisories: pnpmAdvisories ?? [] } : null,
|
|
3113
|
+
images
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
var count = (n, noun, plural2 = `${noun}s`) => `${n} ${n === 1 ? noun : plural2}`;
|
|
3117
|
+
var monthYear = (time) => new Date(time).toLocaleString("en", { month: "short", year: "numeric", timeZone: "UTC" }).toLowerCase();
|
|
3118
|
+
function newestAged(pinned, versions, cutoff) {
|
|
3119
|
+
const major = parseComparableVersion(pinned)?.major;
|
|
3120
|
+
return versions.filter(
|
|
3121
|
+
(entry) => !isPrerelease(entry.version) && parseComparableVersion(entry.version)?.major === major && compareVersionStrings(entry.version, pinned) > 0 && entry.publishedAt <= cutoff
|
|
3122
|
+
).sort((a, b) => compareVersionStrings(b.version, a.version))[0];
|
|
3123
|
+
}
|
|
3124
|
+
function planRuntimeUpdates(pins, feeds, options) {
|
|
3125
|
+
const cutoff = options.now - options.releaseAgeMinutes * 60 * 1e3;
|
|
3126
|
+
const updates = [];
|
|
3127
|
+
const unchecked = [];
|
|
3128
|
+
if (pins.node) {
|
|
3129
|
+
if (!feeds.node) unchecked.push("node");
|
|
3130
|
+
const target = feeds.node && newestAged(pins.node, feeds.node, cutoff);
|
|
3131
|
+
if (target) {
|
|
3132
|
+
const node = pins.node;
|
|
3133
|
+
const security = (feeds.node ?? []).some(
|
|
3134
|
+
(release) => release.security && compareVersionStrings(release.version, node) > 0 && compareVersionStrings(release.version, target.version) <= 0
|
|
3135
|
+
);
|
|
3136
|
+
updates.push({
|
|
3137
|
+
name: "node",
|
|
3138
|
+
current: node,
|
|
3139
|
+
target: target.version,
|
|
3140
|
+
file: "package.json",
|
|
3141
|
+
severity: security ? "high" : null,
|
|
3142
|
+
note: security ? "security release" : "",
|
|
3143
|
+
releasesUrl: `https://nodejs.org/en/blog/release/v${target.version}`,
|
|
3144
|
+
pin: { kind: "node" }
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
if (pins.pnpm) {
|
|
3149
|
+
if (!feeds.pnpm) unchecked.push("pnpm");
|
|
3150
|
+
const published = Object.entries(feeds.pnpm?.published ?? {}).map(([version, time]) => ({
|
|
3151
|
+
version,
|
|
3152
|
+
publishedAt: Date.parse(time)
|
|
3153
|
+
}));
|
|
3154
|
+
const target = feeds.pnpm && newestAged(pins.pnpm, published, cutoff);
|
|
3155
|
+
if (target) {
|
|
3156
|
+
const pnpm = pins.pnpm;
|
|
3157
|
+
const fixed = (feeds.pnpm?.advisories ?? []).filter(
|
|
3158
|
+
(advisory) => satisfiesComparators(pnpm, advisory.vulnerable_versions) && !satisfiesComparators(target.version, advisory.vulnerable_versions)
|
|
3159
|
+
).length;
|
|
3160
|
+
updates.push({
|
|
3161
|
+
name: "pnpm",
|
|
3162
|
+
current: pnpm,
|
|
3163
|
+
target: target.version,
|
|
3164
|
+
file: "package.json",
|
|
3165
|
+
severity: fixed > 0 ? "high" : null,
|
|
3166
|
+
note: fixed > 0 ? count(fixed, "advisory", "advisories") : "",
|
|
3167
|
+
releasesUrl: `https://github.com/pnpm/pnpm/releases/tag/v${target.version}`,
|
|
3168
|
+
pin: { kind: "pnpm" }
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
pins.images.forEach((pin, i) => {
|
|
3173
|
+
const shape = pin.tag.match(MINOR_TAG);
|
|
3174
|
+
if (!shape) return;
|
|
3175
|
+
const feed = feeds.images[i];
|
|
3176
|
+
if (!feed) {
|
|
3177
|
+
unchecked.push(pin.image);
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
const [, version, suffix = ""] = shape;
|
|
3181
|
+
const parts = version.split(".").length;
|
|
3182
|
+
const major = version.split(".")[0];
|
|
3183
|
+
const target = feed.tags.map((tag) => ({ tag, match: tag.name.match(MINOR_TAG) })).filter(
|
|
3184
|
+
({ match }) => match && (match[2] ?? "") === suffix && match[1].split(".").length === parts && match[1].split(".")[0] === major && compareVersionStrings(match[1], version) > 0
|
|
3185
|
+
).sort((a, b) => compareVersionStrings(b.match?.[1] ?? "", a.match?.[1] ?? ""))[0];
|
|
3186
|
+
if (!target?.match) return;
|
|
3187
|
+
const frozen = !!feed.pinned && options.now - feed.pinned.pushedAt > FROZEN_AFTER && options.now - target.tag.pushedAt < RECENT_PUSH;
|
|
3188
|
+
const repository = hubRepository(pin.image);
|
|
3189
|
+
updates.push({
|
|
3190
|
+
name: pin.image,
|
|
3191
|
+
current: version,
|
|
3192
|
+
target: target.match[1],
|
|
3193
|
+
file: pin.file,
|
|
3194
|
+
severity: frozen ? "moderate" : null,
|
|
3195
|
+
note: frozen && feed.pinned ? `tag frozen since ${monthYear(feed.pinned.pushedAt)}` : "",
|
|
3196
|
+
releasesUrl: repository.startsWith("library/") ? `https://hub.docker.com/_/${pin.image}/tags` : `https://hub.docker.com/r/${repository}/tags`,
|
|
3197
|
+
pin: { kind: "image", line: pin.line, ref: `${pin.image}:${pin.tag}`, to: `${pin.image}:${target.tag.name}` }
|
|
3198
|
+
});
|
|
3199
|
+
});
|
|
3200
|
+
return { updates, unchecked };
|
|
3201
|
+
}
|
|
3202
|
+
function applyRuntimeUpdate(content, update) {
|
|
3203
|
+
const { pin } = update;
|
|
3204
|
+
if (pin.kind === "image") {
|
|
3205
|
+
const lines = content.split("\n");
|
|
3206
|
+
lines[pin.line] = lines[pin.line].replace(pin.ref, pin.to);
|
|
3207
|
+
return lines.join("\n");
|
|
3208
|
+
}
|
|
3209
|
+
return editJsonText(content, (json) => {
|
|
3210
|
+
if (pin.kind === "node") json.engines.node = update.target;
|
|
3211
|
+
else json.packageManager = `pnpm@${update.target}`;
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3214
|
+
async function missingNodeImages(version, suffixes) {
|
|
3215
|
+
const missing = await Promise.all(
|
|
3216
|
+
suffixes.map(async (suffix) => {
|
|
3217
|
+
const tag = `${version}${suffix}`;
|
|
3218
|
+
const found = await fetchJson(`https://hub.docker.com/v2/repositories/library/node/tags/${tag}`);
|
|
3219
|
+
return found ? [] : [`node:${tag}`];
|
|
3220
|
+
})
|
|
3221
|
+
);
|
|
3222
|
+
return missing.flat();
|
|
3223
|
+
}
|
|
3224
|
+
function pinLocation(file) {
|
|
3225
|
+
const dir = dirname5(file);
|
|
3226
|
+
return dir === "." ? file : dir;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
2773
3229
|
// src/utils/security-plan.ts
|
|
2774
3230
|
var SEVERITIES = ["critical", "high", "moderate", "low", "info"];
|
|
2775
3231
|
function isAuditOverride(key) {
|
|
@@ -2965,37 +3421,37 @@ function writeAuditSettings(content, next) {
|
|
|
2965
3421
|
|
|
2966
3422
|
// src/services/audit-security.ts
|
|
2967
3423
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2968
|
-
import { cpSync, existsSync as existsSync7, mkdirSync, mkdtempSync, readFileSync as
|
|
3424
|
+
import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync2, mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2969
3425
|
import { tmpdir } from "os";
|
|
2970
|
-
import { dirname as
|
|
3426
|
+
import { dirname as dirname6, join as join11, relative as relative2 } from "path";
|
|
2971
3427
|
import { confirm, select as select2 } from "@inquirer/prompts";
|
|
2972
3428
|
var WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
2973
3429
|
var LOCK_FILE = "pnpm-lock.yaml";
|
|
2974
3430
|
var RESOLUTION_FILES = ["package.json", LOCK_FILE, ".npmrc", ".pnpmfile.cjs"];
|
|
2975
|
-
var readOr = (path, fallback = "") => existsSync7(path) ?
|
|
2976
|
-
var
|
|
3431
|
+
var readOr = (path, fallback = "") => existsSync7(path) ? readFileSync3(path, "utf8") : fallback;
|
|
3432
|
+
var count2 = (n, noun, plural2 = `${noun}s`) => `${n} ${n === 1 ? noun : plural2}`;
|
|
2977
3433
|
var isBreaking = (item) => item.remedy.kind !== "none" && item.remedy.breaking;
|
|
2978
3434
|
var needsDecision = (item) => item.standing === "new" && (item.remedy.kind !== "override" || item.remedy.breaking || item.direct);
|
|
2979
3435
|
var sameEntries = (a, b) => Object.keys(a).length === Object.keys(b).length && Object.entries(a).every(([key, value]) => b[key] === value);
|
|
2980
3436
|
async function createStaging(forkPath, workspaceYaml) {
|
|
2981
|
-
const staging = mkdtempSync(
|
|
3437
|
+
const staging = mkdtempSync(join11(tmpdir(), "cella-audit-"));
|
|
2982
3438
|
const projects = await runPnpmJson(["ls", "-r", "--depth", "-1", "--json"], forkPath) ?? [];
|
|
2983
3439
|
const files = [
|
|
2984
3440
|
...RESOLUTION_FILES,
|
|
2985
3441
|
...readPatchFiles(workspaceYaml),
|
|
2986
|
-
...projects.map((project) =>
|
|
3442
|
+
...projects.map((project) => join11(relative2(forkPath, project.path), "package.json"))
|
|
2987
3443
|
];
|
|
2988
3444
|
for (const file of new Set(files)) {
|
|
2989
|
-
if (file.startsWith("..") || !existsSync7(
|
|
2990
|
-
|
|
2991
|
-
cpSync(
|
|
3445
|
+
if (file.startsWith("..") || !existsSync7(join11(forkPath, file))) continue;
|
|
3446
|
+
mkdirSync2(dirname6(join11(staging, file)), { recursive: true });
|
|
3447
|
+
cpSync(join11(forkPath, file), join11(staging, file));
|
|
2992
3448
|
}
|
|
2993
3449
|
return staging;
|
|
2994
3450
|
}
|
|
2995
3451
|
async function resolveStaging(staging, workspaceYaml, settings) {
|
|
2996
|
-
|
|
3452
|
+
writeFileSync2(join11(staging, WORKSPACE_FILE), writeAuditSettings(workspaceYaml, settings));
|
|
2997
3453
|
const result = await runPnpm(["install", "--lockfile-only", "--no-frozen-lockfile", "--ignore-scripts"], staging);
|
|
2998
|
-
return result.ok ? { lock: readOr(
|
|
3454
|
+
return result.ok ? { lock: readOr(join11(staging, LOCK_FILE)) } : { error: `${result.stdout}${result.stderr}`.trim() };
|
|
2999
3455
|
}
|
|
3000
3456
|
function lockedVersions(lock, name) {
|
|
3001
3457
|
const escaped = name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
@@ -3096,9 +3552,11 @@ function changeLine(label, before, after) {
|
|
|
3096
3552
|
const counts = moved ? `${before.length} \u2192 ${after.length}` : String(after.length);
|
|
3097
3553
|
return ` ${label.padEnd(9)} ${counts.padEnd(7)} ${colors_default.dim(moved || "unchanged")}`;
|
|
3098
3554
|
}
|
|
3555
|
+
var NO_EDITS = { manifests: {}, files: {} };
|
|
3099
3556
|
async function runSecurityPass(forkPath, options) {
|
|
3100
|
-
const
|
|
3101
|
-
const
|
|
3557
|
+
const { releaseAgeMinutes, edits = NO_EDITS } = options;
|
|
3558
|
+
const workspaceYaml = readOr(join11(forkPath, WORKSPACE_FILE));
|
|
3559
|
+
const workspaceLock = readOr(join11(forkPath, LOCK_FILE));
|
|
3102
3560
|
let previous;
|
|
3103
3561
|
try {
|
|
3104
3562
|
previous = readAuditSettings(workspaceYaml);
|
|
@@ -3106,12 +3564,13 @@ async function runSecurityPass(forkPath, options) {
|
|
|
3106
3564
|
console.error(
|
|
3107
3565
|
`${colors_default.red("\u2717")} ${WORKSPACE_FILE} could not be read: ${error instanceof Error ? error.message : error}`
|
|
3108
3566
|
);
|
|
3109
|
-
return;
|
|
3567
|
+
return false;
|
|
3110
3568
|
}
|
|
3111
3569
|
const auditKeys = Object.keys(previous.overrides).filter(isAuditOverride);
|
|
3112
3570
|
const lasting = Object.fromEntries(Object.entries(previous.overrides).filter(([key]) => !isAuditOverride(key)));
|
|
3571
|
+
const updatesManifests = Object.keys(edits.manifests).length > 0;
|
|
3113
3572
|
const spinner = createSpinner(
|
|
3114
|
-
auditKeys.length > 0 ? `resolving without the ${
|
|
3573
|
+
auditKeys.length > 0 ? `resolving without the ${count2(auditKeys.length, "audit override")}...` : "resolving the workspace..."
|
|
3115
3574
|
);
|
|
3116
3575
|
const fail = (message, detail = "") => {
|
|
3117
3576
|
spinner.stop();
|
|
@@ -3119,16 +3578,21 @@ async function runSecurityPass(forkPath, options) {
|
|
|
3119
3578
|
if (detail) console.error(colors_default.dim(detail.split("\n").slice(-12).join("\n")));
|
|
3120
3579
|
console.info(colors_default.dim(" the workspace is unchanged"));
|
|
3121
3580
|
console.info();
|
|
3581
|
+
return false;
|
|
3122
3582
|
};
|
|
3123
3583
|
const staging = await createStaging(forkPath, workspaceYaml);
|
|
3124
3584
|
try {
|
|
3585
|
+
for (const [file, content] of Object.entries(edits.manifests)) writeFileSync2(join11(staging, file), content);
|
|
3125
3586
|
const bare = await resolveStaging(staging, workspaceYaml, { ...previous, overrides: lasting, ignoredGhsas: [] });
|
|
3126
|
-
if ("error" in bare)
|
|
3587
|
+
if ("error" in bare) {
|
|
3588
|
+
const without = updatesManifests ? "with the selected updates" : "without its audit overrides";
|
|
3589
|
+
return fail(`could not resolve the workspace ${without}`, bare.error);
|
|
3590
|
+
}
|
|
3127
3591
|
spinnerText("checking for vulnerabilities...");
|
|
3128
3592
|
const audit = await runPnpmAudit(staging);
|
|
3129
3593
|
if (!audit) return fail("pnpm audit gave no answer, so nothing can be decided");
|
|
3130
3594
|
const advisories = advisoriesFromAudit(audit);
|
|
3131
|
-
spinnerText(`checking ${
|
|
3595
|
+
spinnerText(`checking ${count2(advisories.length, "advisory", "advisories")} against the registry...`);
|
|
3132
3596
|
const versionedExcludes = previous.releaseAgeExcludes.flatMap((entry) => parseVersionedExclude(entry)?.name ?? []);
|
|
3133
3597
|
const names = [.../* @__PURE__ */ new Set([...advisories.map((advisory) => advisory.module), ...versionedExcludes])];
|
|
3134
3598
|
const times = await Promise.all(names.map((name) => fetchPublishTimes(name, forkPath)));
|
|
@@ -3137,8 +3601,6 @@ async function runSecurityPass(forkPath, options) {
|
|
|
3137
3601
|
);
|
|
3138
3602
|
const unanswered = advisories.map((advisory) => advisory.module).filter((name) => !published[name]);
|
|
3139
3603
|
if (unanswered.length > 0) return fail(`the registry did not answer for ${[...new Set(unanswered)].join(", ")}`);
|
|
3140
|
-
const releaseAge = Number.parseInt((await runPnpm(["config", "get", "minimumReleaseAge"], forkPath)).stdout, 10);
|
|
3141
|
-
const releaseAgeMinutes = Number.isNaN(releaseAge) ? 0 : releaseAge;
|
|
3142
3604
|
const releaseAgeHours = Math.round(releaseAgeMinutes / 60);
|
|
3143
3605
|
const planInput = { advisories, published, previous, releaseAgeMinutes, now: Date.now() };
|
|
3144
3606
|
const items = planSecurity(planInput);
|
|
@@ -3156,60 +3618,72 @@ async function runSecurityPass(forkPath, options) {
|
|
|
3156
3618
|
if (!options.apply) {
|
|
3157
3619
|
if (changes) console.info(colors_default.dim("run without --list to apply"));
|
|
3158
3620
|
console.info();
|
|
3159
|
-
return;
|
|
3621
|
+
return false;
|
|
3160
3622
|
}
|
|
3161
3623
|
let decisions;
|
|
3162
3624
|
try {
|
|
3163
3625
|
decisions = await decide(items, releaseAgeHours);
|
|
3164
3626
|
} catch {
|
|
3165
3627
|
console.info(colors_default.dim(" cancelled, the workspace is unchanged"));
|
|
3166
|
-
return;
|
|
3628
|
+
return false;
|
|
3167
3629
|
}
|
|
3168
3630
|
const open = decisions.filter((decision) => decision.action === "open").map((decision) => decision.item.module);
|
|
3169
3631
|
const kept = Object.assign({}, lasting, ...open.map((module) => standingOverrides(module, planInput)));
|
|
3170
3632
|
const next = settingsFor(decisions, kept, excludes);
|
|
3171
3633
|
const nextYaml = writeAuditSettings(workspaceYaml, next);
|
|
3172
|
-
|
|
3173
|
-
|
|
3634
|
+
const settingsChanged = nextYaml !== workspaceYaml;
|
|
3635
|
+
const otherFiles = Object.keys(edits.files);
|
|
3636
|
+
const writeFiles = () => {
|
|
3637
|
+
for (const [file, content] of Object.entries(edits.files)) writeFileSync2(join11(forkPath, file), content);
|
|
3638
|
+
if (otherFiles.length > 0) console.info(`${checkMark} ${otherFiles.join(", ")} updated`);
|
|
3639
|
+
};
|
|
3640
|
+
if (!settingsChanged && !updatesManifests) {
|
|
3641
|
+
writeFiles();
|
|
3642
|
+
const state = `${count2(auditKeys.length, "override")} \xB7 ${next.ignoredGhsas.length} ignored`;
|
|
3174
3643
|
console.info(`${checkMark} security settings are current ${colors_default.dim(`\xB7 ${state}`)}`);
|
|
3175
3644
|
printOpen(open);
|
|
3176
|
-
return;
|
|
3645
|
+
return true;
|
|
3177
3646
|
}
|
|
3178
3647
|
const summarize = () => {
|
|
3648
|
+
if (!settingsChanged) return;
|
|
3179
3649
|
console.info(colors_default.bold(WORKSPACE_FILE));
|
|
3180
3650
|
console.info(changeLine("overrides", auditKeys, Object.keys(next.overrides).filter(isAuditOverride)));
|
|
3181
3651
|
console.info(changeLine("excludes", previous.releaseAgeExcludes, next.releaseAgeExcludes));
|
|
3182
3652
|
console.info(changeLine("ignored", previous.ignoredGhsas, next.ignoredGhsas));
|
|
3183
3653
|
console.info();
|
|
3184
3654
|
};
|
|
3185
|
-
if (sameEntries(next.overrides, previous.overrides)) {
|
|
3186
|
-
|
|
3655
|
+
if (!updatesManifests && sameEntries(next.overrides, previous.overrides)) {
|
|
3656
|
+
writeFiles();
|
|
3657
|
+
writeFileSync2(join11(forkPath, WORKSPACE_FILE), nextYaml);
|
|
3187
3658
|
console.info(`${checkMark} ${WORKSPACE_FILE} updated ${colors_default.dim("\xB7 nothing to install")}`);
|
|
3188
3659
|
printOpen(open);
|
|
3189
3660
|
summarize();
|
|
3190
|
-
return;
|
|
3661
|
+
return true;
|
|
3191
3662
|
}
|
|
3192
3663
|
const stagingSpinner = createSpinner("resolving with the new settings...");
|
|
3193
3664
|
const staged = await resolveStaging(staging, workspaceYaml, next);
|
|
3194
3665
|
stagingSpinner.stop();
|
|
3195
3666
|
if ("error" in staged) return fail("could not resolve the workspace with the new settings", staged.error);
|
|
3196
|
-
|
|
3197
|
-
|
|
3667
|
+
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(join11(forkPath, file))]));
|
|
3669
|
+
before[WORKSPACE_FILE] = workspaceYaml;
|
|
3670
|
+
before[LOCK_FILE] = workspaceLock;
|
|
3671
|
+
for (const [file, content] of Object.entries(written)) writeFileSync2(join11(forkPath, file), content);
|
|
3198
3672
|
const install = spawnSync2("pnpm", ["install"], { cwd: forkPath, stdio: "inherit" });
|
|
3199
3673
|
console.info();
|
|
3200
3674
|
if (install.status !== 0) {
|
|
3201
|
-
|
|
3202
|
-
writeFileSync(join9(forkPath, LOCK_FILE), workspaceLock);
|
|
3675
|
+
for (const [file, content] of Object.entries(before)) writeFileSync2(join11(forkPath, file), content);
|
|
3203
3676
|
console.error(`${colors_default.red("\u2717")} pnpm install failed (exit code ${install.status})`);
|
|
3204
|
-
console.info(colors_default.dim(` ${
|
|
3677
|
+
console.info(colors_default.dim(` ${Object.keys(before).join(", ")} are back as they were; run pnpm install to match`));
|
|
3205
3678
|
console.info();
|
|
3206
|
-
return;
|
|
3679
|
+
return false;
|
|
3207
3680
|
}
|
|
3208
3681
|
const verified = await runPnpmAudit(forkPath);
|
|
3209
3682
|
const reported = verified ? [...new Set(advisoriesFromAudit(verified).map((advisory) => advisory.module))] : open;
|
|
3210
3683
|
console.info(`${checkMark} installed${reported.length === 0 ? ` ${colors_default.dim("\xB7")} audit clean` : ""}`);
|
|
3211
3684
|
printOpen(reported);
|
|
3212
3685
|
summarize();
|
|
3686
|
+
return true;
|
|
3213
3687
|
} finally {
|
|
3214
3688
|
spinner.stop();
|
|
3215
3689
|
rmSync(staging, { recursive: true, force: true });
|
|
@@ -3217,7 +3691,7 @@ async function runSecurityPass(forkPath, options) {
|
|
|
3217
3691
|
}
|
|
3218
3692
|
function printOpen(modules) {
|
|
3219
3693
|
if (modules.length > 0) {
|
|
3220
|
-
console.info(`${warningMark} ${
|
|
3694
|
+
console.info(`${warningMark} ${count2(modules.length, "package")} still reported: ${modules.join(", ")}`);
|
|
3221
3695
|
}
|
|
3222
3696
|
console.info();
|
|
3223
3697
|
}
|
|
@@ -3250,7 +3724,7 @@ function printPlan(plan) {
|
|
|
3250
3724
|
printDropped("ignore dropped", droppedIgnores);
|
|
3251
3725
|
const lastingCount = Object.keys(plan.lasting).length;
|
|
3252
3726
|
if (lastingCount > 0) {
|
|
3253
|
-
console.info(colors_default.dim(`${
|
|
3727
|
+
console.info(colors_default.dim(`${count2(lastingCount, "override")} without a version selector left as written`));
|
|
3254
3728
|
}
|
|
3255
3729
|
if (items.some(isBreaking)) {
|
|
3256
3730
|
console.info(colors_default.dim("\u26A0 a breaking jump: another major, or another minor below 1.0"));
|
|
@@ -3260,13 +3734,14 @@ function printPlan(plan) {
|
|
|
3260
3734
|
}
|
|
3261
3735
|
|
|
3262
3736
|
// src/services/audit.ts
|
|
3737
|
+
var DEPENDENCY_SECTIONS = ["dependencies", "devDependencies", "optionalDependencies"];
|
|
3263
3738
|
function createDependentPkgReader() {
|
|
3264
3739
|
const cache = /* @__PURE__ */ new Map();
|
|
3265
3740
|
return (location) => {
|
|
3266
3741
|
let cached = cache.get(location);
|
|
3267
3742
|
if (cached === void 0) {
|
|
3268
3743
|
try {
|
|
3269
|
-
cached = JSON.parse(
|
|
3744
|
+
cached = JSON.parse(readFileSync4(join12(location, "package.json"), "utf8"));
|
|
3270
3745
|
} catch {
|
|
3271
3746
|
cached = null;
|
|
3272
3747
|
}
|
|
@@ -3277,85 +3752,105 @@ function createDependentPkgReader() {
|
|
|
3277
3752
|
}
|
|
3278
3753
|
async function runAudit(config) {
|
|
3279
3754
|
const { forkPath } = config;
|
|
3280
|
-
const spinner = createSpinner("checking
|
|
3755
|
+
const spinner = createSpinner("checking packages and runtimes...");
|
|
3281
3756
|
try {
|
|
3282
|
-
const
|
|
3757
|
+
const pins = await readRuntimePins(forkPath);
|
|
3758
|
+
const now = Date.now();
|
|
3759
|
+
const [outdated, auditResult, releaseAgeMinutes, feeds] = await Promise.all([
|
|
3283
3760
|
runPnpmJson(["-r", "outdated", "--json", "--long"], forkPath),
|
|
3284
|
-
runPnpmAudit(forkPath)
|
|
3761
|
+
runPnpmAudit(forkPath),
|
|
3762
|
+
readReleaseAgeMinutes(forkPath),
|
|
3763
|
+
fetchRuntimeFeeds(pins, forkPath, now)
|
|
3285
3764
|
]);
|
|
3286
3765
|
if (!outdated) {
|
|
3287
3766
|
spinnerFail("pnpm outdated gave no answer: run pnpm -r outdated to see why");
|
|
3288
3767
|
console.info(`${warningMark} security pass skipped: it runs after the update check`);
|
|
3289
3768
|
return;
|
|
3290
3769
|
}
|
|
3291
|
-
const
|
|
3292
|
-
const
|
|
3293
|
-
|
|
3294
|
-
|
|
3770
|
+
const runtimes = planRuntimeUpdates(pins, feeds, { releaseAgeMinutes, now });
|
|
3771
|
+
const rows = [
|
|
3772
|
+
...packageRows(outdated, auditResult ? advisoriesFromAudit(auditResult) : []),
|
|
3773
|
+
...runtimes.updates.map(runtimeRow)
|
|
3774
|
+
];
|
|
3775
|
+
if (rows.length === 0) {
|
|
3776
|
+
spinnerSuccess("all packages and runtimes are up to date");
|
|
3777
|
+
printNotes(forkPath, runtimes.unchecked);
|
|
3295
3778
|
console.info();
|
|
3296
|
-
await runSecurityPass(forkPath, { apply: !config.list });
|
|
3779
|
+
await runSecurityPass(forkPath, { apply: !config.list, releaseAgeMinutes });
|
|
3297
3780
|
return;
|
|
3298
3781
|
}
|
|
3299
|
-
const advisories = auditResult ? advisoriesFromAudit(auditResult) : [];
|
|
3300
|
-
const severityOf = (name) => SEVERITIES.find((severity) => advisories.some((a) => a.module === name && a.severity === severity)) ?? null;
|
|
3301
|
-
const readDependentPkg = createDependentPkgReader();
|
|
3302
|
-
const enhancedPackages = packageNames.map((name) => {
|
|
3303
|
-
const pkg = outdatedPackages[name];
|
|
3304
|
-
const repoUrl = getRepoUrl(pkg.latestManifest?.repository?.url);
|
|
3305
|
-
const pinnedIn = [];
|
|
3306
|
-
for (const d of pkg.dependentPackages) {
|
|
3307
|
-
const spec = readDependentPkg(d.location)?.[pkg.dependencyType]?.[name] || "";
|
|
3308
|
-
if (spec && !spec.startsWith("^") && !spec.startsWith("~") && !spec.startsWith(">")) {
|
|
3309
|
-
pinnedIn.push(d.name);
|
|
3310
|
-
}
|
|
3311
|
-
}
|
|
3312
|
-
return {
|
|
3313
|
-
name,
|
|
3314
|
-
current: pkg.current,
|
|
3315
|
-
latest: pkg.latest,
|
|
3316
|
-
dependentPackages: pkg.dependentPackages,
|
|
3317
|
-
isDev: pkg.dependencyType === "devDependencies",
|
|
3318
|
-
isMajorUpdate: isMajorVersionChange(pkg.current, pkg.latest),
|
|
3319
|
-
pinnedIn,
|
|
3320
|
-
repoUrl,
|
|
3321
|
-
releasesUrl: getReleasesUrl(repoUrl),
|
|
3322
|
-
severity: severityOf(name)
|
|
3323
|
-
};
|
|
3324
|
-
});
|
|
3325
|
-
enhancedPackages.sort((a, b) => {
|
|
3326
|
-
const depCompare = dependentsText(a).localeCompare(dependentsText(b));
|
|
3327
|
-
if (depCompare !== 0) return depCompare;
|
|
3328
|
-
return a.name.localeCompare(b.name);
|
|
3329
|
-
});
|
|
3330
3782
|
spinner.stop();
|
|
3331
|
-
printOutdatedResults(
|
|
3783
|
+
printOutdatedResults(rows, forkPath);
|
|
3784
|
+
printNotes(forkPath, runtimes.unchecked);
|
|
3332
3785
|
console.info();
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
if (outcome === "failed") {
|
|
3336
|
-
console.info(`${warningMark} security pass skipped: it needs a workspace that installs`);
|
|
3786
|
+
if (config.list) {
|
|
3787
|
+
await runSecurityPass(forkPath, { apply: false, releaseAgeMinutes });
|
|
3337
3788
|
return;
|
|
3338
3789
|
}
|
|
3339
|
-
|
|
3790
|
+
const selected = await promptForUpdates(rows);
|
|
3791
|
+
if (!selected) return;
|
|
3792
|
+
const { edits, bumped } = await planEdits(forkPath, rows, selected, pins.nodeImageSuffixes);
|
|
3793
|
+
if (await runSecurityPass(forkPath, { apply: true, releaseAgeMinutes, edits })) printNext(bumped);
|
|
3340
3794
|
} finally {
|
|
3341
3795
|
spinner.stop();
|
|
3342
3796
|
}
|
|
3343
3797
|
}
|
|
3344
|
-
|
|
3345
|
-
|
|
3798
|
+
function packageRows(outdated, advisories) {
|
|
3799
|
+
const severityOf = (name) => SEVERITIES.find((severity) => advisories.some((a) => a.module === name && a.severity === severity)) ?? null;
|
|
3800
|
+
const readDependentPkg = createDependentPkgReader();
|
|
3801
|
+
const rows = Object.entries(outdated).map(([name, pkg]) => {
|
|
3802
|
+
const repoUrl = getRepoUrl(pkg.latestManifest?.repository?.url);
|
|
3803
|
+
const current = pkg.current ?? "missing";
|
|
3804
|
+
const pinnedIn = [];
|
|
3805
|
+
for (const d of pkg.dependentPackages) {
|
|
3806
|
+
const spec = readDependentPkg(d.location)?.[pkg.dependencyType]?.[name] || "";
|
|
3807
|
+
if (spec && !spec.startsWith("^") && !spec.startsWith("~") && !spec.startsWith(">")) {
|
|
3808
|
+
pinnedIn.push(d.name);
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
return {
|
|
3812
|
+
name,
|
|
3813
|
+
current,
|
|
3814
|
+
latest: pkg.latest,
|
|
3815
|
+
dependentPackages: pkg.dependentPackages,
|
|
3816
|
+
isDev: pkg.dependencyType === "devDependencies",
|
|
3817
|
+
breaking: isBreakingJump(current, pkg.latest),
|
|
3818
|
+
pinnedIn,
|
|
3819
|
+
repoUrl,
|
|
3820
|
+
releasesUrl: getReleasesUrl(repoUrl),
|
|
3821
|
+
severity: severityOf(name)
|
|
3822
|
+
};
|
|
3823
|
+
});
|
|
3824
|
+
return rows.sort((a, b) => inText(a).localeCompare(inText(b)) || a.name.localeCompare(b.name));
|
|
3825
|
+
}
|
|
3826
|
+
function runtimeRow(update) {
|
|
3827
|
+
return {
|
|
3828
|
+
name: update.name,
|
|
3829
|
+
current: update.current,
|
|
3830
|
+
latest: update.target,
|
|
3831
|
+
dependentPackages: [],
|
|
3832
|
+
isDev: false,
|
|
3833
|
+
breaking: isBreakingJump(update.current, update.target),
|
|
3834
|
+
pinnedIn: [],
|
|
3835
|
+
repoUrl: null,
|
|
3836
|
+
releasesUrl: update.releasesUrl,
|
|
3837
|
+
severity: update.severity,
|
|
3838
|
+
runtime: update
|
|
3839
|
+
};
|
|
3840
|
+
}
|
|
3841
|
+
var rowKey = (row) => row.runtime ? `runtime:${row.runtime.file}:${row.name}` : row.name;
|
|
3842
|
+
var isRed = (row) => row.severity === "critical" || row.severity === "high";
|
|
3843
|
+
var inText = (row) => row.runtime ? pinLocation(row.runtime.file) : formatDependents(row.dependentPackages.map((d) => d.name));
|
|
3844
|
+
function printOutdatedResults(rows, forkPath) {
|
|
3346
3845
|
const DEV_TAG = " (dev)";
|
|
3846
|
+
const RUNTIME_TAG = " (runtime)";
|
|
3347
3847
|
const MAX_NAME_LEN = 35;
|
|
3348
3848
|
const MAX_DEPENDENTS_LEN = 15;
|
|
3349
|
-
const
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
);
|
|
3353
|
-
const
|
|
3354
|
-
const maxLatestLen = Math.max(6, ...enhancedPackages.map((p) => p.latest.length));
|
|
3355
|
-
const maxDependentsLen = Math.min(
|
|
3356
|
-
MAX_DEPENDENTS_LEN,
|
|
3357
|
-
Math.max(10, ...enhancedPackages.map((p) => dependentsText(p).length))
|
|
3358
|
-
);
|
|
3849
|
+
const tag = (row) => row.runtime ? RUNTIME_TAG : row.isDev ? DEV_TAG : "";
|
|
3850
|
+
const maxNameLen = Math.min(MAX_NAME_LEN, Math.max(7, ...rows.map((p) => p.name.length + tag(p).length)));
|
|
3851
|
+
const maxCurrentLen = Math.max(7, ...rows.map((p) => p.current.length));
|
|
3852
|
+
const maxLatestLen = Math.max(6, ...rows.map((p) => p.latest.length));
|
|
3853
|
+
const maxDependentsLen = Math.min(MAX_DEPENDENTS_LEN, Math.max(10, ...rows.map((p) => inText(p).length)));
|
|
3359
3854
|
const header = [
|
|
3360
3855
|
" ",
|
|
3361
3856
|
colors_default.bold("package".padEnd(maxNameLen)),
|
|
@@ -3365,15 +3860,14 @@ function printOutdatedResults(enhancedPackages, forkPath) {
|
|
|
3365
3860
|
colors_default.bold("links")
|
|
3366
3861
|
].join(" ");
|
|
3367
3862
|
console.info(header);
|
|
3368
|
-
for (const pkg of
|
|
3863
|
+
for (const pkg of rows) {
|
|
3369
3864
|
const vulnIndicator = pkg.severity ? getVulnIcon(pkg.severity) : " ";
|
|
3370
|
-
const
|
|
3371
|
-
const displayName = middleTruncate(fullName, maxNameLen);
|
|
3865
|
+
const displayName = middleTruncate(`${pkg.name}${tag(pkg)}`, maxNameLen);
|
|
3372
3866
|
const name = colors_default.white(displayName.padEnd(maxNameLen));
|
|
3373
3867
|
const current = colors_default.red(pkg.current.padEnd(maxCurrentLen));
|
|
3374
3868
|
const latestText = pkg.latest.padEnd(maxLatestLen);
|
|
3375
|
-
const latest = pkg.
|
|
3376
|
-
const dependents = colors_default.dim(middleTruncate(
|
|
3869
|
+
const latest = pkg.breaking ? colors_default.bold(colors_default.green(latestText)) : colors_default.green(latestText);
|
|
3870
|
+
const dependents = colors_default.dim(middleTruncate(inText(pkg), maxDependentsLen).padEnd(maxDependentsLen));
|
|
3377
3871
|
const links = [];
|
|
3378
3872
|
if (pkg.releasesUrl) {
|
|
3379
3873
|
links.push(hyperlink(colors_default.blue("rel"), pkg.releasesUrl));
|
|
@@ -3386,9 +3880,9 @@ function printOutdatedResults(enhancedPackages, forkPath) {
|
|
|
3386
3880
|
}
|
|
3387
3881
|
console.info();
|
|
3388
3882
|
const dependentMap = /* @__PURE__ */ new Map();
|
|
3389
|
-
for (const pkg of
|
|
3883
|
+
for (const pkg of rows) {
|
|
3390
3884
|
for (const { name, location } of pkg.dependentPackages) {
|
|
3391
|
-
const dependentName = name ||
|
|
3885
|
+
const dependentName = name || basename4(location);
|
|
3392
3886
|
const existing = dependentMap.get(dependentName);
|
|
3393
3887
|
if (existing) {
|
|
3394
3888
|
existing.count++;
|
|
@@ -3399,55 +3893,66 @@ function printOutdatedResults(enhancedPackages, forkPath) {
|
|
|
3399
3893
|
}
|
|
3400
3894
|
const sortedDependents = [...dependentMap.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
3401
3895
|
const maxDependentNameLen = Math.max(0, ...sortedDependents.map(([name]) => name.length));
|
|
3402
|
-
for (const [dependentName, { location, count:
|
|
3403
|
-
const packageJsonPath2 =
|
|
3896
|
+
for (const [dependentName, { location, count: count3 }] of sortedDependents) {
|
|
3897
|
+
const packageJsonPath2 = join12(location, "package.json");
|
|
3404
3898
|
const relativePath = relative3(forkPath, packageJsonPath2);
|
|
3405
3899
|
const paddedName = dependentName.padEnd(maxDependentNameLen);
|
|
3406
3900
|
console.info(
|
|
3407
|
-
` ${colors_default.dim("\u2022")} ${paddedName} ${hyperlink(relativePath, `file://${packageJsonPath2}`)} ${colors_default.dim(`${
|
|
3901
|
+
` ${colors_default.dim("\u2022")} ${paddedName} ${hyperlink(relativePath, `file://${packageJsonPath2}`)} ${colors_default.dim(`${count3}`)}`
|
|
3408
3902
|
);
|
|
3409
3903
|
}
|
|
3410
3904
|
}
|
|
3411
|
-
function
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
const
|
|
3415
|
-
const
|
|
3905
|
+
function printNotes(forkPath, unchecked) {
|
|
3906
|
+
let held = [];
|
|
3907
|
+
try {
|
|
3908
|
+
const updateConfig = readTopLevel(readFileSync4(join12(forkPath, "pnpm-workspace.yaml"), "utf8"), "updateConfig");
|
|
3909
|
+
const ignored = updateConfig?.ignoreDependencies;
|
|
3910
|
+
held = Array.isArray(ignored) ? ignored.map(String) : [];
|
|
3911
|
+
} catch {
|
|
3912
|
+
}
|
|
3913
|
+
if (held.length > 0) console.info(` ${colors_default.dim(`held: ${held.join(", ")} \xB7 pnpm-workspace.yaml updateConfig`)}`);
|
|
3914
|
+
if (unchecked.length > 0)
|
|
3915
|
+
console.info(` ${colors_default.dim(`not checked, no answer from the feed: ${unchecked.join(", ")}`)}`);
|
|
3916
|
+
}
|
|
3917
|
+
function buildUpdateChoices(rows) {
|
|
3918
|
+
const makeChoice = (row) => {
|
|
3919
|
+
const devTag = row.isDev ? colors_default.dim(" (dev)") : "";
|
|
3920
|
+
const version = `${colors_default.red(row.current)} \u2192 ${colors_default.green(row.latest)}`;
|
|
3921
|
+
const notes = [
|
|
3922
|
+
row.breaking ? colors_default.bold("breaking") : "",
|
|
3923
|
+
row.runtime?.note ? colors_default.dim(row.runtime.note) : "",
|
|
3924
|
+
row.pinnedIn.length > 0 ? `${warningMark} ${colors_default.dim(`pinned in ${row.pinnedIn.join(", ")}`)}` : ""
|
|
3925
|
+
].filter(Boolean);
|
|
3416
3926
|
return {
|
|
3417
|
-
name: `${
|
|
3418
|
-
value:
|
|
3419
|
-
checked:
|
|
3927
|
+
name: `${row.name}${devTag} ${version}${notes.length > 0 ? ` ${notes.join(" ")}` : ""}`,
|
|
3928
|
+
value: rowKey(row),
|
|
3929
|
+
checked: isRed(row)
|
|
3420
3930
|
};
|
|
3421
3931
|
};
|
|
3422
|
-
const
|
|
3423
|
-
const
|
|
3932
|
+
const runtimes = rows.filter((p) => p.runtime);
|
|
3933
|
+
const pinned = rows.filter((p) => !p.runtime && p.pinnedIn.length > 0);
|
|
3934
|
+
const regular = rows.filter((p) => !p.runtime && p.pinnedIn.length === 0);
|
|
3424
3935
|
const choices = [];
|
|
3936
|
+
if (runtimes.length > 0) {
|
|
3937
|
+
choices.push(new Separator(colors_default.yellow(`\u2500\u2500 runtimes (${runtimes.length}) \u2500\u2500`)));
|
|
3938
|
+
choices.push(...runtimes.map(makeChoice));
|
|
3939
|
+
}
|
|
3425
3940
|
if (pinned.length > 0) {
|
|
3426
3941
|
choices.push(new Separator(colors_default.yellow(`\u2500\u2500 pinned (${pinned.length}) \u2500\u2500`)));
|
|
3427
3942
|
choices.push(...pinned.map(makeChoice));
|
|
3428
3943
|
}
|
|
3429
3944
|
if (regular.length > 0) {
|
|
3430
|
-
if (
|
|
3945
|
+
if (choices.length > 0) choices.push(new Separator(colors_default.dim(`\u2500\u2500 packages (${regular.length}) \u2500\u2500`)));
|
|
3431
3946
|
choices.push(...regular.map(makeChoice));
|
|
3432
3947
|
}
|
|
3433
3948
|
return choices;
|
|
3434
3949
|
}
|
|
3435
|
-
async function promptForUpdates(
|
|
3436
|
-
let wantsUpdate;
|
|
3950
|
+
async function promptForUpdates(rows) {
|
|
3437
3951
|
try {
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
console.info();
|
|
3443
|
-
if (!wantsUpdate) return "skipped";
|
|
3444
|
-
const choices = buildUpdateChoices(packages);
|
|
3445
|
-
let selected;
|
|
3446
|
-
try {
|
|
3447
|
-
selected = await checkbox({
|
|
3448
|
-
message: "toggle packages to update",
|
|
3449
|
-
choices,
|
|
3450
|
-
required: true,
|
|
3952
|
+
const selected = await checkbox({
|
|
3953
|
+
message: `toggle what to update ${colors_default.dim("\xB7 enter with nothing ticked skips")}`,
|
|
3954
|
+
choices: buildUpdateChoices(rows),
|
|
3955
|
+
required: false,
|
|
3451
3956
|
pageSize: 20,
|
|
3452
3957
|
loop: false,
|
|
3453
3958
|
theme: {
|
|
@@ -3460,83 +3965,93 @@ async function promptForUpdates(packages, forkPath) {
|
|
|
3460
3965
|
const tips = keys.map(([key, action]) => `${colors_default.dim(key)} ${action}`);
|
|
3461
3966
|
return tips.join(" ");
|
|
3462
3967
|
},
|
|
3463
|
-
renderSelectedChoices: (selectedChoices) => colors_default.dim(`${selectedChoices.length}
|
|
3968
|
+
renderSelectedChoices: (selectedChoices) => colors_default.dim(`${selectedChoices.length} selected`)
|
|
3464
3969
|
}
|
|
3465
3970
|
}
|
|
3466
3971
|
});
|
|
3972
|
+
console.info();
|
|
3973
|
+
return selected;
|
|
3467
3974
|
} catch {
|
|
3468
|
-
return
|
|
3975
|
+
return null;
|
|
3469
3976
|
}
|
|
3470
|
-
return executeUpdates(packages, forkPath, selected);
|
|
3471
3977
|
}
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3978
|
+
function bumpText(update) {
|
|
3979
|
+
if (update.pin.kind === "node") return `engines.node ${update.target}`;
|
|
3980
|
+
if (update.pin.kind === "pnpm") return `packageManager pnpm@${update.target}`;
|
|
3981
|
+
return update.pin.to;
|
|
3982
|
+
}
|
|
3983
|
+
async function planEdits(forkPath, rows, selected, nodeImageSuffixes) {
|
|
3984
|
+
const contents = /* @__PURE__ */ new Map();
|
|
3985
|
+
const summary = /* @__PURE__ */ new Map();
|
|
3986
|
+
const bumped = [];
|
|
3987
|
+
const edit = (file, label, item, resolves, change) => {
|
|
3988
|
+
const entry = contents.get(file) ?? { content: readFileSync4(join12(forkPath, file), "utf8"), resolves: false };
|
|
3989
|
+
const next = change(entry.content);
|
|
3990
|
+
if (next === entry.content) return;
|
|
3991
|
+
contents.set(file, { content: next, resolves: entry.resolves || resolves });
|
|
3992
|
+
summary.set(label, [...summary.get(label) ?? [], item]);
|
|
3993
|
+
};
|
|
3994
|
+
const chosen = rows.filter((row) => selected.includes(rowKey(row)));
|
|
3995
|
+
for (const row of chosen) {
|
|
3996
|
+
if (row.runtime) continue;
|
|
3997
|
+
for (const { name: dependent, location } of row.dependentPackages) {
|
|
3998
|
+
const file = relative3(forkPath, join12(location, "package.json"));
|
|
3999
|
+
edit(
|
|
4000
|
+
file,
|
|
4001
|
+
dependent || file,
|
|
4002
|
+
row.name,
|
|
4003
|
+
true,
|
|
4004
|
+
(content) => editJsonText(content, (json) => {
|
|
4005
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
4006
|
+
const deps = json[section];
|
|
4007
|
+
const spec = deps?.[row.name];
|
|
4008
|
+
if (!deps || spec === void 0) continue;
|
|
4009
|
+
const next = rewriteSpecifier(spec, row.latest);
|
|
4010
|
+
if (next) deps[row.name] = next;
|
|
4011
|
+
else console.info(`${warningMark} ${row.name} in ${dependent || file} left as written: ${spec}`);
|
|
4012
|
+
}
|
|
4013
|
+
})
|
|
4014
|
+
);
|
|
3491
4015
|
}
|
|
3492
4016
|
}
|
|
3493
|
-
const
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
4017
|
+
for (const row of chosen) {
|
|
4018
|
+
const update = row.runtime;
|
|
4019
|
+
if (!update) continue;
|
|
4020
|
+
if (update.pin.kind === "node") {
|
|
4021
|
+
const missing = await missingNodeImages(update.target, nodeImageSuffixes);
|
|
4022
|
+
if (missing.length > 0) {
|
|
4023
|
+
console.info(`${warningMark} node ${update.target} skipped: ${missing.join(", ")} is not on docker hub yet`);
|
|
4024
|
+
continue;
|
|
4025
|
+
}
|
|
3502
4026
|
}
|
|
4027
|
+
edit(update.file, update.file, bumpText(update), false, (content) => applyRuntimeUpdate(content, update));
|
|
4028
|
+
bumped.push(update);
|
|
3503
4029
|
}
|
|
3504
|
-
for (const [
|
|
3505
|
-
|
|
3506
|
-
console.info(`${colors_default.cyan("\u2191")} ${colors_default.bold(workspace)}: ${colors_default.dim(pkgList)}`);
|
|
4030
|
+
for (const [label, items] of summary) {
|
|
4031
|
+
console.info(`${colors_default.cyan("\u2191")} ${colors_default.bold(label)}: ${colors_default.dim(items.join(", "))}`);
|
|
3507
4032
|
}
|
|
3508
|
-
console.info();
|
|
3509
|
-
const
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
const normalizeResult = spawnSync3("pnpm", ["-r", "up", ...selectedCatalogPackages], {
|
|
3513
|
-
cwd: forkPath,
|
|
3514
|
-
stdio: "inherit"
|
|
3515
|
-
});
|
|
3516
|
-
console.info();
|
|
3517
|
-
if (normalizeResult.status !== 0) {
|
|
3518
|
-
console.error(colors_default.red(`\u2717 catalog normalization failed (exit code ${normalizeResult.status})`));
|
|
3519
|
-
return "failed";
|
|
3520
|
-
}
|
|
3521
|
-
}
|
|
3522
|
-
const result = spawnSync3("pnpm", ["-r", "up", "--latest", ...selectedPackages], { cwd: forkPath, stdio: "inherit" });
|
|
3523
|
-
console.info();
|
|
3524
|
-
if (result.status !== 0) {
|
|
3525
|
-
console.error(colors_default.red(`\u2717 update failed (exit code ${result.status})`));
|
|
3526
|
-
return "failed";
|
|
4033
|
+
if (summary.size > 0) console.info();
|
|
4034
|
+
const edits = { manifests: {}, files: {} };
|
|
4035
|
+
for (const [file, { content, resolves }] of contents) {
|
|
4036
|
+
(resolves ? edits.manifests : edits.files)[file] = content;
|
|
3527
4037
|
}
|
|
3528
|
-
|
|
3529
|
-
console.info();
|
|
3530
|
-
return "updated";
|
|
4038
|
+
return { edits, bumped };
|
|
3531
4039
|
}
|
|
3532
|
-
function
|
|
3533
|
-
const
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
return /* @__PURE__ */ new Set();
|
|
4040
|
+
function printNext(bumped) {
|
|
4041
|
+
const steps = [];
|
|
4042
|
+
const node = bumped.find((update) => update.pin.kind === "node");
|
|
4043
|
+
if (node && process.version !== `v${node.target}`) {
|
|
4044
|
+
const command = getEnv("VOLTA_HOME") ? `volta install node@${node.target}` : `switch to node ${node.target}`;
|
|
4045
|
+
steps.push([command, `this machine runs ${process.version.slice(1)}`]);
|
|
3539
4046
|
}
|
|
4047
|
+
for (const update of bumped) {
|
|
4048
|
+
if (update.pin.kind === "image") steps.push([`docker build -f ${update.file} .`, "check that the image builds"]);
|
|
4049
|
+
}
|
|
4050
|
+
if (steps.length === 0) return;
|
|
4051
|
+
const width = Math.max(...steps.map(([command]) => command.length));
|
|
4052
|
+
console.info(colors_default.bold("next"));
|
|
4053
|
+
for (const [command, why] of steps) console.info(` ${colors_default.dim("\u2022")} ${command.padEnd(width)} ${colors_default.dim(why)}`);
|
|
4054
|
+
console.info();
|
|
3540
4055
|
}
|
|
3541
4056
|
|
|
3542
4057
|
// src/services/contributions.ts
|
|
@@ -3552,111 +4067,9 @@ import {
|
|
|
3552
4067
|
} from "@inquirer/core";
|
|
3553
4068
|
import { select as select3 } from "@inquirer/prompts";
|
|
3554
4069
|
|
|
3555
|
-
// src/utils/diff.ts
|
|
3556
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
3557
|
-
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3558
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
3559
|
-
import { basename as basename3, join as join11 } from "path";
|
|
3560
|
-
import process6 from "process";
|
|
3561
|
-
function gitDiffFile(cwd, range, filePath, options = {}) {
|
|
3562
|
-
const args = ["diff", "--no-color"];
|
|
3563
|
-
if (options.dstPrefix) args.push("--src-prefix=cella/", `--dst-prefix=${options.dstPrefix}/`);
|
|
3564
|
-
args.push(range, "--", filePath);
|
|
3565
|
-
try {
|
|
3566
|
-
return execFileSync2("git", args, { cwd, maxBuffer: MAX_BUFFER });
|
|
3567
|
-
} catch (error) {
|
|
3568
|
-
const stderr = error.stderr?.toString().trim();
|
|
3569
|
-
throw new Error(stderr || `failed to diff ${filePath}`);
|
|
3570
|
-
}
|
|
3571
|
-
}
|
|
3572
|
-
function escapeHtml(text) {
|
|
3573
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3574
|
-
}
|
|
3575
|
-
async function renderDiffPage(patch, meta) {
|
|
3576
|
-
const { preloadPatchDiff } = await import("@pierre/diffs/ssr");
|
|
3577
|
-
const { prerenderedHTML } = await preloadPatchDiff({
|
|
3578
|
-
patch,
|
|
3579
|
-
options: {
|
|
3580
|
-
diffStyle: "split",
|
|
3581
|
-
themeType: "system",
|
|
3582
|
-
lineDiffType: "word-alt",
|
|
3583
|
-
hunkSeparators: "line-info",
|
|
3584
|
-
overflow: "wrap",
|
|
3585
|
-
disableFileHeader: true
|
|
3586
|
-
}
|
|
3587
|
-
});
|
|
3588
|
-
const title = `${escapeHtml(meta.filePath)} \xB7 ${escapeHtml(meta.srcLabel)} vs ${escapeHtml(meta.dstLabel)}`;
|
|
3589
|
-
const note = meta.note ? `<span class="note">\xB7 ${escapeHtml(meta.note)}</span>` : "";
|
|
3590
|
-
return `<!doctype html>
|
|
3591
|
-
<html lang="en">
|
|
3592
|
-
<head>
|
|
3593
|
-
<meta charset="utf-8">
|
|
3594
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
3595
|
-
<title>${title}</title>
|
|
3596
|
-
<style>
|
|
3597
|
-
:root { color-scheme: light dark; }
|
|
3598
|
-
* { box-sizing: border-box; }
|
|
3599
|
-
body {
|
|
3600
|
-
margin: 0;
|
|
3601
|
-
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
3602
|
-
background: light-dark(#fafafa, #111);
|
|
3603
|
-
color: light-dark(#111, #eee);
|
|
3604
|
-
}
|
|
3605
|
-
header {
|
|
3606
|
-
display: flex; align-items: baseline; gap: 0.75rem; flex-wrap: wrap;
|
|
3607
|
-
padding: 0.9rem 1.25rem;
|
|
3608
|
-
border-bottom: 1px solid light-dark(#e2e2e2, #2a2a2a);
|
|
3609
|
-
position: sticky; top: 0;
|
|
3610
|
-
background: inherit;
|
|
3611
|
-
}
|
|
3612
|
-
header .brand { font-weight: 600; color: light-dark(#0891b2, #22d3ee); }
|
|
3613
|
-
header code { font-family: ui-monospace, monospace; font-size: 0.9rem; }
|
|
3614
|
-
header .sides { margin-left: auto; font-size: 0.8rem; opacity: 0.7; }
|
|
3615
|
-
main { max-width: 1400px; margin: 1rem auto; padding: 0 1rem 2rem; }
|
|
3616
|
-
.diff-shell {
|
|
3617
|
-
border: 1px solid light-dark(#e2e2e2, #2a2a2a);
|
|
3618
|
-
border-radius: 8px; overflow: hidden;
|
|
3619
|
-
background: light-dark(#fff, #161616);
|
|
3620
|
-
}
|
|
3621
|
-
</style>
|
|
3622
|
-
</head>
|
|
3623
|
-
<body>
|
|
3624
|
-
<header>
|
|
3625
|
-
<span class="brand">\u29C8 cella diff</span>
|
|
3626
|
-
<code>${escapeHtml(meta.filePath)}</code>
|
|
3627
|
-
<span class="sides">${escapeHtml(meta.srcLabel)} \u2190 \u2192 ${escapeHtml(meta.dstLabel)} ${note}</span>
|
|
3628
|
-
</header>
|
|
3629
|
-
<main>
|
|
3630
|
-
<!-- Declarative shadow DOM: the library's CSS targets :host, which only
|
|
3631
|
-
matches inside a shadow root (its browser component renders into one). -->
|
|
3632
|
-
<div class="diff-shell">
|
|
3633
|
-
<template shadowrootmode="open">
|
|
3634
|
-
${prerenderedHTML}
|
|
3635
|
-
</template>
|
|
3636
|
-
</div>
|
|
3637
|
-
</main>
|
|
3638
|
-
</body>
|
|
3639
|
-
</html>`;
|
|
3640
|
-
}
|
|
3641
|
-
function openWithDefaultApp(target) {
|
|
3642
|
-
try {
|
|
3643
|
-
execFileSync2(process6.platform === "darwin" ? "open" : "xdg-open", [target], { stdio: "ignore" });
|
|
3644
|
-
} catch {
|
|
3645
|
-
}
|
|
3646
|
-
}
|
|
3647
|
-
async function openDiffInBrowser(patch, meta, repoPath) {
|
|
3648
|
-
const html = await renderDiffPage(patch, meta);
|
|
3649
|
-
const dir = join11(tmpdir2(), `cella-diff-${basename3(repoPath)}`);
|
|
3650
|
-
mkdirSync2(dir, { recursive: true });
|
|
3651
|
-
const outPath = join11(dir, `${meta.filePath.replace(/[^\w.-]+/g, "-")}.html`);
|
|
3652
|
-
writeFileSync2(outPath, html);
|
|
3653
|
-
openWithDefaultApp(outPath);
|
|
3654
|
-
return outPath;
|
|
3655
|
-
}
|
|
3656
|
-
|
|
3657
4070
|
// src/services/contrib-core.ts
|
|
3658
4071
|
import { rmSync as rmSync2 } from "fs";
|
|
3659
|
-
import { dirname as
|
|
4072
|
+
import { dirname as dirname7, join as join13, resolve as resolve4 } from "path";
|
|
3660
4073
|
async function listFilesAtRef(repoPath, ref) {
|
|
3661
4074
|
const raw = await git(["ls-tree", "-r", "--name-only", ref], repoPath);
|
|
3662
4075
|
return new Set(raw.split("\n").filter(Boolean));
|
|
@@ -3672,10 +4085,10 @@ async function detectContributableFiles(repoPath, baseRef, forkRef, config, fork
|
|
|
3672
4085
|
const baseFiles = await listFilesAtRef(repoPath, baseRef);
|
|
3673
4086
|
const baseDirs = /* @__PURE__ */ new Set(["."]);
|
|
3674
4087
|
for (const f of baseFiles) {
|
|
3675
|
-
let dir =
|
|
4088
|
+
let dir = dirname7(f);
|
|
3676
4089
|
while (dir !== ".") {
|
|
3677
4090
|
baseDirs.add(dir);
|
|
3678
|
-
dir =
|
|
4091
|
+
dir = dirname7(dir);
|
|
3679
4092
|
}
|
|
3680
4093
|
}
|
|
3681
4094
|
const files = [];
|
|
@@ -3688,7 +4101,7 @@ async function detectContributableFiles(repoPath, baseRef, forkRef, config, fork
|
|
|
3688
4101
|
if (inCella && !inFork) {
|
|
3689
4102
|
kind = "deleted";
|
|
3690
4103
|
} else if (!inCella && inFork) {
|
|
3691
|
-
if (!baseDirs.has(
|
|
4104
|
+
if (!baseDirs.has(dirname7(file.path))) continue;
|
|
3692
4105
|
kind = "created";
|
|
3693
4106
|
} else {
|
|
3694
4107
|
kind = "modified";
|
|
@@ -3712,7 +4125,7 @@ async function readBlobInfo(repoPath, ref, filePath) {
|
|
|
3712
4125
|
async function buildContribBranch(repoPath, baseRef, forkRef, files, forkName) {
|
|
3713
4126
|
const branch = `contrib/${forkName}`;
|
|
3714
4127
|
const gitDir = resolve4(repoPath, await git(["rev-parse", "--git-dir"], repoPath));
|
|
3715
|
-
const tmpIndex =
|
|
4128
|
+
const tmpIndex = join13(gitDir, `tmp-contrib-index-${forkName.replace(/[^\w.-]/g, "_")}`);
|
|
3716
4129
|
const indexEnv = { GIT_INDEX_FILE: tmpIndex };
|
|
3717
4130
|
try {
|
|
3718
4131
|
await git(["read-tree", baseRef], repoPath, { env: indexEnv });
|
|
@@ -3748,10 +4161,10 @@ ${commitBody}`;
|
|
|
3748
4161
|
|
|
3749
4162
|
// src/services/fork-utils.ts
|
|
3750
4163
|
import { existsSync as existsSync8 } from "fs";
|
|
3751
|
-
import { dirname as
|
|
4164
|
+
import { dirname as dirname8, resolve as resolve5 } from "path";
|
|
3752
4165
|
async function resolveForkBasePath(repoPath) {
|
|
3753
4166
|
const commonDir = await git(["rev-parse", "--git-common-dir"], repoPath, { ignoreErrors: true });
|
|
3754
|
-
return commonDir ?
|
|
4167
|
+
return commonDir ? dirname8(resolve5(repoPath, commonDir)) : repoPath;
|
|
3755
4168
|
}
|
|
3756
4169
|
function validateForkPath(fork, basePath, requireConfig = false) {
|
|
3757
4170
|
const resolvedPath = resolve5(basePath, fork.localPath);
|
|
@@ -3831,8 +4244,13 @@ var contribPrompt = createPrompt((config, done) => {
|
|
|
3831
4244
|
try {
|
|
3832
4245
|
const patch = gitDiffFile(cwd, `${baseRef}..${branch}`, path);
|
|
3833
4246
|
if (patch.length === 0) return setStatusMsg(`no changes for ${path}`);
|
|
3834
|
-
|
|
3835
|
-
|
|
4247
|
+
const page2 = await writeDiffPage(
|
|
4248
|
+
[{ files: [{ path, patch: patch.toString() }] }],
|
|
4249
|
+
{ title: path, note: `cella \u2190 \u2192 ${forkName}` },
|
|
4250
|
+
cwd,
|
|
4251
|
+
path
|
|
4252
|
+
);
|
|
4253
|
+
setStatusMsg(openInBrowser(page2) ? `opened ${path} in browser` : `diff page: ${page2}`);
|
|
3836
4254
|
} catch (error) {
|
|
3837
4255
|
setStatusMsg(`diff failed: ${errorMessage(error)}`);
|
|
3838
4256
|
}
|
|
@@ -4061,20 +4479,20 @@ async function runContributions(config) {
|
|
|
4061
4479
|
|
|
4062
4480
|
// src/services/forks.ts
|
|
4063
4481
|
import { resolve as resolve6 } from "path";
|
|
4064
|
-
import
|
|
4482
|
+
import process8 from "process";
|
|
4065
4483
|
import { Separator as Separator2, select as select5 } from "@inquirer/prompts";
|
|
4066
4484
|
|
|
4067
4485
|
// src/services/sync.ts
|
|
4068
|
-
import { spawnSync as
|
|
4486
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
4069
4487
|
import { select as select4 } from "@inquirer/prompts";
|
|
4070
4488
|
|
|
4071
4489
|
// src/utils/gh.ts
|
|
4072
|
-
import { spawnSync as
|
|
4490
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
4073
4491
|
function ghAvailable() {
|
|
4074
|
-
return
|
|
4492
|
+
return spawnSync3("gh", ["--version"], { stdio: "ignore" }).status === 0;
|
|
4075
4493
|
}
|
|
4076
4494
|
function runGh(cwd, args) {
|
|
4077
|
-
const result =
|
|
4495
|
+
const result = spawnSync3("gh", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
4078
4496
|
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
4079
4497
|
return { ok: result.status === 0, output };
|
|
4080
4498
|
}
|
|
@@ -4106,12 +4524,12 @@ function closePr(cwd, ref) {
|
|
|
4106
4524
|
}
|
|
4107
4525
|
|
|
4108
4526
|
// src/services/packages.ts
|
|
4109
|
-
import { existsSync as existsSync10, readFileSync as
|
|
4110
|
-
import { join as
|
|
4527
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
4528
|
+
import { join as join15 } from "path";
|
|
4111
4529
|
|
|
4112
4530
|
// src/services/package-usage.ts
|
|
4113
|
-
import { existsSync as existsSync9, readFileSync as
|
|
4114
|
-
import { join as
|
|
4531
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, statSync } from "fs";
|
|
4532
|
+
import { join as join14 } from "path";
|
|
4115
4533
|
var MAX_FILE_BYTES = 1024 * 1024;
|
|
4116
4534
|
function escapeRegExp(value) {
|
|
4117
4535
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -4119,7 +4537,7 @@ function escapeRegExp(value) {
|
|
|
4119
4537
|
function readSearchable(filePath) {
|
|
4120
4538
|
try {
|
|
4121
4539
|
if (statSync(filePath).size > MAX_FILE_BYTES) return "";
|
|
4122
|
-
const text =
|
|
4540
|
+
const text = readFileSync5(filePath, "utf8");
|
|
4123
4541
|
return text.includes("\0") ? "" : text;
|
|
4124
4542
|
} catch {
|
|
4125
4543
|
return "";
|
|
@@ -4127,7 +4545,7 @@ function readSearchable(filePath) {
|
|
|
4127
4545
|
}
|
|
4128
4546
|
function fileSource(forkPath, path) {
|
|
4129
4547
|
let text;
|
|
4130
|
-
return { path, label: path, text: () => text ??= readSearchable(
|
|
4548
|
+
return { path, label: path, text: () => text ??= readSearchable(join14(forkPath, path)) };
|
|
4131
4549
|
}
|
|
4132
4550
|
async function readScriptsAtRef(forkPath, ref, path) {
|
|
4133
4551
|
try {
|
|
@@ -4139,7 +4557,7 @@ async function readScriptsAtRef(forkPath, ref, path) {
|
|
|
4139
4557
|
async function scriptSources(forkPath, upstreamRef, baseRef, path) {
|
|
4140
4558
|
let forkScripts;
|
|
4141
4559
|
try {
|
|
4142
|
-
forkScripts = JSON.parse(
|
|
4560
|
+
forkScripts = JSON.parse(readFileSync5(join14(forkPath, path), "utf8")).scripts ?? {};
|
|
4143
4561
|
} catch {
|
|
4144
4562
|
return [];
|
|
4145
4563
|
}
|
|
@@ -4149,11 +4567,11 @@ async function scriptSources(forkPath, upstreamRef, baseRef, path) {
|
|
|
4149
4567
|
}
|
|
4150
4568
|
function binNames(forkPath, location, name) {
|
|
4151
4569
|
const bareName = name.split("/").pop();
|
|
4152
|
-
for (const dir of [
|
|
4153
|
-
const manifest =
|
|
4570
|
+
for (const dir of [join14(forkPath, location), forkPath]) {
|
|
4571
|
+
const manifest = join14(dir, "node_modules", name, "package.json");
|
|
4154
4572
|
if (!existsSync9(manifest)) continue;
|
|
4155
4573
|
try {
|
|
4156
|
-
const { bin } = JSON.parse(
|
|
4574
|
+
const { bin } = JSON.parse(readFileSync5(manifest, "utf8"));
|
|
4157
4575
|
if (typeof bin === "string") return [bareName];
|
|
4158
4576
|
return bin && typeof bin === "object" ? Object.keys(bin) : [];
|
|
4159
4577
|
} catch {
|
|
@@ -4334,7 +4752,7 @@ function setTopLevelKey(pkg, key, value) {
|
|
|
4334
4752
|
function readPackageJson(filePath) {
|
|
4335
4753
|
if (!existsSync10(filePath)) return null;
|
|
4336
4754
|
try {
|
|
4337
|
-
return JSON.parse(
|
|
4755
|
+
return JSON.parse(readFileSync6(filePath, "utf8"));
|
|
4338
4756
|
} catch (error) {
|
|
4339
4757
|
throw new Error(`cannot parse ${filePath}: ${errorMessage(error)}`);
|
|
4340
4758
|
}
|
|
@@ -4366,13 +4784,13 @@ async function syncPackageJson(config, baseRef, usage, relativePath, keysToSync)
|
|
|
4366
4784
|
const changes = [];
|
|
4367
4785
|
const kept = [];
|
|
4368
4786
|
const pkgRelPath = packageJsonPath(relativePath);
|
|
4369
|
-
const pkgPath =
|
|
4787
|
+
const pkgPath = join15(forkPath, pkgRelPath);
|
|
4370
4788
|
const forkPkg = readPackageJson(pkgPath);
|
|
4371
4789
|
const upstream = await readPackageJsonAtRef(forkPath, upstreamRef, relativePath);
|
|
4372
4790
|
const basePkg = baseRef ? (await readPackageJsonAtRef(forkPath, baseRef, relativePath))?.data : void 0;
|
|
4373
4791
|
if (!upstream) return { updated: false, changes, kept };
|
|
4374
4792
|
if (!forkPkg) {
|
|
4375
|
-
const workspaceArrived = relativePath !== "" && existsSync10(
|
|
4793
|
+
const workspaceArrived = relativePath !== "" && existsSync10(join15(forkPath, relativePath));
|
|
4376
4794
|
if (basePkg || !workspaceArrived || isIgnored(pkgRelPath, config)) return { updated: false, changes, kept };
|
|
4377
4795
|
writeFileSync3(pkgPath, upstream.raw.endsWith("\n") ? upstream.raw : `${upstream.raw}
|
|
4378
4796
|
`, "utf8");
|
|
@@ -4578,6 +4996,7 @@ async function runSync(config, options) {
|
|
|
4578
4996
|
const staged = result.files.some((file) => SYNC_APPLIED_STATUSES.includes(file.status));
|
|
4579
4997
|
const stagedBranch = options?.stagedBranch && result.conflicts.length === 0 && staged ? options.stagedBranch : void 0;
|
|
4580
4998
|
printSyncComplete(result, { stagedBranch });
|
|
4999
|
+
await reportReviewPage(config.forkPath, result, "sync");
|
|
4581
5000
|
await printMigrationNotesLine(config, result);
|
|
4582
5001
|
return result;
|
|
4583
5002
|
}
|
|
@@ -4686,7 +5105,7 @@ async function shipSyncBranch(config, branch) {
|
|
|
4686
5105
|
console.info(colors_default.dim("opening a pull request..."));
|
|
4687
5106
|
const prBody = await buildSyncPrBodyForBranch(forkPath, cutBase);
|
|
4688
5107
|
const bodyArgs = prBody ? ["--body", prBody] : ["--fill"];
|
|
4689
|
-
const pr =
|
|
5108
|
+
const pr = spawnSync4("gh", ["pr", "create", "--base", base, "--head", branch, "--title", prTitle, ...bodyArgs], {
|
|
4690
5109
|
cwd: forkPath,
|
|
4691
5110
|
encoding: "utf8",
|
|
4692
5111
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4717,7 +5136,7 @@ ${pr.stderr ?? ""}`.match(/https?:\/\/\S+/)?.[0];
|
|
|
4717
5136
|
function finalizeWorkspace(forkPath) {
|
|
4718
5137
|
for (const args of [["install"], ["check"]]) {
|
|
4719
5138
|
console.info(colors_default.dim(`running pnpm ${args.join(" ")}...`));
|
|
4720
|
-
const result =
|
|
5139
|
+
const result = spawnSync4("pnpm", args, { cwd: forkPath, stdio: "inherit" });
|
|
4721
5140
|
if (result.status !== 0) return false;
|
|
4722
5141
|
}
|
|
4723
5142
|
return true;
|
|
@@ -4983,7 +5402,7 @@ async function runForks(config) {
|
|
|
4983
5402
|
loop: false
|
|
4984
5403
|
});
|
|
4985
5404
|
if (selectedPath === "_exit") {
|
|
4986
|
-
|
|
5405
|
+
process8.exit(0);
|
|
4987
5406
|
}
|
|
4988
5407
|
const resolvedForkPath = resolve6(forkBasePath, selectedPath);
|
|
4989
5408
|
const selectedFork = forks.find((f) => f.localPath === selectedPath);
|
|
@@ -4998,11 +5417,11 @@ async function runForks(config) {
|
|
|
4998
5417
|
|
|
4999
5418
|
// src/services/stats.ts
|
|
5000
5419
|
import { readFile as readFile3 } from "fs/promises";
|
|
5001
|
-
import { join as
|
|
5420
|
+
import { join as join17 } from "path";
|
|
5002
5421
|
|
|
5003
5422
|
// src/utils/coverage-utils.ts
|
|
5004
|
-
import { existsSync as existsSync11, readFileSync as
|
|
5005
|
-
import { join as
|
|
5423
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
|
|
5424
|
+
import { join as join16, relative as relative4, sep as sep2 } from "path";
|
|
5006
5425
|
var STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
5007
5426
|
function colorPct(pct) {
|
|
5008
5427
|
if (typeof pct !== "number" || !Number.isFinite(pct)) return colors_default.dim("n/a");
|
|
@@ -5020,14 +5439,14 @@ function formatAge(ms) {
|
|
|
5020
5439
|
return `${mins} minute${mins === 1 ? "" : "s"} ago`;
|
|
5021
5440
|
}
|
|
5022
5441
|
function printCoverageSummary(forkPath) {
|
|
5023
|
-
const file =
|
|
5442
|
+
const file = join16(forkPath, ".coverage", "coverage-summary.json");
|
|
5024
5443
|
let summary;
|
|
5025
5444
|
let mtimeMs = 0;
|
|
5026
5445
|
let problem = "no coverage data found";
|
|
5027
5446
|
if (existsSync11(file)) {
|
|
5028
5447
|
try {
|
|
5029
5448
|
mtimeMs = statSync2(file).mtimeMs;
|
|
5030
|
-
summary = JSON.parse(
|
|
5449
|
+
summary = JSON.parse(readFileSync7(file, "utf8"));
|
|
5031
5450
|
} catch {
|
|
5032
5451
|
problem = "could not read coverage-summary.json";
|
|
5033
5452
|
}
|
|
@@ -5071,7 +5490,7 @@ function printCoverageSummary(forkPath) {
|
|
|
5071
5490
|
// src/services/stats.ts
|
|
5072
5491
|
var sourceExtensions = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mjs", "cjs", "json", "jsonc", "css", "html"]);
|
|
5073
5492
|
async function getWorkspacePackages(forkPath) {
|
|
5074
|
-
const content = await readFile3(
|
|
5493
|
+
const content = await readFile3(join17(forkPath, "pnpm-workspace.yaml"), "utf8");
|
|
5075
5494
|
const patterns = readTopLevel(content, "packages");
|
|
5076
5495
|
return (Array.isArray(patterns) ? patterns.map(String) : []).map((pattern) => {
|
|
5077
5496
|
if (pattern.endsWith("/*")) {
|
|
@@ -5429,14 +5848,14 @@ function getForkPath() {
|
|
|
5429
5848
|
}
|
|
5430
5849
|
return resolved;
|
|
5431
5850
|
}
|
|
5432
|
-
let cwd =
|
|
5851
|
+
let cwd = process9.cwd();
|
|
5433
5852
|
if (cwd.endsWith("/cli/cella") || cwd.endsWith("\\cli\\cella")) {
|
|
5434
5853
|
cwd = resolve7(cwd, "../..");
|
|
5435
5854
|
}
|
|
5436
5855
|
return cwd;
|
|
5437
5856
|
}
|
|
5438
5857
|
function preflight(forkPath) {
|
|
5439
|
-
if (!existsSync12(
|
|
5858
|
+
if (!existsSync12(join18(forkPath, ".git"))) {
|
|
5440
5859
|
throw new Error(`not a git repository: ${forkPath}`);
|
|
5441
5860
|
}
|
|
5442
5861
|
}
|
|
@@ -5454,7 +5873,7 @@ var services = {
|
|
|
5454
5873
|
}
|
|
5455
5874
|
};
|
|
5456
5875
|
function isHelpOrVersionRequest() {
|
|
5457
|
-
return
|
|
5876
|
+
return process9.argv.slice(2).some((arg) => arg === "--help" || arg === "-h" || arg === "--version" || arg === "-v") || process9.argv[2] === "help";
|
|
5458
5877
|
}
|
|
5459
5878
|
async function main() {
|
|
5460
5879
|
try {
|
|
@@ -5473,7 +5892,7 @@ async function main() {
|
|
|
5473
5892
|
} catch (error) {
|
|
5474
5893
|
console.error();
|
|
5475
5894
|
console.error(`${colors_default.red("\u2717")} ${errorMessage(error)}`);
|
|
5476
|
-
|
|
5895
|
+
process9.exit(1);
|
|
5477
5896
|
}
|
|
5478
5897
|
}
|
|
5479
5898
|
main();
|