contribute-now 0.2.0-dev.33be40f → 0.2.0-dev.69b11fd
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/dist/index.js +698 -320
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
2
4
|
|
|
3
5
|
// src/index.ts
|
|
4
|
-
import { defineCommand as
|
|
6
|
+
import { defineCommand as defineCommand12, runMain } from "citty";
|
|
5
7
|
|
|
6
8
|
// src/commands/clean.ts
|
|
7
9
|
import { defineCommand } from "citty";
|
|
@@ -30,6 +32,9 @@ var CONFIG_FILENAME = ".contributerc.json";
|
|
|
30
32
|
function getConfigPath(cwd = process.cwd()) {
|
|
31
33
|
return join(cwd, CONFIG_FILENAME);
|
|
32
34
|
}
|
|
35
|
+
function configExists(cwd = process.cwd()) {
|
|
36
|
+
return existsSync(getConfigPath(cwd));
|
|
37
|
+
}
|
|
33
38
|
function readConfig(cwd = process.cwd()) {
|
|
34
39
|
const path = getConfigPath(cwd);
|
|
35
40
|
if (!existsSync(path))
|
|
@@ -638,14 +643,60 @@ async function rebase(branch) {
|
|
|
638
643
|
return run2(["rebase", branch]);
|
|
639
644
|
}
|
|
640
645
|
async function getUpstreamRef() {
|
|
641
|
-
const { exitCode, stdout } = await run2([
|
|
646
|
+
const { exitCode, stdout } = await run2([
|
|
647
|
+
"rev-parse",
|
|
648
|
+
"--abbrev-ref",
|
|
649
|
+
"--symbolic-full-name",
|
|
650
|
+
"@{u}"
|
|
651
|
+
]);
|
|
642
652
|
if (exitCode !== 0)
|
|
643
653
|
return null;
|
|
644
654
|
return stdout.trim() || null;
|
|
645
655
|
}
|
|
656
|
+
async function unsetUpstream() {
|
|
657
|
+
return run2(["branch", "--unset-upstream"]);
|
|
658
|
+
}
|
|
646
659
|
async function rebaseOnto(newBase, oldBase) {
|
|
647
660
|
return run2(["rebase", "--onto", newBase, oldBase]);
|
|
648
661
|
}
|
|
662
|
+
async function getMergeBase(ref1, ref2) {
|
|
663
|
+
const { exitCode, stdout } = await run2(["merge-base", ref1, ref2]);
|
|
664
|
+
if (exitCode !== 0)
|
|
665
|
+
return null;
|
|
666
|
+
return stdout.trim() || null;
|
|
667
|
+
}
|
|
668
|
+
async function getCommitHash(ref) {
|
|
669
|
+
const { exitCode, stdout } = await run2(["rev-parse", ref]);
|
|
670
|
+
if (exitCode !== 0)
|
|
671
|
+
return null;
|
|
672
|
+
return stdout.trim() || null;
|
|
673
|
+
}
|
|
674
|
+
async function determineRebaseStrategy(currentBranch, syncRef) {
|
|
675
|
+
const upstreamRef = await getUpstreamRef();
|
|
676
|
+
if (!upstreamRef) {
|
|
677
|
+
return { strategy: "plain" };
|
|
678
|
+
}
|
|
679
|
+
const upstreamHash = await getCommitHash(upstreamRef);
|
|
680
|
+
if (!upstreamHash) {
|
|
681
|
+
return { strategy: "plain" };
|
|
682
|
+
}
|
|
683
|
+
const slashIdx = upstreamRef.indexOf("/");
|
|
684
|
+
const upstreamBranchName = slashIdx !== -1 ? upstreamRef.slice(slashIdx + 1) : upstreamRef;
|
|
685
|
+
if (upstreamBranchName === currentBranch) {
|
|
686
|
+
return { strategy: "plain" };
|
|
687
|
+
}
|
|
688
|
+
const [forkFromUpstream, forkFromSync] = await Promise.all([
|
|
689
|
+
getMergeBase("HEAD", upstreamRef),
|
|
690
|
+
getMergeBase("HEAD", syncRef)
|
|
691
|
+
]);
|
|
692
|
+
if (forkFromUpstream && forkFromSync && forkFromUpstream === forkFromSync) {
|
|
693
|
+
return { strategy: "plain" };
|
|
694
|
+
}
|
|
695
|
+
if (forkFromUpstream) {
|
|
696
|
+
return { strategy: "onto", ontoOldBase: forkFromUpstream };
|
|
697
|
+
}
|
|
698
|
+
return { strategy: "plain" };
|
|
699
|
+
}
|
|
649
700
|
async function getStagedDiff() {
|
|
650
701
|
const { stdout } = await run2(["diff", "--cached"]);
|
|
651
702
|
return stdout;
|
|
@@ -1565,11 +1616,374 @@ ${pc5.bold(`AI suggested ${validGroups.length} commit group(s):`)}
|
|
|
1565
1616
|
process.exit(0);
|
|
1566
1617
|
}
|
|
1567
1618
|
|
|
1619
|
+
// src/commands/doctor.ts
|
|
1620
|
+
import { execFile as execFileCb3 } from "node:child_process";
|
|
1621
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
1622
|
+
import pc6 from "picocolors";
|
|
1623
|
+
// package.json
|
|
1624
|
+
var package_default = {
|
|
1625
|
+
name: "contribute-now",
|
|
1626
|
+
version: "0.2.0-dev.69b11fd",
|
|
1627
|
+
description: "Git workflow CLI for squash-merge two-branch models. Keeps dev in sync with main after squash merges.",
|
|
1628
|
+
type: "module",
|
|
1629
|
+
bin: {
|
|
1630
|
+
contrib: "dist/index.js",
|
|
1631
|
+
contribute: "dist/index.js"
|
|
1632
|
+
},
|
|
1633
|
+
files: [
|
|
1634
|
+
"dist"
|
|
1635
|
+
],
|
|
1636
|
+
scripts: {
|
|
1637
|
+
build: "bun build src/index.ts --outfile dist/index.js --target node --packages external",
|
|
1638
|
+
cli: "bun run src/index.ts --",
|
|
1639
|
+
dev: "bun src/index.ts",
|
|
1640
|
+
test: "bun test",
|
|
1641
|
+
lint: "biome check .",
|
|
1642
|
+
"lint:fix": "biome check --write .",
|
|
1643
|
+
format: "biome format --write .",
|
|
1644
|
+
"www:dev": "bun run --cwd www dev",
|
|
1645
|
+
"www:build": "bun run --cwd www build",
|
|
1646
|
+
"www:preview": "bun run --cwd www preview"
|
|
1647
|
+
},
|
|
1648
|
+
engines: {
|
|
1649
|
+
node: ">=18",
|
|
1650
|
+
bun: ">=1.0"
|
|
1651
|
+
},
|
|
1652
|
+
keywords: [
|
|
1653
|
+
"git",
|
|
1654
|
+
"workflow",
|
|
1655
|
+
"squash-merge",
|
|
1656
|
+
"sync",
|
|
1657
|
+
"cli",
|
|
1658
|
+
"contribute",
|
|
1659
|
+
"fork",
|
|
1660
|
+
"dev-branch",
|
|
1661
|
+
"clean-commit"
|
|
1662
|
+
],
|
|
1663
|
+
author: "Waren Gonzaga",
|
|
1664
|
+
license: "GPL-3.0",
|
|
1665
|
+
repository: {
|
|
1666
|
+
type: "git",
|
|
1667
|
+
url: "git+https://github.com/warengonzaga/contribute-now.git"
|
|
1668
|
+
},
|
|
1669
|
+
dependencies: {
|
|
1670
|
+
"@clack/prompts": "^1.0.1",
|
|
1671
|
+
"@github/copilot-sdk": "^0.1.25",
|
|
1672
|
+
"@wgtechlabs/log-engine": "^2.3.1",
|
|
1673
|
+
citty: "^0.1.6",
|
|
1674
|
+
figlet: "^1.10.0",
|
|
1675
|
+
picocolors: "^1.1.1"
|
|
1676
|
+
},
|
|
1677
|
+
devDependencies: {
|
|
1678
|
+
"@biomejs/biome": "^2.4.4",
|
|
1679
|
+
"@types/bun": "latest",
|
|
1680
|
+
"@types/figlet": "^1.7.0",
|
|
1681
|
+
typescript: "^5.7.0"
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
|
|
1685
|
+
// src/utils/remote.ts
|
|
1686
|
+
function parseRepoFromUrl(url) {
|
|
1687
|
+
const httpsMatch = url.match(/https?:\/\/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
1688
|
+
if (httpsMatch) {
|
|
1689
|
+
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
1690
|
+
}
|
|
1691
|
+
const sshMatch = url.match(/git@github\.com:([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
1692
|
+
if (sshMatch) {
|
|
1693
|
+
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
1694
|
+
}
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
async function detectForkSetup() {
|
|
1698
|
+
const remotes = await getRemotes();
|
|
1699
|
+
const hasOrigin = remotes.includes("origin");
|
|
1700
|
+
const hasUpstream = remotes.includes("upstream");
|
|
1701
|
+
return {
|
|
1702
|
+
isFork: hasUpstream,
|
|
1703
|
+
originRemote: hasOrigin ? "origin" : null,
|
|
1704
|
+
upstreamRemote: hasUpstream ? "upstream" : null
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
async function getRepoInfoFromRemote(remote = "origin") {
|
|
1708
|
+
const url = await getRemoteUrl(remote);
|
|
1709
|
+
if (!url)
|
|
1710
|
+
return null;
|
|
1711
|
+
return parseRepoFromUrl(url);
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// src/commands/doctor.ts
|
|
1715
|
+
var PASS = ` ${pc6.green("✔")} `;
|
|
1716
|
+
var FAIL = ` ${pc6.red("✗")} `;
|
|
1717
|
+
var WARN = ` ${pc6.yellow("⚠")} `;
|
|
1718
|
+
function printReport(report) {
|
|
1719
|
+
for (const section of report.sections) {
|
|
1720
|
+
console.log(`
|
|
1721
|
+
${pc6.bold(pc6.underline(section.title))}`);
|
|
1722
|
+
for (const check of section.checks) {
|
|
1723
|
+
const prefix = check.ok ? check.warning ? WARN : PASS : FAIL;
|
|
1724
|
+
const text2 = check.detail ? `${check.label} ${pc6.dim(`— ${check.detail}`)}` : check.label;
|
|
1725
|
+
console.log(`${prefix}${text2}`);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
console.log();
|
|
1729
|
+
}
|
|
1730
|
+
function toJson(report) {
|
|
1731
|
+
return JSON.stringify(report.sections.map((s) => ({
|
|
1732
|
+
section: s.title,
|
|
1733
|
+
checks: s.checks.map((c) => ({
|
|
1734
|
+
label: c.label,
|
|
1735
|
+
ok: c.ok,
|
|
1736
|
+
warning: c.warning ?? false,
|
|
1737
|
+
detail: c.detail ?? null
|
|
1738
|
+
}))
|
|
1739
|
+
})), null, 2);
|
|
1740
|
+
}
|
|
1741
|
+
function runCmd(cmd, args) {
|
|
1742
|
+
return new Promise((resolve) => {
|
|
1743
|
+
execFileCb3(cmd, args, (error2, stdout) => {
|
|
1744
|
+
resolve({
|
|
1745
|
+
ok: !error2,
|
|
1746
|
+
stdout: (stdout ?? "").trim()
|
|
1747
|
+
});
|
|
1748
|
+
});
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
async function toolSection() {
|
|
1752
|
+
const checks = [];
|
|
1753
|
+
checks.push({
|
|
1754
|
+
label: `contrib v${package_default.version ?? "unknown"}`,
|
|
1755
|
+
ok: true
|
|
1756
|
+
});
|
|
1757
|
+
const runtime = typeof globalThis.Bun !== "undefined" ? `Bun ${globalThis.Bun.version ?? "?"}` : `Node ${process.version}`;
|
|
1758
|
+
checks.push({ label: runtime, ok: true, detail: `${process.platform}-${process.arch}` });
|
|
1759
|
+
return { title: "Tool", checks };
|
|
1760
|
+
}
|
|
1761
|
+
async function depsSection() {
|
|
1762
|
+
const checks = [];
|
|
1763
|
+
const git = await runCmd("git", ["--version"]);
|
|
1764
|
+
checks.push({
|
|
1765
|
+
label: git.ok ? git.stdout.replace("git version ", "git ") : "git not found",
|
|
1766
|
+
ok: git.ok
|
|
1767
|
+
});
|
|
1768
|
+
const ghInstalled = await checkGhInstalled();
|
|
1769
|
+
if (ghInstalled) {
|
|
1770
|
+
const ghVer = await runCmd("gh", ["--version"]);
|
|
1771
|
+
const ver = ghVer.stdout.split(`
|
|
1772
|
+
`)[0] ?? "gh";
|
|
1773
|
+
checks.push({ label: ver, ok: true });
|
|
1774
|
+
const ghAuth = await checkGhAuth();
|
|
1775
|
+
checks.push({
|
|
1776
|
+
label: ghAuth ? "gh authenticated" : "gh not authenticated",
|
|
1777
|
+
ok: ghAuth,
|
|
1778
|
+
warning: !ghAuth,
|
|
1779
|
+
detail: ghAuth ? undefined : "run `gh auth login`"
|
|
1780
|
+
});
|
|
1781
|
+
} else {
|
|
1782
|
+
checks.push({
|
|
1783
|
+
label: "gh CLI not installed",
|
|
1784
|
+
ok: false,
|
|
1785
|
+
detail: "install from https://cli.github.com"
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
try {
|
|
1789
|
+
await import("@github/copilot-sdk");
|
|
1790
|
+
checks.push({ label: "Copilot SDK importable", ok: true });
|
|
1791
|
+
} catch {
|
|
1792
|
+
checks.push({
|
|
1793
|
+
label: "Copilot SDK not loadable",
|
|
1794
|
+
ok: false,
|
|
1795
|
+
warning: true,
|
|
1796
|
+
detail: "AI features will be unavailable"
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
return { title: "Dependencies", checks };
|
|
1800
|
+
}
|
|
1801
|
+
async function configSection() {
|
|
1802
|
+
const checks = [];
|
|
1803
|
+
const exists = configExists();
|
|
1804
|
+
if (!exists) {
|
|
1805
|
+
checks.push({
|
|
1806
|
+
label: ".contributerc.json not found",
|
|
1807
|
+
ok: false,
|
|
1808
|
+
detail: "run `contrib setup` to create it"
|
|
1809
|
+
});
|
|
1810
|
+
return { title: "Config", checks };
|
|
1811
|
+
}
|
|
1812
|
+
const config = readConfig();
|
|
1813
|
+
if (!config) {
|
|
1814
|
+
checks.push({ label: ".contributerc.json found but invalid", ok: false });
|
|
1815
|
+
return { title: "Config", checks };
|
|
1816
|
+
}
|
|
1817
|
+
checks.push({ label: ".contributerc.json found and valid", ok: true });
|
|
1818
|
+
const desc = WORKFLOW_DESCRIPTIONS[config.workflow] ?? config.workflow;
|
|
1819
|
+
checks.push({
|
|
1820
|
+
label: `Workflow: ${config.workflow}`,
|
|
1821
|
+
ok: true,
|
|
1822
|
+
detail: desc
|
|
1823
|
+
});
|
|
1824
|
+
checks.push({ label: `Role: ${config.role}`, ok: true });
|
|
1825
|
+
checks.push({ label: `Commit convention: ${config.commitConvention}`, ok: true });
|
|
1826
|
+
if (hasDevBranch(config.workflow)) {
|
|
1827
|
+
checks.push({
|
|
1828
|
+
label: `Dev branch: ${config.devBranch ?? "(not set)"}`,
|
|
1829
|
+
ok: !!config.devBranch
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
const ignored = isGitignored();
|
|
1833
|
+
checks.push({
|
|
1834
|
+
label: ignored ? ".contributerc.json in .gitignore" : ".contributerc.json NOT in .gitignore",
|
|
1835
|
+
ok: true,
|
|
1836
|
+
warning: !ignored,
|
|
1837
|
+
detail: ignored ? undefined : "consider adding it to .gitignore"
|
|
1838
|
+
});
|
|
1839
|
+
return { title: "Config", checks };
|
|
1840
|
+
}
|
|
1841
|
+
async function gitSection() {
|
|
1842
|
+
const checks = [];
|
|
1843
|
+
const inRepo = await isGitRepo();
|
|
1844
|
+
checks.push({
|
|
1845
|
+
label: inRepo ? "Inside a git repository" : "Not inside a git repository",
|
|
1846
|
+
ok: inRepo
|
|
1847
|
+
});
|
|
1848
|
+
if (!inRepo)
|
|
1849
|
+
return { title: "Git Environment", checks };
|
|
1850
|
+
const branch = await getCurrentBranch();
|
|
1851
|
+
const head = await runCmd("git", ["rev-parse", "--short", "HEAD"]);
|
|
1852
|
+
checks.push({
|
|
1853
|
+
label: `Branch: ${branch ?? "(detached)"}`,
|
|
1854
|
+
ok: !!branch,
|
|
1855
|
+
detail: head.ok ? `HEAD ${head.stdout}` : undefined
|
|
1856
|
+
});
|
|
1857
|
+
const remotes = await getRemotes();
|
|
1858
|
+
if (remotes.length === 0) {
|
|
1859
|
+
checks.push({ label: "No remotes configured", ok: false, warning: true });
|
|
1860
|
+
} else {
|
|
1861
|
+
for (const remote of remotes) {
|
|
1862
|
+
const url = await getRemoteUrl(remote);
|
|
1863
|
+
const repoInfo = url ? parseRepoFromUrl(url) : null;
|
|
1864
|
+
const detail = repoInfo ? `${repoInfo.owner}/${repoInfo.repo}` : url ?? "unknown URL";
|
|
1865
|
+
checks.push({ label: `Remote: ${remote}`, ok: true, detail });
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
const dirty = await hasUncommittedChanges();
|
|
1869
|
+
checks.push({
|
|
1870
|
+
label: dirty ? "Uncommitted changes detected" : "Working tree clean",
|
|
1871
|
+
ok: true,
|
|
1872
|
+
warning: dirty
|
|
1873
|
+
});
|
|
1874
|
+
return { title: "Git Environment", checks };
|
|
1875
|
+
}
|
|
1876
|
+
async function forkSection() {
|
|
1877
|
+
const checks = [];
|
|
1878
|
+
const fork = await detectForkSetup();
|
|
1879
|
+
checks.push({
|
|
1880
|
+
label: fork.isFork ? "Fork detected (upstream remote exists)" : "Not a fork (no upstream remote)",
|
|
1881
|
+
ok: true
|
|
1882
|
+
});
|
|
1883
|
+
if (fork.originRemote) {
|
|
1884
|
+
checks.push({ label: `Origin remote: ${fork.originRemote}`, ok: true });
|
|
1885
|
+
}
|
|
1886
|
+
if (fork.upstreamRemote) {
|
|
1887
|
+
checks.push({ label: `Upstream remote: ${fork.upstreamRemote}`, ok: true });
|
|
1888
|
+
}
|
|
1889
|
+
return { title: "Fork Detection", checks };
|
|
1890
|
+
}
|
|
1891
|
+
async function workflowSection() {
|
|
1892
|
+
const checks = [];
|
|
1893
|
+
const config = readConfig();
|
|
1894
|
+
if (!config) {
|
|
1895
|
+
checks.push({
|
|
1896
|
+
label: "Cannot resolve workflow (no config)",
|
|
1897
|
+
ok: false,
|
|
1898
|
+
detail: "run `contrib setup` first"
|
|
1899
|
+
});
|
|
1900
|
+
return { title: "Workflow Resolution", checks };
|
|
1901
|
+
}
|
|
1902
|
+
const baseBranch = getBaseBranch(config);
|
|
1903
|
+
checks.push({ label: `Base branch: ${baseBranch}`, ok: true });
|
|
1904
|
+
const sync = getSyncSource(config);
|
|
1905
|
+
checks.push({
|
|
1906
|
+
label: `Sync source: ${sync.ref}`,
|
|
1907
|
+
ok: true,
|
|
1908
|
+
detail: `strategy: ${sync.strategy}`
|
|
1909
|
+
});
|
|
1910
|
+
checks.push({
|
|
1911
|
+
label: `Branch prefixes: ${config.branchPrefixes.join(", ")}`,
|
|
1912
|
+
ok: config.branchPrefixes.length > 0
|
|
1913
|
+
});
|
|
1914
|
+
return { title: "Workflow Resolution", checks };
|
|
1915
|
+
}
|
|
1916
|
+
function envSection() {
|
|
1917
|
+
const checks = [];
|
|
1918
|
+
const vars = ["GITHUB_TOKEN", "GH_TOKEN", "COPILOT_AGENT_TOKEN", "NO_COLOR", "FORCE_COLOR", "CI"];
|
|
1919
|
+
for (const name of vars) {
|
|
1920
|
+
const val = process.env[name];
|
|
1921
|
+
if (val !== undefined) {
|
|
1922
|
+
const isSecret = name.toLowerCase().includes("token");
|
|
1923
|
+
const display = isSecret ? `${val.slice(0, 4)}${"*".repeat(Math.min(val.length - 4, 12))}` : val;
|
|
1924
|
+
checks.push({ label: `${name} = ${display}`, ok: true });
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
if (checks.length === 0) {
|
|
1928
|
+
checks.push({ label: "No relevant environment variables set", ok: true });
|
|
1929
|
+
}
|
|
1930
|
+
return { title: "Environment", checks };
|
|
1931
|
+
}
|
|
1932
|
+
var doctor_default = defineCommand3({
|
|
1933
|
+
meta: {
|
|
1934
|
+
name: "doctor",
|
|
1935
|
+
description: "Diagnose the contribute-now CLI environment and configuration"
|
|
1936
|
+
},
|
|
1937
|
+
args: {
|
|
1938
|
+
json: {
|
|
1939
|
+
type: "boolean",
|
|
1940
|
+
description: "Output report as JSON",
|
|
1941
|
+
default: false
|
|
1942
|
+
}
|
|
1943
|
+
},
|
|
1944
|
+
async run({ args }) {
|
|
1945
|
+
const isJson = args.json;
|
|
1946
|
+
const [tool, deps, config, git, fork, workflow] = await Promise.all([
|
|
1947
|
+
toolSection(),
|
|
1948
|
+
depsSection(),
|
|
1949
|
+
configSection(),
|
|
1950
|
+
gitSection(),
|
|
1951
|
+
forkSection(),
|
|
1952
|
+
workflowSection()
|
|
1953
|
+
]);
|
|
1954
|
+
const env = envSection();
|
|
1955
|
+
const report = {
|
|
1956
|
+
sections: [tool, deps, config, git, fork, workflow, env]
|
|
1957
|
+
};
|
|
1958
|
+
if (isJson) {
|
|
1959
|
+
console.log(toJson(report));
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
heading("\uD83E\uDE7A contribute-now doctor");
|
|
1963
|
+
printReport(report);
|
|
1964
|
+
const total = report.sections.flatMap((s) => s.checks);
|
|
1965
|
+
const failures = total.filter((c) => !c.ok);
|
|
1966
|
+
const warnings = total.filter((c) => c.ok && c.warning);
|
|
1967
|
+
if (failures.length === 0 && warnings.length === 0) {
|
|
1968
|
+
console.log(` ${pc6.green("All checks passed!")} No issues detected.
|
|
1969
|
+
`);
|
|
1970
|
+
} else {
|
|
1971
|
+
if (failures.length > 0) {
|
|
1972
|
+
console.log(` ${pc6.red(`${failures.length} issue${failures.length !== 1 ? "s" : ""} found.`)}`);
|
|
1973
|
+
}
|
|
1974
|
+
if (warnings.length > 0) {
|
|
1975
|
+
console.log(` ${pc6.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}.`)}`);
|
|
1976
|
+
}
|
|
1977
|
+
console.log();
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
});
|
|
1981
|
+
|
|
1568
1982
|
// src/commands/hook.ts
|
|
1569
1983
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1570
1984
|
import { join as join3 } from "node:path";
|
|
1571
|
-
import { defineCommand as
|
|
1572
|
-
import
|
|
1985
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
1986
|
+
import pc7 from "picocolors";
|
|
1573
1987
|
var HOOK_MARKER = "# managed by contribute-now";
|
|
1574
1988
|
function getHooksDir(cwd = process.cwd()) {
|
|
1575
1989
|
return join3(cwd, ".git", "hooks");
|
|
@@ -1607,7 +2021,7 @@ else
|
|
|
1607
2021
|
fi
|
|
1608
2022
|
`;
|
|
1609
2023
|
}
|
|
1610
|
-
var hook_default =
|
|
2024
|
+
var hook_default = defineCommand4({
|
|
1611
2025
|
meta: {
|
|
1612
2026
|
name: "hook",
|
|
1613
2027
|
description: "Install or uninstall the commit-msg git hook"
|
|
@@ -1665,8 +2079,8 @@ async function installHook() {
|
|
|
1665
2079
|
}
|
|
1666
2080
|
writeFileSync2(hookPath, generateHookScript(), { mode: 493 });
|
|
1667
2081
|
success(`commit-msg hook installed.`);
|
|
1668
|
-
info(`Convention: ${
|
|
1669
|
-
info(`Path: ${
|
|
2082
|
+
info(`Convention: ${pc7.bold(CONVENTION_LABELS[config.commitConvention])}`);
|
|
2083
|
+
info(`Path: ${pc7.dim(hookPath)}`);
|
|
1670
2084
|
}
|
|
1671
2085
|
async function uninstallHook() {
|
|
1672
2086
|
heading("\uD83E\uDE9D hook uninstall");
|
|
@@ -1685,30 +2099,9 @@ async function uninstallHook() {
|
|
|
1685
2099
|
}
|
|
1686
2100
|
|
|
1687
2101
|
// src/commands/setup.ts
|
|
1688
|
-
import { defineCommand as
|
|
1689
|
-
import
|
|
1690
|
-
|
|
1691
|
-
// src/utils/remote.ts
|
|
1692
|
-
function parseRepoFromUrl(url) {
|
|
1693
|
-
const httpsMatch = url.match(/https?:\/\/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
1694
|
-
if (httpsMatch) {
|
|
1695
|
-
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
1696
|
-
}
|
|
1697
|
-
const sshMatch = url.match(/git@github\.com:([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
1698
|
-
if (sshMatch) {
|
|
1699
|
-
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
1700
|
-
}
|
|
1701
|
-
return null;
|
|
1702
|
-
}
|
|
1703
|
-
async function getRepoInfoFromRemote(remote = "origin") {
|
|
1704
|
-
const url = await getRemoteUrl(remote);
|
|
1705
|
-
if (!url)
|
|
1706
|
-
return null;
|
|
1707
|
-
return parseRepoFromUrl(url);
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
// src/commands/setup.ts
|
|
1711
|
-
var setup_default = defineCommand4({
|
|
2102
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
2103
|
+
import pc8 from "picocolors";
|
|
2104
|
+
var setup_default = defineCommand5({
|
|
1712
2105
|
meta: {
|
|
1713
2106
|
name: "setup",
|
|
1714
2107
|
description: "Initialize contribute-now config for this repo (.contributerc.json)"
|
|
@@ -1729,7 +2122,7 @@ var setup_default = defineCommand4({
|
|
|
1729
2122
|
workflow = "github-flow";
|
|
1730
2123
|
else if (workflowChoice.startsWith("Git Flow"))
|
|
1731
2124
|
workflow = "git-flow";
|
|
1732
|
-
info(`Workflow: ${
|
|
2125
|
+
info(`Workflow: ${pc8.bold(WORKFLOW_DESCRIPTIONS[workflow])}`);
|
|
1733
2126
|
const conventionChoice = await selectPrompt("Which commit convention should this project use?", [
|
|
1734
2127
|
`${CONVENTION_DESCRIPTIONS["clean-commit"]} (recommended)`,
|
|
1735
2128
|
CONVENTION_DESCRIPTIONS.conventional,
|
|
@@ -1782,8 +2175,8 @@ var setup_default = defineCommand4({
|
|
|
1782
2175
|
detectedRole = roleChoice;
|
|
1783
2176
|
detectionSource = "user selection";
|
|
1784
2177
|
} else {
|
|
1785
|
-
info(`Detected role: ${
|
|
1786
|
-
const confirmed = await confirmPrompt(`Role detected as ${
|
|
2178
|
+
info(`Detected role: ${pc8.bold(detectedRole)} (via ${detectionSource})`);
|
|
2179
|
+
const confirmed = await confirmPrompt(`Role detected as ${pc8.bold(detectedRole)}. Is this correct?`);
|
|
1787
2180
|
if (!confirmed) {
|
|
1788
2181
|
const roleChoice = await selectPrompt("Select your role:", ["maintainer", "contributor"]);
|
|
1789
2182
|
detectedRole = roleChoice;
|
|
@@ -1828,22 +2221,22 @@ var setup_default = defineCommand4({
|
|
|
1828
2221
|
warn(' echo ".contributerc.json" >> .gitignore');
|
|
1829
2222
|
}
|
|
1830
2223
|
console.log();
|
|
1831
|
-
info(`Workflow: ${
|
|
1832
|
-
info(`Convention: ${
|
|
1833
|
-
info(`Role: ${
|
|
2224
|
+
info(`Workflow: ${pc8.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
2225
|
+
info(`Convention: ${pc8.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
|
|
2226
|
+
info(`Role: ${pc8.bold(config.role)}`);
|
|
1834
2227
|
if (config.devBranch) {
|
|
1835
|
-
info(`Main: ${
|
|
2228
|
+
info(`Main: ${pc8.bold(config.mainBranch)} | Dev: ${pc8.bold(config.devBranch)}`);
|
|
1836
2229
|
} else {
|
|
1837
|
-
info(`Main: ${
|
|
2230
|
+
info(`Main: ${pc8.bold(config.mainBranch)}`);
|
|
1838
2231
|
}
|
|
1839
|
-
info(`Origin: ${
|
|
2232
|
+
info(`Origin: ${pc8.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${pc8.bold(config.upstream)}` : ""}`);
|
|
1840
2233
|
}
|
|
1841
2234
|
});
|
|
1842
2235
|
|
|
1843
2236
|
// src/commands/start.ts
|
|
1844
|
-
import { defineCommand as
|
|
1845
|
-
import
|
|
1846
|
-
var start_default =
|
|
2237
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
2238
|
+
import pc9 from "picocolors";
|
|
2239
|
+
var start_default = defineCommand6({
|
|
1847
2240
|
meta: {
|
|
1848
2241
|
name: "start",
|
|
1849
2242
|
description: "Create a new feature branch from the latest base branch"
|
|
@@ -1890,8 +2283,8 @@ var start_default = defineCommand5({
|
|
|
1890
2283
|
if (suggested) {
|
|
1891
2284
|
spinner.success("Branch name suggestion ready.");
|
|
1892
2285
|
console.log(`
|
|
1893
|
-
${
|
|
1894
|
-
const accepted = await confirmPrompt(`Use ${
|
|
2286
|
+
${pc9.dim("AI suggestion:")} ${pc9.bold(pc9.cyan(suggested))}`);
|
|
2287
|
+
const accepted = await confirmPrompt(`Use ${pc9.bold(suggested)} as your branch name?`);
|
|
1895
2288
|
if (accepted) {
|
|
1896
2289
|
branchName = suggested;
|
|
1897
2290
|
} else {
|
|
@@ -1902,14 +2295,14 @@ var start_default = defineCommand5({
|
|
|
1902
2295
|
}
|
|
1903
2296
|
}
|
|
1904
2297
|
if (!hasPrefix(branchName, branchPrefixes)) {
|
|
1905
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
2298
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc9.bold(branchName)}:`, branchPrefixes);
|
|
1906
2299
|
branchName = formatBranchName(prefix, branchName);
|
|
1907
2300
|
}
|
|
1908
2301
|
if (!isValidBranchName(branchName)) {
|
|
1909
2302
|
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
1910
2303
|
process.exit(1);
|
|
1911
2304
|
}
|
|
1912
|
-
info(`Creating branch: ${
|
|
2305
|
+
info(`Creating branch: ${pc9.bold(branchName)}`);
|
|
1913
2306
|
await fetchRemote(syncSource.remote);
|
|
1914
2307
|
const updateResult = await updateLocalBranch(baseBranch, syncSource.ref);
|
|
1915
2308
|
if (updateResult.exitCode !== 0) {}
|
|
@@ -1918,14 +2311,14 @@ var start_default = defineCommand5({
|
|
|
1918
2311
|
error(`Failed to create branch: ${result.stderr}`);
|
|
1919
2312
|
process.exit(1);
|
|
1920
2313
|
}
|
|
1921
|
-
success(`✅ Created ${
|
|
2314
|
+
success(`✅ Created ${pc9.bold(branchName)} from latest ${pc9.bold(baseBranch)}`);
|
|
1922
2315
|
}
|
|
1923
2316
|
});
|
|
1924
2317
|
|
|
1925
2318
|
// src/commands/status.ts
|
|
1926
|
-
import { defineCommand as
|
|
1927
|
-
import
|
|
1928
|
-
var status_default =
|
|
2319
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
2320
|
+
import pc10 from "picocolors";
|
|
2321
|
+
var status_default = defineCommand7({
|
|
1929
2322
|
meta: {
|
|
1930
2323
|
name: "status",
|
|
1931
2324
|
description: "Show sync status of branches"
|
|
@@ -1941,20 +2334,17 @@ var status_default = defineCommand6({
|
|
|
1941
2334
|
process.exit(1);
|
|
1942
2335
|
}
|
|
1943
2336
|
heading("\uD83D\uDCCA contribute-now status");
|
|
1944
|
-
console.log(` ${
|
|
1945
|
-
console.log(` ${
|
|
2337
|
+
console.log(` ${pc10.dim("Workflow:")} ${pc10.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
2338
|
+
console.log(` ${pc10.dim("Role:")} ${pc10.bold(config.role)}`);
|
|
1946
2339
|
console.log();
|
|
1947
2340
|
await fetchAll();
|
|
1948
2341
|
const currentBranch = await getCurrentBranch();
|
|
1949
2342
|
const { mainBranch, origin, upstream, workflow } = config;
|
|
1950
2343
|
const baseBranch = getBaseBranch(config);
|
|
1951
2344
|
const isContributor = config.role === "contributor";
|
|
1952
|
-
const [dirty, fileStatus] = await Promise.all([
|
|
1953
|
-
hasUncommittedChanges(),
|
|
1954
|
-
getFileStatus()
|
|
1955
|
-
]);
|
|
2345
|
+
const [dirty, fileStatus] = await Promise.all([hasUncommittedChanges(), getFileStatus()]);
|
|
1956
2346
|
if (dirty) {
|
|
1957
|
-
console.log(` ${
|
|
2347
|
+
console.log(` ${pc10.yellow("⚠")} ${pc10.yellow("Uncommitted changes in working tree")}`);
|
|
1958
2348
|
console.log();
|
|
1959
2349
|
}
|
|
1960
2350
|
const mainRemote = `${origin}/${mainBranch}`;
|
|
@@ -1970,82 +2360,82 @@ var status_default = defineCommand6({
|
|
|
1970
2360
|
if (currentBranch && currentBranch !== mainBranch && currentBranch !== config.devBranch) {
|
|
1971
2361
|
const branchDiv = await getDivergence(currentBranch, baseBranch);
|
|
1972
2362
|
const branchLine = formatStatus(currentBranch, baseBranch, branchDiv.ahead, branchDiv.behind);
|
|
1973
|
-
console.log(branchLine +
|
|
2363
|
+
console.log(branchLine + pc10.dim(` (current ${pc10.green("*")})`));
|
|
1974
2364
|
} else if (currentBranch) {
|
|
1975
|
-
console.log(
|
|
2365
|
+
console.log(pc10.dim(` (on ${pc10.bold(currentBranch)} branch)`));
|
|
1976
2366
|
}
|
|
1977
2367
|
const hasFiles = fileStatus.staged.length > 0 || fileStatus.modified.length > 0 || fileStatus.untracked.length > 0;
|
|
1978
2368
|
if (hasFiles) {
|
|
1979
2369
|
console.log();
|
|
1980
2370
|
if (fileStatus.staged.length > 0) {
|
|
1981
|
-
console.log(` ${
|
|
2371
|
+
console.log(` ${pc10.green("Staged for commit:")}`);
|
|
1982
2372
|
for (const { file, status } of fileStatus.staged) {
|
|
1983
|
-
console.log(` ${
|
|
2373
|
+
console.log(` ${pc10.green("+")} ${pc10.dim(`${status}:`)} ${file}`);
|
|
1984
2374
|
}
|
|
1985
2375
|
}
|
|
1986
2376
|
if (fileStatus.modified.length > 0) {
|
|
1987
|
-
console.log(` ${
|
|
2377
|
+
console.log(` ${pc10.yellow("Unstaged changes:")}`);
|
|
1988
2378
|
for (const { file, status } of fileStatus.modified) {
|
|
1989
|
-
console.log(` ${
|
|
2379
|
+
console.log(` ${pc10.yellow("~")} ${pc10.dim(`${status}:`)} ${file}`);
|
|
1990
2380
|
}
|
|
1991
2381
|
}
|
|
1992
2382
|
if (fileStatus.untracked.length > 0) {
|
|
1993
|
-
console.log(` ${
|
|
2383
|
+
console.log(` ${pc10.red("Untracked files:")}`);
|
|
1994
2384
|
for (const file of fileStatus.untracked) {
|
|
1995
|
-
console.log(` ${
|
|
2385
|
+
console.log(` ${pc10.red("?")} ${file}`);
|
|
1996
2386
|
}
|
|
1997
2387
|
}
|
|
1998
2388
|
} else if (!dirty) {
|
|
1999
|
-
console.log(` ${
|
|
2389
|
+
console.log(` ${pc10.green("✓")} ${pc10.dim("Working tree clean")}`);
|
|
2000
2390
|
}
|
|
2001
2391
|
const tips = [];
|
|
2002
2392
|
if (fileStatus.staged.length > 0) {
|
|
2003
|
-
tips.push(`Run ${
|
|
2393
|
+
tips.push(`Run ${pc10.bold("contrib commit")} to commit staged changes`);
|
|
2004
2394
|
}
|
|
2005
2395
|
if (fileStatus.modified.length > 0 || fileStatus.untracked.length > 0) {
|
|
2006
|
-
tips.push(`Run ${
|
|
2396
|
+
tips.push(`Run ${pc10.bold("contrib commit")} to stage and commit changes`);
|
|
2007
2397
|
}
|
|
2008
2398
|
if (fileStatus.staged.length === 0 && fileStatus.modified.length === 0 && fileStatus.untracked.length === 0 && currentBranch && currentBranch !== mainBranch && currentBranch !== config.devBranch) {
|
|
2009
2399
|
const branchDiv = await getDivergence(currentBranch, `${origin}/${currentBranch}`);
|
|
2010
2400
|
if (branchDiv.ahead > 0) {
|
|
2011
|
-
tips.push(`Run ${
|
|
2401
|
+
tips.push(`Run ${pc10.bold("contrib submit")} to push and create/update your PR`);
|
|
2012
2402
|
}
|
|
2013
2403
|
}
|
|
2014
2404
|
if (tips.length > 0) {
|
|
2015
2405
|
console.log();
|
|
2016
|
-
console.log(` ${
|
|
2406
|
+
console.log(` ${pc10.dim("\uD83D\uDCA1 Tip:")}`);
|
|
2017
2407
|
for (const tip of tips) {
|
|
2018
|
-
console.log(` ${
|
|
2408
|
+
console.log(` ${pc10.dim(tip)}`);
|
|
2019
2409
|
}
|
|
2020
2410
|
}
|
|
2021
2411
|
console.log();
|
|
2022
2412
|
}
|
|
2023
2413
|
});
|
|
2024
2414
|
function formatStatus(branch, base, ahead, behind) {
|
|
2025
|
-
const label =
|
|
2415
|
+
const label = pc10.bold(branch.padEnd(20));
|
|
2026
2416
|
if (ahead === 0 && behind === 0) {
|
|
2027
|
-
return ` ${
|
|
2417
|
+
return ` ${pc10.green("✓")} ${label} ${pc10.dim(`in sync with ${base}`)}`;
|
|
2028
2418
|
}
|
|
2029
2419
|
if (ahead > 0 && behind === 0) {
|
|
2030
|
-
return ` ${
|
|
2420
|
+
return ` ${pc10.yellow("↑")} ${label} ${pc10.yellow(`${ahead} commit${ahead !== 1 ? "s" : ""} ahead of ${base}`)}`;
|
|
2031
2421
|
}
|
|
2032
2422
|
if (behind > 0 && ahead === 0) {
|
|
2033
|
-
return ` ${
|
|
2423
|
+
return ` ${pc10.red("↓")} ${label} ${pc10.red(`${behind} commit${behind !== 1 ? "s" : ""} behind ${base}`)}`;
|
|
2034
2424
|
}
|
|
2035
|
-
return ` ${
|
|
2425
|
+
return ` ${pc10.red("⚡")} ${label} ${pc10.yellow(`${ahead} ahead`)}${pc10.dim(", ")}${pc10.red(`${behind} behind`)} ${pc10.dim(base)}`;
|
|
2036
2426
|
}
|
|
2037
2427
|
|
|
2038
2428
|
// src/commands/submit.ts
|
|
2039
|
-
import { defineCommand as
|
|
2040
|
-
import
|
|
2429
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
2430
|
+
import pc11 from "picocolors";
|
|
2041
2431
|
async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
2042
|
-
info(`Checking out ${
|
|
2432
|
+
info(`Checking out ${pc11.bold(baseBranch)}...`);
|
|
2043
2433
|
const coResult = await checkoutBranch2(baseBranch);
|
|
2044
2434
|
if (coResult.exitCode !== 0) {
|
|
2045
2435
|
error(`Failed to checkout ${baseBranch}: ${coResult.stderr}`);
|
|
2046
2436
|
process.exit(1);
|
|
2047
2437
|
}
|
|
2048
|
-
info(`Squash merging ${
|
|
2438
|
+
info(`Squash merging ${pc11.bold(featureBranch)} into ${pc11.bold(baseBranch)}...`);
|
|
2049
2439
|
const mergeResult = await mergeSquash(featureBranch);
|
|
2050
2440
|
if (mergeResult.exitCode !== 0) {
|
|
2051
2441
|
error(`Squash merge failed: ${mergeResult.stderr}`);
|
|
@@ -2075,26 +2465,26 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
2075
2465
|
error(`Commit failed: ${commitResult.stderr}`);
|
|
2076
2466
|
process.exit(1);
|
|
2077
2467
|
}
|
|
2078
|
-
info(`Pushing ${
|
|
2468
|
+
info(`Pushing ${pc11.bold(baseBranch)} to ${origin}...`);
|
|
2079
2469
|
const pushResult = await pushBranch(origin, baseBranch);
|
|
2080
2470
|
if (pushResult.exitCode !== 0) {
|
|
2081
2471
|
error(`Failed to push ${baseBranch}: ${pushResult.stderr}`);
|
|
2082
2472
|
process.exit(1);
|
|
2083
2473
|
}
|
|
2084
|
-
info(`Deleting local branch ${
|
|
2474
|
+
info(`Deleting local branch ${pc11.bold(featureBranch)}...`);
|
|
2085
2475
|
const delLocal = await forceDeleteBranch(featureBranch);
|
|
2086
2476
|
if (delLocal.exitCode !== 0) {
|
|
2087
2477
|
warn(`Could not delete local branch: ${delLocal.stderr.trim()}`);
|
|
2088
2478
|
}
|
|
2089
|
-
info(`Deleting remote branch ${
|
|
2479
|
+
info(`Deleting remote branch ${pc11.bold(featureBranch)}...`);
|
|
2090
2480
|
const delRemote = await deleteRemoteBranch(origin, featureBranch);
|
|
2091
2481
|
if (delRemote.exitCode !== 0) {
|
|
2092
2482
|
warn(`Could not delete remote branch: ${delRemote.stderr.trim()}`);
|
|
2093
2483
|
}
|
|
2094
|
-
success(`✅ Squash merged ${
|
|
2095
|
-
info(`Run ${
|
|
2484
|
+
success(`✅ Squash merged ${pc11.bold(featureBranch)} into ${pc11.bold(baseBranch)} and pushed.`);
|
|
2485
|
+
info(`Run ${pc11.bold("contrib start")} to begin a new feature.`);
|
|
2096
2486
|
}
|
|
2097
|
-
var submit_default =
|
|
2487
|
+
var submit_default = defineCommand8({
|
|
2098
2488
|
meta: {
|
|
2099
2489
|
name: "submit",
|
|
2100
2490
|
description: "Push current branch and create a pull request"
|
|
@@ -2134,7 +2524,7 @@ var submit_default = defineCommand7({
|
|
|
2134
2524
|
process.exit(1);
|
|
2135
2525
|
}
|
|
2136
2526
|
if (protectedBranches.includes(currentBranch)) {
|
|
2137
|
-
error(`Cannot submit ${protectedBranches.map((b) =>
|
|
2527
|
+
error(`Cannot submit ${protectedBranches.map((b) => pc11.bold(b)).join(" or ")} as a PR. Switch to your feature branch.`);
|
|
2138
2528
|
process.exit(1);
|
|
2139
2529
|
}
|
|
2140
2530
|
heading("\uD83D\uDE80 contrib submit");
|
|
@@ -2143,7 +2533,7 @@ var submit_default = defineCommand7({
|
|
|
2143
2533
|
if (ghInstalled && ghAuthed) {
|
|
2144
2534
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
2145
2535
|
if (mergedPR) {
|
|
2146
|
-
warn(`PR #${mergedPR.number} (${
|
|
2536
|
+
warn(`PR #${mergedPR.number} (${pc11.bold(mergedPR.title)}) was already merged.`);
|
|
2147
2537
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
2148
2538
|
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
2149
2539
|
if (hasWork) {
|
|
@@ -2151,7 +2541,7 @@ var submit_default = defineCommand7({
|
|
|
2151
2541
|
warn("You have uncommitted changes in your working tree.");
|
|
2152
2542
|
}
|
|
2153
2543
|
if (localWork.unpushedCommits > 0) {
|
|
2154
|
-
warn(`You have ${
|
|
2544
|
+
warn(`You have ${pc11.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not in the merged PR.`);
|
|
2155
2545
|
}
|
|
2156
2546
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
2157
2547
|
const DISCARD = "Discard all changes and clean up";
|
|
@@ -2162,7 +2552,7 @@ var submit_default = defineCommand7({
|
|
|
2162
2552
|
return;
|
|
2163
2553
|
}
|
|
2164
2554
|
if (action === SAVE_NEW_BRANCH) {
|
|
2165
|
-
info(
|
|
2555
|
+
info(pc11.dim("Tip: Describe what you're working on in plain English and we'll generate a branch name."));
|
|
2166
2556
|
const description = await inputPrompt("What are you working on?");
|
|
2167
2557
|
let newBranchName = description;
|
|
2168
2558
|
if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
|
|
@@ -2171,8 +2561,8 @@ var submit_default = defineCommand7({
|
|
|
2171
2561
|
if (suggested) {
|
|
2172
2562
|
spinner.success("Branch name suggestion ready.");
|
|
2173
2563
|
console.log(`
|
|
2174
|
-
${
|
|
2175
|
-
const accepted = await confirmPrompt(`Use ${
|
|
2564
|
+
${pc11.dim("AI suggestion:")} ${pc11.bold(pc11.cyan(suggested))}`);
|
|
2565
|
+
const accepted = await confirmPrompt(`Use ${pc11.bold(suggested)} as your branch name?`);
|
|
2176
2566
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
2177
2567
|
} else {
|
|
2178
2568
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -2180,37 +2570,45 @@ var submit_default = defineCommand7({
|
|
|
2180
2570
|
}
|
|
2181
2571
|
}
|
|
2182
2572
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
2183
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
2573
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc11.bold(newBranchName)}:`, config.branchPrefixes);
|
|
2184
2574
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
2185
2575
|
}
|
|
2186
2576
|
if (!isValidBranchName(newBranchName)) {
|
|
2187
2577
|
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
2188
2578
|
process.exit(1);
|
|
2189
2579
|
}
|
|
2580
|
+
const staleUpstream = await getUpstreamRef();
|
|
2581
|
+
const staleUpstreamHash = staleUpstream ? await getCommitHash(staleUpstream) : null;
|
|
2190
2582
|
const renameResult = await renameBranch(currentBranch, newBranchName);
|
|
2191
2583
|
if (renameResult.exitCode !== 0) {
|
|
2192
2584
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
2193
2585
|
process.exit(1);
|
|
2194
2586
|
}
|
|
2195
|
-
success(`Renamed ${
|
|
2587
|
+
success(`Renamed ${pc11.bold(currentBranch)} → ${pc11.bold(newBranchName)}`);
|
|
2588
|
+
await unsetUpstream();
|
|
2196
2589
|
const syncSource2 = getSyncSource(config);
|
|
2197
|
-
info(`Syncing ${
|
|
2590
|
+
info(`Syncing ${pc11.bold(newBranchName)} with latest ${pc11.bold(baseBranch)}...`);
|
|
2198
2591
|
await fetchRemote(syncSource2.remote);
|
|
2199
|
-
|
|
2200
|
-
|
|
2592
|
+
let rebaseResult;
|
|
2593
|
+
if (staleUpstreamHash) {
|
|
2594
|
+
rebaseResult = await rebaseOnto(syncSource2.ref, staleUpstreamHash);
|
|
2595
|
+
} else {
|
|
2596
|
+
const savedStrategy = await determineRebaseStrategy(newBranchName, syncSource2.ref);
|
|
2597
|
+
rebaseResult = savedStrategy.strategy === "onto" && savedStrategy.ontoOldBase ? await rebaseOnto(syncSource2.ref, savedStrategy.ontoOldBase) : await rebase(syncSource2.ref);
|
|
2598
|
+
}
|
|
2201
2599
|
if (rebaseResult.exitCode !== 0) {
|
|
2202
2600
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
2203
|
-
info(` ${
|
|
2601
|
+
info(` ${pc11.bold("git rebase --continue")}`);
|
|
2204
2602
|
} else {
|
|
2205
|
-
success(`Rebased ${
|
|
2603
|
+
success(`Rebased ${pc11.bold(newBranchName)} onto ${pc11.bold(syncSource2.ref)}.`);
|
|
2206
2604
|
}
|
|
2207
|
-
info(`All your changes are preserved. Run ${
|
|
2605
|
+
info(`All your changes are preserved. Run ${pc11.bold("contrib submit")} when ready to create a new PR.`);
|
|
2208
2606
|
return;
|
|
2209
2607
|
}
|
|
2210
2608
|
warn("Discarding local changes...");
|
|
2211
2609
|
}
|
|
2212
2610
|
const syncSource = getSyncSource(config);
|
|
2213
|
-
info(`Switching to ${
|
|
2611
|
+
info(`Switching to ${pc11.bold(baseBranch)} and syncing...`);
|
|
2214
2612
|
await fetchRemote(syncSource.remote);
|
|
2215
2613
|
const coResult = await checkoutBranch2(baseBranch);
|
|
2216
2614
|
if (coResult.exitCode !== 0) {
|
|
@@ -2218,46 +2616,22 @@ var submit_default = defineCommand7({
|
|
|
2218
2616
|
process.exit(1);
|
|
2219
2617
|
}
|
|
2220
2618
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
2221
|
-
success(`Synced ${
|
|
2222
|
-
info(`Deleting stale branch ${
|
|
2619
|
+
success(`Synced ${pc11.bold(baseBranch)} with ${pc11.bold(syncSource.ref)}.`);
|
|
2620
|
+
info(`Deleting stale branch ${pc11.bold(currentBranch)}...`);
|
|
2223
2621
|
const delResult = await forceDeleteBranch(currentBranch);
|
|
2224
2622
|
if (delResult.exitCode === 0) {
|
|
2225
|
-
success(`Deleted ${
|
|
2623
|
+
success(`Deleted ${pc11.bold(currentBranch)}.`);
|
|
2226
2624
|
} else {
|
|
2227
2625
|
warn(`Could not delete branch: ${delResult.stderr.trim()}`);
|
|
2228
2626
|
}
|
|
2229
2627
|
console.log();
|
|
2230
|
-
info(`You're now on ${
|
|
2628
|
+
info(`You're now on ${pc11.bold(baseBranch)}. Run ${pc11.bold("contrib start")} to begin a new feature.`);
|
|
2231
2629
|
return;
|
|
2232
2630
|
}
|
|
2233
2631
|
}
|
|
2234
|
-
info(`Pushing ${pc10.bold(currentBranch)} to ${origin}...`);
|
|
2235
|
-
const pushResult = await pushSetUpstream(origin, currentBranch);
|
|
2236
|
-
if (pushResult.exitCode !== 0) {
|
|
2237
|
-
error(`Failed to push: ${pushResult.stderr}`);
|
|
2238
|
-
process.exit(1);
|
|
2239
|
-
}
|
|
2240
|
-
if (!ghInstalled || !ghAuthed) {
|
|
2241
|
-
const repoInfo = await getRepoInfoFromRemote(origin);
|
|
2242
|
-
if (repoInfo) {
|
|
2243
|
-
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/compare/${baseBranch}...${currentBranch}?expand=1`;
|
|
2244
|
-
console.log();
|
|
2245
|
-
info("Create your PR manually:");
|
|
2246
|
-
console.log(` ${pc10.cyan(prUrl)}`);
|
|
2247
|
-
} else {
|
|
2248
|
-
info("gh CLI not available. Create your PR manually on GitHub.");
|
|
2249
|
-
}
|
|
2250
|
-
return;
|
|
2251
|
-
}
|
|
2252
|
-
const existingPR = await getPRForBranch(currentBranch);
|
|
2253
|
-
if (existingPR) {
|
|
2254
|
-
success(`Pushed changes to existing PR #${existingPR.number}: ${pc10.bold(existingPR.title)}`);
|
|
2255
|
-
console.log(` ${pc10.cyan(existingPR.url)}`);
|
|
2256
|
-
return;
|
|
2257
|
-
}
|
|
2258
2632
|
let prTitle = null;
|
|
2259
2633
|
let prBody = null;
|
|
2260
|
-
|
|
2634
|
+
async function tryGenerateAI() {
|
|
2261
2635
|
const [copilotError, commits, diff] = await Promise.all([
|
|
2262
2636
|
checkCopilotAvailable(),
|
|
2263
2637
|
getLog(baseBranch, "HEAD"),
|
|
@@ -2271,10 +2645,10 @@ var submit_default = defineCommand7({
|
|
|
2271
2645
|
prBody = result.body;
|
|
2272
2646
|
spinner.success("PR description generated.");
|
|
2273
2647
|
console.log(`
|
|
2274
|
-
${
|
|
2648
|
+
${pc11.dim("AI title:")} ${pc11.bold(pc11.cyan(prTitle))}`);
|
|
2275
2649
|
console.log(`
|
|
2276
|
-
${
|
|
2277
|
-
console.log(
|
|
2650
|
+
${pc11.dim("AI body preview:")}`);
|
|
2651
|
+
console.log(pc11.dim(prBody.slice(0, 300) + (prBody.length > 300 ? "..." : "")));
|
|
2278
2652
|
} else {
|
|
2279
2653
|
spinner.fail("AI did not return a PR description.");
|
|
2280
2654
|
}
|
|
@@ -2282,77 +2656,119 @@ ${pc10.dim("AI body preview:")}`);
|
|
|
2282
2656
|
warn(`AI unavailable: ${copilotError}`);
|
|
2283
2657
|
}
|
|
2284
2658
|
}
|
|
2659
|
+
if (!args["no-ai"]) {
|
|
2660
|
+
await tryGenerateAI();
|
|
2661
|
+
}
|
|
2285
2662
|
const CANCEL = "Cancel";
|
|
2286
2663
|
const SQUASH_LOCAL = `Squash merge to ${baseBranch} locally (no PR)`;
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2664
|
+
const REGENERATE = "Regenerate AI description";
|
|
2665
|
+
let submitAction = "cancel";
|
|
2666
|
+
const isMaintainer = config.role === "maintainer";
|
|
2667
|
+
let actionResolved = false;
|
|
2668
|
+
while (!actionResolved) {
|
|
2669
|
+
if (prTitle && prBody) {
|
|
2670
|
+
const choices = ["Use AI description"];
|
|
2671
|
+
if (isMaintainer)
|
|
2672
|
+
choices.push(SQUASH_LOCAL);
|
|
2673
|
+
choices.push("Edit title", "Write manually", "Use gh --fill (auto-fill from commits)", REGENERATE, CANCEL);
|
|
2674
|
+
const action = await selectPrompt("What would you like to do with the PR description?", choices);
|
|
2675
|
+
if (action === CANCEL) {
|
|
2676
|
+
submitAction = "cancel";
|
|
2677
|
+
actionResolved = true;
|
|
2678
|
+
} else if (action === REGENERATE) {
|
|
2679
|
+
prTitle = null;
|
|
2680
|
+
prBody = null;
|
|
2681
|
+
await tryGenerateAI();
|
|
2682
|
+
} else if (action === SQUASH_LOCAL) {
|
|
2683
|
+
submitAction = "squash";
|
|
2684
|
+
actionResolved = true;
|
|
2685
|
+
} else if (action === "Use AI description") {
|
|
2686
|
+
submitAction = "create-pr";
|
|
2687
|
+
actionResolved = true;
|
|
2688
|
+
} else if (action === "Edit title") {
|
|
2689
|
+
prTitle = await inputPrompt("PR title", prTitle);
|
|
2690
|
+
submitAction = "create-pr";
|
|
2691
|
+
actionResolved = true;
|
|
2692
|
+
} else if (action === "Write manually") {
|
|
2693
|
+
prTitle = await inputPrompt("PR title");
|
|
2694
|
+
prBody = await inputPrompt("PR body (markdown)");
|
|
2695
|
+
submitAction = "create-pr";
|
|
2696
|
+
actionResolved = true;
|
|
2697
|
+
} else {
|
|
2698
|
+
submitAction = "fill";
|
|
2699
|
+
actionResolved = true;
|
|
2700
|
+
}
|
|
2315
2701
|
} else {
|
|
2316
|
-
const
|
|
2317
|
-
if (
|
|
2318
|
-
|
|
2319
|
-
|
|
2702
|
+
const choices = [];
|
|
2703
|
+
if (isMaintainer)
|
|
2704
|
+
choices.push(SQUASH_LOCAL);
|
|
2705
|
+
if (!args["no-ai"])
|
|
2706
|
+
choices.push(REGENERATE);
|
|
2707
|
+
choices.push("Write title & body manually", "Use gh --fill (auto-fill from commits)", CANCEL);
|
|
2708
|
+
const action = await selectPrompt("How would you like to create the PR?", choices);
|
|
2709
|
+
if (action === CANCEL) {
|
|
2710
|
+
submitAction = "cancel";
|
|
2711
|
+
actionResolved = true;
|
|
2712
|
+
} else if (action === REGENERATE) {
|
|
2713
|
+
await tryGenerateAI();
|
|
2714
|
+
} else if (action === SQUASH_LOCAL) {
|
|
2715
|
+
submitAction = "squash";
|
|
2716
|
+
actionResolved = true;
|
|
2717
|
+
} else if (action === "Write title & body manually") {
|
|
2718
|
+
prTitle = await inputPrompt("PR title");
|
|
2719
|
+
prBody = await inputPrompt("PR body (markdown)");
|
|
2720
|
+
submitAction = "create-pr";
|
|
2721
|
+
actionResolved = true;
|
|
2722
|
+
} else {
|
|
2723
|
+
submitAction = "fill";
|
|
2724
|
+
actionResolved = true;
|
|
2320
2725
|
}
|
|
2321
|
-
success(`✅ PR created: ${fillResult.stdout.trim()}`);
|
|
2322
|
-
return;
|
|
2323
|
-
}
|
|
2324
|
-
} else {
|
|
2325
|
-
const choices = [
|
|
2326
|
-
"Write title & body manually",
|
|
2327
|
-
"Use gh --fill (auto-fill from commits)"
|
|
2328
|
-
];
|
|
2329
|
-
if (config.role === "maintainer")
|
|
2330
|
-
choices.push(SQUASH_LOCAL);
|
|
2331
|
-
choices.push(CANCEL);
|
|
2332
|
-
const action = await selectPrompt("How would you like to create the PR?", choices);
|
|
2333
|
-
if (action === CANCEL) {
|
|
2334
|
-
warn("Submit cancelled.");
|
|
2335
|
-
return;
|
|
2336
|
-
}
|
|
2337
|
-
if (action === SQUASH_LOCAL) {
|
|
2338
|
-
await performSquashMerge(origin, baseBranch, currentBranch, {
|
|
2339
|
-
model: args.model,
|
|
2340
|
-
convention: config.commitConvention
|
|
2341
|
-
});
|
|
2342
|
-
return;
|
|
2343
2726
|
}
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2727
|
+
}
|
|
2728
|
+
if (submitAction === "cancel") {
|
|
2729
|
+
warn("Submit cancelled.");
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
if (submitAction === "squash") {
|
|
2733
|
+
await performSquashMerge(origin, baseBranch, currentBranch, {
|
|
2734
|
+
defaultMsg: prTitle ?? undefined,
|
|
2735
|
+
model: args.model,
|
|
2736
|
+
convention: config.commitConvention
|
|
2737
|
+
});
|
|
2738
|
+
return;
|
|
2739
|
+
}
|
|
2740
|
+
info(`Pushing ${pc11.bold(currentBranch)} to ${origin}...`);
|
|
2741
|
+
const pushResult = await pushSetUpstream(origin, currentBranch);
|
|
2742
|
+
if (pushResult.exitCode !== 0) {
|
|
2743
|
+
error(`Failed to push: ${pushResult.stderr}`);
|
|
2744
|
+
process.exit(1);
|
|
2745
|
+
}
|
|
2746
|
+
if (!ghInstalled || !ghAuthed) {
|
|
2747
|
+
const repoInfo = await getRepoInfoFromRemote(origin);
|
|
2748
|
+
if (repoInfo) {
|
|
2749
|
+
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/compare/${baseBranch}...${currentBranch}?expand=1`;
|
|
2750
|
+
console.log();
|
|
2751
|
+
info("Create your PR manually:");
|
|
2752
|
+
console.log(` ${pc11.cyan(prUrl)}`);
|
|
2347
2753
|
} else {
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2754
|
+
info("gh CLI not available. Create your PR manually on GitHub.");
|
|
2755
|
+
}
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
2758
|
+
const existingPR = await getPRForBranch(currentBranch);
|
|
2759
|
+
if (existingPR) {
|
|
2760
|
+
success(`Pushed changes to existing PR #${existingPR.number}: ${pc11.bold(existingPR.title)}`);
|
|
2761
|
+
console.log(` ${pc11.cyan(existingPR.url)}`);
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
if (submitAction === "fill") {
|
|
2765
|
+
const fillResult = await createPRFill(baseBranch, args.draft);
|
|
2766
|
+
if (fillResult.exitCode !== 0) {
|
|
2767
|
+
error(`Failed to create PR: ${fillResult.stderr}`);
|
|
2768
|
+
process.exit(1);
|
|
2355
2769
|
}
|
|
2770
|
+
success(`✅ PR created: ${fillResult.stdout.trim()}`);
|
|
2771
|
+
return;
|
|
2356
2772
|
}
|
|
2357
2773
|
if (!prTitle) {
|
|
2358
2774
|
error("No PR title provided.");
|
|
@@ -2373,9 +2789,9 @@ ${pc10.dim("AI body preview:")}`);
|
|
|
2373
2789
|
});
|
|
2374
2790
|
|
|
2375
2791
|
// src/commands/sync.ts
|
|
2376
|
-
import { defineCommand as
|
|
2377
|
-
import
|
|
2378
|
-
var sync_default =
|
|
2792
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
2793
|
+
import pc12 from "picocolors";
|
|
2794
|
+
var sync_default = defineCommand9({
|
|
2379
2795
|
meta: {
|
|
2380
2796
|
name: "sync",
|
|
2381
2797
|
description: "Sync your local branches with the remote"
|
|
@@ -2417,12 +2833,12 @@ var sync_default = defineCommand8({
|
|
|
2417
2833
|
}
|
|
2418
2834
|
const div = await getDivergence(baseBranch, syncSource.ref);
|
|
2419
2835
|
if (div.ahead > 0 || div.behind > 0) {
|
|
2420
|
-
info(`${
|
|
2836
|
+
info(`${pc12.bold(baseBranch)} is ${pc12.yellow(`${div.ahead} ahead`)} and ${pc12.red(`${div.behind} behind`)} ${syncSource.ref}`);
|
|
2421
2837
|
} else {
|
|
2422
|
-
info(`${
|
|
2838
|
+
info(`${pc12.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
|
|
2423
2839
|
}
|
|
2424
2840
|
if (!args.yes) {
|
|
2425
|
-
const ok = await confirmPrompt(`This will pull ${
|
|
2841
|
+
const ok = await confirmPrompt(`This will pull ${pc12.bold(syncSource.ref)} into local ${pc12.bold(baseBranch)}.`);
|
|
2426
2842
|
if (!ok)
|
|
2427
2843
|
process.exit(0);
|
|
2428
2844
|
}
|
|
@@ -2440,7 +2856,7 @@ var sync_default = defineCommand8({
|
|
|
2440
2856
|
if (hasDevBranch(workflow) && role === "maintainer") {
|
|
2441
2857
|
const mainDiv = await getDivergence(config.mainBranch, `${origin}/${config.mainBranch}`);
|
|
2442
2858
|
if (mainDiv.behind > 0) {
|
|
2443
|
-
info(`Also syncing ${
|
|
2859
|
+
info(`Also syncing ${pc12.bold(config.mainBranch)}...`);
|
|
2444
2860
|
const mainCoResult = await checkoutBranch2(config.mainBranch);
|
|
2445
2861
|
if (mainCoResult.exitCode === 0) {
|
|
2446
2862
|
const mainPullResult = await pullBranch(origin, config.mainBranch);
|
|
@@ -2456,9 +2872,9 @@ var sync_default = defineCommand8({
|
|
|
2456
2872
|
|
|
2457
2873
|
// src/commands/update.ts
|
|
2458
2874
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
2459
|
-
import { defineCommand as
|
|
2460
|
-
import
|
|
2461
|
-
var update_default =
|
|
2875
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
2876
|
+
import pc13 from "picocolors";
|
|
2877
|
+
var update_default = defineCommand10({
|
|
2462
2878
|
meta: {
|
|
2463
2879
|
name: "update",
|
|
2464
2880
|
description: "Rebase current branch onto the latest base branch"
|
|
@@ -2493,7 +2909,7 @@ var update_default = defineCommand9({
|
|
|
2493
2909
|
process.exit(1);
|
|
2494
2910
|
}
|
|
2495
2911
|
if (protectedBranches.includes(currentBranch)) {
|
|
2496
|
-
error(`Use \`contrib sync\` to update ${protectedBranches.map((b) =>
|
|
2912
|
+
error(`Use \`contrib sync\` to update ${protectedBranches.map((b) => pc13.bold(b)).join(" or ")} branches.`);
|
|
2497
2913
|
process.exit(1);
|
|
2498
2914
|
}
|
|
2499
2915
|
if (await hasUncommittedChanges()) {
|
|
@@ -2503,8 +2919,8 @@ var update_default = defineCommand9({
|
|
|
2503
2919
|
heading("\uD83D\uDD03 contrib update");
|
|
2504
2920
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
2505
2921
|
if (mergedPR) {
|
|
2506
|
-
warn(`PR #${mergedPR.number} (${
|
|
2507
|
-
info(`Link: ${
|
|
2922
|
+
warn(`PR #${mergedPR.number} (${pc13.bold(mergedPR.title)}) has already been merged.`);
|
|
2923
|
+
info(`Link: ${pc13.underline(mergedPR.url)}`);
|
|
2508
2924
|
const localWork = await hasLocalWork(syncSource.remote, currentBranch);
|
|
2509
2925
|
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
2510
2926
|
if (hasWork) {
|
|
@@ -2517,13 +2933,13 @@ var update_default = defineCommand9({
|
|
|
2517
2933
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
2518
2934
|
const DISCARD = "Discard all changes and clean up";
|
|
2519
2935
|
const CANCEL = "Cancel";
|
|
2520
|
-
const action = await selectPrompt(`${
|
|
2936
|
+
const action = await selectPrompt(`${pc13.bold(currentBranch)} is stale but has local work. What would you like to do?`, [SAVE_NEW_BRANCH, DISCARD, CANCEL]);
|
|
2521
2937
|
if (action === CANCEL) {
|
|
2522
2938
|
info("No changes made. You are still on your current branch.");
|
|
2523
2939
|
return;
|
|
2524
2940
|
}
|
|
2525
2941
|
if (action === SAVE_NEW_BRANCH) {
|
|
2526
|
-
info(
|
|
2942
|
+
info(pc13.dim("Tip: Describe what you're working on in plain English and we'll generate a branch name."));
|
|
2527
2943
|
const description = await inputPrompt("What are you working on?");
|
|
2528
2944
|
let newBranchName = description;
|
|
2529
2945
|
if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
|
|
@@ -2532,8 +2948,8 @@ var update_default = defineCommand9({
|
|
|
2532
2948
|
if (suggested) {
|
|
2533
2949
|
spinner.success("Branch name suggestion ready.");
|
|
2534
2950
|
console.log(`
|
|
2535
|
-
${
|
|
2536
|
-
const accepted = await confirmPrompt(`Use ${
|
|
2951
|
+
${pc13.dim("AI suggestion:")} ${pc13.bold(pc13.cyan(suggested))}`);
|
|
2952
|
+
const accepted = await confirmPrompt(`Use ${pc13.bold(suggested)} as your branch name?`);
|
|
2537
2953
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
2538
2954
|
} else {
|
|
2539
2955
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -2541,29 +2957,37 @@ var update_default = defineCommand9({
|
|
|
2541
2957
|
}
|
|
2542
2958
|
}
|
|
2543
2959
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
2544
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
2960
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc13.bold(newBranchName)}:`, config.branchPrefixes);
|
|
2545
2961
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
2546
2962
|
}
|
|
2547
2963
|
if (!isValidBranchName(newBranchName)) {
|
|
2548
2964
|
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
2549
2965
|
process.exit(1);
|
|
2550
2966
|
}
|
|
2967
|
+
const staleUpstream = await getUpstreamRef();
|
|
2968
|
+
const staleUpstreamHash = staleUpstream ? await getCommitHash(staleUpstream) : null;
|
|
2551
2969
|
const renameResult = await renameBranch(currentBranch, newBranchName);
|
|
2552
2970
|
if (renameResult.exitCode !== 0) {
|
|
2553
2971
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
2554
2972
|
process.exit(1);
|
|
2555
2973
|
}
|
|
2556
|
-
success(`Renamed ${
|
|
2974
|
+
success(`Renamed ${pc13.bold(currentBranch)} → ${pc13.bold(newBranchName)}`);
|
|
2975
|
+
await unsetUpstream();
|
|
2557
2976
|
await fetchRemote(syncSource.remote);
|
|
2558
|
-
|
|
2559
|
-
|
|
2977
|
+
let rebaseResult2;
|
|
2978
|
+
if (staleUpstreamHash) {
|
|
2979
|
+
rebaseResult2 = await rebaseOnto(syncSource.ref, staleUpstreamHash);
|
|
2980
|
+
} else {
|
|
2981
|
+
const savedStrategy = await determineRebaseStrategy(newBranchName, syncSource.ref);
|
|
2982
|
+
rebaseResult2 = savedStrategy.strategy === "onto" && savedStrategy.ontoOldBase ? await rebaseOnto(syncSource.ref, savedStrategy.ontoOldBase) : await rebase(syncSource.ref);
|
|
2983
|
+
}
|
|
2560
2984
|
if (rebaseResult2.exitCode !== 0) {
|
|
2561
2985
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
2562
|
-
info(` ${
|
|
2986
|
+
info(` ${pc13.bold("git rebase --continue")}`);
|
|
2563
2987
|
} else {
|
|
2564
|
-
success(`Rebased ${
|
|
2988
|
+
success(`Rebased ${pc13.bold(newBranchName)} onto ${pc13.bold(syncSource.ref)}.`);
|
|
2565
2989
|
}
|
|
2566
|
-
info(`All your changes are preserved. Run ${
|
|
2990
|
+
info(`All your changes are preserved. Run ${pc13.bold("contrib submit")} when ready to create a new PR.`);
|
|
2567
2991
|
return;
|
|
2568
2992
|
}
|
|
2569
2993
|
warn("Discarding local changes...");
|
|
@@ -2575,18 +2999,21 @@ var update_default = defineCommand9({
|
|
|
2575
2999
|
process.exit(1);
|
|
2576
3000
|
}
|
|
2577
3001
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
2578
|
-
success(`Synced ${
|
|
2579
|
-
info(`Deleting stale branch ${
|
|
3002
|
+
success(`Synced ${pc13.bold(baseBranch)} with ${pc13.bold(syncSource.ref)}.`);
|
|
3003
|
+
info(`Deleting stale branch ${pc13.bold(currentBranch)}...`);
|
|
2580
3004
|
await forceDeleteBranch(currentBranch);
|
|
2581
|
-
success(`Deleted ${
|
|
2582
|
-
info(`Run ${
|
|
3005
|
+
success(`Deleted ${pc13.bold(currentBranch)}.`);
|
|
3006
|
+
info(`Run ${pc13.bold("contrib start")} to begin a new feature branch.`);
|
|
2583
3007
|
return;
|
|
2584
3008
|
}
|
|
2585
|
-
info(`Updating ${
|
|
3009
|
+
info(`Updating ${pc13.bold(currentBranch)} with latest ${pc13.bold(baseBranch)}...`);
|
|
2586
3010
|
await fetchRemote(syncSource.remote);
|
|
2587
3011
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
2588
|
-
const
|
|
2589
|
-
|
|
3012
|
+
const rebaseStrategy = await determineRebaseStrategy(currentBranch, syncSource.ref);
|
|
3013
|
+
if (rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase) {
|
|
3014
|
+
info(pc13.dim(`Using --onto rebase (branch was based on a different ref)`));
|
|
3015
|
+
}
|
|
3016
|
+
const rebaseResult = rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase ? await rebaseOnto(syncSource.ref, rebaseStrategy.ontoOldBase) : await rebase(syncSource.ref);
|
|
2590
3017
|
if (rebaseResult.exitCode !== 0) {
|
|
2591
3018
|
warn("Rebase hit conflicts. Resolve them manually.");
|
|
2592
3019
|
console.log();
|
|
@@ -2613,10 +3040,10 @@ ${content.slice(0, 2000)}
|
|
|
2613
3040
|
if (suggestion) {
|
|
2614
3041
|
spinner.success("AI conflict guidance ready.");
|
|
2615
3042
|
console.log(`
|
|
2616
|
-
${
|
|
2617
|
-
console.log(
|
|
3043
|
+
${pc13.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
3044
|
+
console.log(pc13.dim("─".repeat(60)));
|
|
2618
3045
|
console.log(suggestion);
|
|
2619
|
-
console.log(
|
|
3046
|
+
console.log(pc13.dim("─".repeat(60)));
|
|
2620
3047
|
console.log();
|
|
2621
3048
|
} else {
|
|
2622
3049
|
spinner.fail("AI could not analyze the conflicts.");
|
|
@@ -2624,22 +3051,22 @@ ${pc12.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
|
2624
3051
|
}
|
|
2625
3052
|
}
|
|
2626
3053
|
}
|
|
2627
|
-
console.log(
|
|
3054
|
+
console.log(pc13.bold("To resolve:"));
|
|
2628
3055
|
console.log(` 1. Fix conflicts in the affected files`);
|
|
2629
|
-
console.log(` 2. ${
|
|
2630
|
-
console.log(` 3. ${
|
|
3056
|
+
console.log(` 2. ${pc13.cyan("git add <resolved-files>")}`);
|
|
3057
|
+
console.log(` 3. ${pc13.cyan("git rebase --continue")}`);
|
|
2631
3058
|
console.log();
|
|
2632
|
-
console.log(` Or abort: ${
|
|
3059
|
+
console.log(` Or abort: ${pc13.cyan("git rebase --abort")}`);
|
|
2633
3060
|
process.exit(1);
|
|
2634
3061
|
}
|
|
2635
|
-
success(`✅ ${
|
|
3062
|
+
success(`✅ ${pc13.bold(currentBranch)} has been rebased onto latest ${pc13.bold(baseBranch)}`);
|
|
2636
3063
|
}
|
|
2637
3064
|
});
|
|
2638
3065
|
|
|
2639
3066
|
// src/commands/validate.ts
|
|
2640
|
-
import { defineCommand as
|
|
2641
|
-
import
|
|
2642
|
-
var validate_default =
|
|
3067
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
3068
|
+
import pc14 from "picocolors";
|
|
3069
|
+
var validate_default = defineCommand11({
|
|
2643
3070
|
meta: {
|
|
2644
3071
|
name: "validate",
|
|
2645
3072
|
description: "Validate a commit message against the configured convention"
|
|
@@ -2669,7 +3096,7 @@ var validate_default = defineCommand10({
|
|
|
2669
3096
|
}
|
|
2670
3097
|
const errors = getValidationError(convention);
|
|
2671
3098
|
for (const line of errors) {
|
|
2672
|
-
console.error(
|
|
3099
|
+
console.error(pc14.red(` ✗ ${line}`));
|
|
2673
3100
|
}
|
|
2674
3101
|
process.exit(1);
|
|
2675
3102
|
}
|
|
@@ -2677,76 +3104,19 @@ var validate_default = defineCommand10({
|
|
|
2677
3104
|
|
|
2678
3105
|
// src/ui/banner.ts
|
|
2679
3106
|
import figlet from "figlet";
|
|
2680
|
-
import
|
|
2681
|
-
|
|
2682
|
-
var package_default = {
|
|
2683
|
-
name: "contribute-now",
|
|
2684
|
-
version: "0.2.0-dev.33be40f",
|
|
2685
|
-
description: "Git workflow CLI for squash-merge two-branch models. Keeps dev in sync with main after squash merges.",
|
|
2686
|
-
type: "module",
|
|
2687
|
-
bin: {
|
|
2688
|
-
contrib: "dist/index.js",
|
|
2689
|
-
contribute: "dist/index.js"
|
|
2690
|
-
},
|
|
2691
|
-
files: [
|
|
2692
|
-
"dist"
|
|
2693
|
-
],
|
|
2694
|
-
scripts: {
|
|
2695
|
-
build: "bun build src/index.ts --outfile dist/index.js --target node --packages external",
|
|
2696
|
-
cli: "bun run src/index.ts --",
|
|
2697
|
-
dev: "bun src/index.ts",
|
|
2698
|
-
test: "bun test",
|
|
2699
|
-
lint: "biome check .",
|
|
2700
|
-
"lint:fix": "biome check --write .",
|
|
2701
|
-
format: "biome format --write .",
|
|
2702
|
-
"www:dev": "bun run --cwd www dev",
|
|
2703
|
-
"www:build": "bun run --cwd www build",
|
|
2704
|
-
"www:preview": "bun run --cwd www preview"
|
|
2705
|
-
},
|
|
2706
|
-
engines: {
|
|
2707
|
-
node: ">=18",
|
|
2708
|
-
bun: ">=1.0"
|
|
2709
|
-
},
|
|
2710
|
-
keywords: [
|
|
2711
|
-
"git",
|
|
2712
|
-
"workflow",
|
|
2713
|
-
"squash-merge",
|
|
2714
|
-
"sync",
|
|
2715
|
-
"cli",
|
|
2716
|
-
"contribute",
|
|
2717
|
-
"fork",
|
|
2718
|
-
"dev-branch",
|
|
2719
|
-
"clean-commit"
|
|
2720
|
-
],
|
|
2721
|
-
author: "Waren Gonzaga",
|
|
2722
|
-
license: "GPL-3.0",
|
|
2723
|
-
repository: {
|
|
2724
|
-
type: "git",
|
|
2725
|
-
url: "git+https://github.com/warengonzaga/contribute-now.git"
|
|
2726
|
-
},
|
|
2727
|
-
dependencies: {
|
|
2728
|
-
"@clack/prompts": "^1.0.1",
|
|
2729
|
-
"@github/copilot-sdk": "^0.1.25",
|
|
2730
|
-
"@wgtechlabs/log-engine": "^2.3.1",
|
|
2731
|
-
citty: "^0.1.6",
|
|
2732
|
-
figlet: "^1.10.0",
|
|
2733
|
-
picocolors: "^1.1.1"
|
|
2734
|
-
},
|
|
2735
|
-
devDependencies: {
|
|
2736
|
-
"@biomejs/biome": "^2.4.4",
|
|
2737
|
-
"@types/bun": "latest",
|
|
2738
|
-
"@types/figlet": "^1.7.0",
|
|
2739
|
-
typescript: "^5.7.0"
|
|
2740
|
-
}
|
|
2741
|
-
};
|
|
2742
|
-
|
|
2743
|
-
// src/ui/banner.ts
|
|
2744
|
-
var LOGO;
|
|
3107
|
+
import pc15 from "picocolors";
|
|
3108
|
+
var LOGO_BIG;
|
|
2745
3109
|
try {
|
|
2746
|
-
|
|
3110
|
+
LOGO_BIG = figlet.textSync(`Contribute
|
|
2747
3111
|
Now`, { font: "ANSI Shadow" });
|
|
2748
3112
|
} catch {
|
|
2749
|
-
|
|
3113
|
+
LOGO_BIG = "Contribute Now";
|
|
3114
|
+
}
|
|
3115
|
+
var LOGO_SMALL;
|
|
3116
|
+
try {
|
|
3117
|
+
LOGO_SMALL = figlet.textSync("Contribute Now", { font: "Slant" });
|
|
3118
|
+
} catch {
|
|
3119
|
+
LOGO_SMALL = "Contribute Now";
|
|
2750
3120
|
}
|
|
2751
3121
|
function getVersion() {
|
|
2752
3122
|
return package_default.version ?? "unknown";
|
|
@@ -2754,23 +3124,30 @@ function getVersion() {
|
|
|
2754
3124
|
function getAuthor() {
|
|
2755
3125
|
return typeof package_default.author === "string" ? package_default.author : "unknown";
|
|
2756
3126
|
}
|
|
2757
|
-
function showBanner(
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
3127
|
+
function showBanner(variant = "small") {
|
|
3128
|
+
const logo = variant === "big" ? LOGO_BIG : LOGO_SMALL;
|
|
3129
|
+
console.log(pc15.cyan(`
|
|
3130
|
+
${logo}`));
|
|
3131
|
+
console.log(` ${pc15.dim(`v${getVersion()}`)} ${pc15.dim("—")} ${pc15.dim(`Built by ${getAuthor()}`)}`);
|
|
3132
|
+
if (variant === "big") {
|
|
2762
3133
|
console.log();
|
|
2763
|
-
console.log(` ${
|
|
2764
|
-
console.log(` ${
|
|
2765
|
-
console.log(` ${
|
|
3134
|
+
console.log(` ${pc15.yellow("Star")} ${pc15.cyan("https://github.com/warengonzaga/contribute-now")}`);
|
|
3135
|
+
console.log(` ${pc15.green("Contribute")} ${pc15.cyan("https://github.com/warengonzaga/contribute-now/blob/main/CONTRIBUTING.md")}`);
|
|
3136
|
+
console.log(` ${pc15.magenta("Sponsor")} ${pc15.cyan("https://warengonzaga.com/sponsor")}`);
|
|
2766
3137
|
}
|
|
2767
3138
|
console.log();
|
|
2768
3139
|
}
|
|
2769
3140
|
|
|
2770
3141
|
// src/index.ts
|
|
2771
|
-
var
|
|
2772
|
-
|
|
2773
|
-
|
|
3142
|
+
var isVersion = process.argv.includes("--version") || process.argv.includes("-v");
|
|
3143
|
+
if (!isVersion) {
|
|
3144
|
+
const subCommands = ["setup", "sync", "start", "commit", "update", "submit", "clean", "status", "hook", "validate", "doctor"];
|
|
3145
|
+
const isHelp = process.argv.includes("--help") || process.argv.includes("-h");
|
|
3146
|
+
const hasSubCommand = subCommands.some((cmd) => process.argv.includes(cmd));
|
|
3147
|
+
const useBigBanner = isHelp || !hasSubCommand;
|
|
3148
|
+
showBanner(useBigBanner ? "big" : "small");
|
|
3149
|
+
}
|
|
3150
|
+
var main = defineCommand12({
|
|
2774
3151
|
meta: {
|
|
2775
3152
|
name: "contrib",
|
|
2776
3153
|
version: getVersion(),
|
|
@@ -2793,7 +3170,8 @@ var main = defineCommand11({
|
|
|
2793
3170
|
clean: clean_default,
|
|
2794
3171
|
status: status_default,
|
|
2795
3172
|
hook: hook_default,
|
|
2796
|
-
validate: validate_default
|
|
3173
|
+
validate: validate_default,
|
|
3174
|
+
doctor: doctor_default
|
|
2797
3175
|
},
|
|
2798
3176
|
run({ args }) {
|
|
2799
3177
|
if (args.version) {
|