contribute-now 0.6.2-dev.12fb962 → 0.6.2-dev.1320af4
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 +10 -3
- package/dist/index.js +1764 -914
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2355,8 +2355,8 @@ var require_semaphore = __commonJS((exports) => {
|
|
|
2355
2355
|
this._waiting = [];
|
|
2356
2356
|
}
|
|
2357
2357
|
lock(thunk) {
|
|
2358
|
-
return new Promise((
|
|
2359
|
-
this._waiting.push({ thunk, resolve:
|
|
2358
|
+
return new Promise((resolve3, reject) => {
|
|
2359
|
+
this._waiting.push({ thunk, resolve: resolve3, reject });
|
|
2360
2360
|
this.runNext();
|
|
2361
2361
|
});
|
|
2362
2362
|
}
|
|
@@ -3847,9 +3847,9 @@ ${JSON.stringify(message, null, 4)}`);
|
|
|
3847
3847
|
if (typeof cancellationStrategy.sender.enableCancellation === "function") {
|
|
3848
3848
|
cancellationStrategy.sender.enableCancellation(requestMessage);
|
|
3849
3849
|
}
|
|
3850
|
-
return new Promise(async (
|
|
3850
|
+
return new Promise(async (resolve3, reject) => {
|
|
3851
3851
|
const resolveWithCleanup = (r3) => {
|
|
3852
|
-
|
|
3852
|
+
resolve3(r3);
|
|
3853
3853
|
cancellationStrategy.sender.cleanup(id);
|
|
3854
3854
|
disposable?.dispose();
|
|
3855
3855
|
};
|
|
@@ -4258,10 +4258,10 @@ var require_ril = __commonJS((exports) => {
|
|
|
4258
4258
|
return api_1.Disposable.create(() => this.stream.off("end", listener));
|
|
4259
4259
|
}
|
|
4260
4260
|
write(data, encoding) {
|
|
4261
|
-
return new Promise((
|
|
4261
|
+
return new Promise((resolve3, reject) => {
|
|
4262
4262
|
const callback = (error2) => {
|
|
4263
4263
|
if (error2 === undefined || error2 === null) {
|
|
4264
|
-
|
|
4264
|
+
resolve3();
|
|
4265
4265
|
} else {
|
|
4266
4266
|
reject(error2);
|
|
4267
4267
|
}
|
|
@@ -4520,10 +4520,10 @@ var require_main = __commonJS((exports) => {
|
|
|
4520
4520
|
exports.generateRandomPipeName = generateRandomPipeName;
|
|
4521
4521
|
function createClientPipeTransport(pipeName, encoding = "utf-8") {
|
|
4522
4522
|
let connectResolve;
|
|
4523
|
-
const connected = new Promise((
|
|
4524
|
-
connectResolve =
|
|
4523
|
+
const connected = new Promise((resolve3, _reject) => {
|
|
4524
|
+
connectResolve = resolve3;
|
|
4525
4525
|
});
|
|
4526
|
-
return new Promise((
|
|
4526
|
+
return new Promise((resolve3, reject) => {
|
|
4527
4527
|
let server = (0, net_1.createServer)((socket) => {
|
|
4528
4528
|
server.close();
|
|
4529
4529
|
connectResolve([
|
|
@@ -4534,7 +4534,7 @@ var require_main = __commonJS((exports) => {
|
|
|
4534
4534
|
server.on("error", reject);
|
|
4535
4535
|
server.listen(pipeName, () => {
|
|
4536
4536
|
server.removeListener("error", reject);
|
|
4537
|
-
|
|
4537
|
+
resolve3({
|
|
4538
4538
|
onConnected: () => {
|
|
4539
4539
|
return connected;
|
|
4540
4540
|
}
|
|
@@ -4553,10 +4553,10 @@ var require_main = __commonJS((exports) => {
|
|
|
4553
4553
|
exports.createServerPipeTransport = createServerPipeTransport;
|
|
4554
4554
|
function createClientSocketTransport(port, encoding = "utf-8") {
|
|
4555
4555
|
let connectResolve;
|
|
4556
|
-
const connected = new Promise((
|
|
4557
|
-
connectResolve =
|
|
4556
|
+
const connected = new Promise((resolve3, _reject) => {
|
|
4557
|
+
connectResolve = resolve3;
|
|
4558
4558
|
});
|
|
4559
|
-
return new Promise((
|
|
4559
|
+
return new Promise((resolve3, reject) => {
|
|
4560
4560
|
const server = (0, net_1.createServer)((socket) => {
|
|
4561
4561
|
server.close();
|
|
4562
4562
|
connectResolve([
|
|
@@ -4567,7 +4567,7 @@ var require_main = __commonJS((exports) => {
|
|
|
4567
4567
|
server.on("error", reject);
|
|
4568
4568
|
server.listen(port, "127.0.0.1", () => {
|
|
4569
4569
|
server.removeListener("error", reject);
|
|
4570
|
-
|
|
4570
|
+
resolve3({
|
|
4571
4571
|
onConnected: () => {
|
|
4572
4572
|
return connected;
|
|
4573
4573
|
}
|
|
@@ -4691,8 +4691,8 @@ class CopilotSession {
|
|
|
4691
4691
|
const effectiveTimeout = timeout ?? 60000;
|
|
4692
4692
|
let resolveIdle;
|
|
4693
4693
|
let rejectWithError;
|
|
4694
|
-
const idlePromise = new Promise((
|
|
4695
|
-
resolveIdle =
|
|
4694
|
+
const idlePromise = new Promise((resolve3, reject) => {
|
|
4695
|
+
resolveIdle = resolve3;
|
|
4696
4696
|
rejectWithError = reject;
|
|
4697
4697
|
});
|
|
4698
4698
|
let lastAssistantMessage;
|
|
@@ -4857,7 +4857,7 @@ var init_session = __esm(() => {
|
|
|
4857
4857
|
import { spawn } from "node:child_process";
|
|
4858
4858
|
import { existsSync as existsSync3 } from "node:fs";
|
|
4859
4859
|
import { Socket } from "node:net";
|
|
4860
|
-
import { dirname as
|
|
4860
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
4861
4861
|
import { fileURLToPath } from "node:url";
|
|
4862
4862
|
function isZodSchema(value) {
|
|
4863
4863
|
return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
|
|
@@ -4879,7 +4879,7 @@ function getNodeExecPath() {
|
|
|
4879
4879
|
function getBundledCliPath() {
|
|
4880
4880
|
const sdkUrl = import.meta.resolve("@github/copilot/sdk");
|
|
4881
4881
|
const sdkPath = fileURLToPath(sdkUrl);
|
|
4882
|
-
return join3(
|
|
4882
|
+
return join3(dirname3(dirname3(sdkPath)), "index.js");
|
|
4883
4883
|
}
|
|
4884
4884
|
|
|
4885
4885
|
class CopilotClient {
|
|
@@ -4984,7 +4984,7 @@ class CopilotClient {
|
|
|
4984
4984
|
lastError = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
4985
4985
|
if (attempt < 3) {
|
|
4986
4986
|
const delay = 100 * Math.pow(2, attempt - 1);
|
|
4987
|
-
await new Promise((
|
|
4987
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
4988
4988
|
}
|
|
4989
4989
|
}
|
|
4990
4990
|
}
|
|
@@ -5184,8 +5184,8 @@ class CopilotClient {
|
|
|
5184
5184
|
}
|
|
5185
5185
|
await this.modelsCacheLock;
|
|
5186
5186
|
let resolveLock;
|
|
5187
|
-
this.modelsCacheLock = new Promise((
|
|
5188
|
-
resolveLock =
|
|
5187
|
+
this.modelsCacheLock = new Promise((resolve3) => {
|
|
5188
|
+
resolveLock = resolve3;
|
|
5189
5189
|
});
|
|
5190
5190
|
try {
|
|
5191
5191
|
if (this.modelsCache !== null) {
|
|
@@ -5290,7 +5290,7 @@ class CopilotClient {
|
|
|
5290
5290
|
};
|
|
5291
5291
|
}
|
|
5292
5292
|
async startCLIServer() {
|
|
5293
|
-
return new Promise((
|
|
5293
|
+
return new Promise((resolve3, reject) => {
|
|
5294
5294
|
this.stderrBuffer = "";
|
|
5295
5295
|
const args = [
|
|
5296
5296
|
...this.options.cliArgs,
|
|
@@ -5339,7 +5339,7 @@ class CopilotClient {
|
|
|
5339
5339
|
let resolved = false;
|
|
5340
5340
|
if (this.options.useStdio) {
|
|
5341
5341
|
resolved = true;
|
|
5342
|
-
|
|
5342
|
+
resolve3();
|
|
5343
5343
|
} else {
|
|
5344
5344
|
this.cliProcess.stdout?.on("data", (data) => {
|
|
5345
5345
|
stdout2 += data.toString();
|
|
@@ -5347,7 +5347,7 @@ class CopilotClient {
|
|
|
5347
5347
|
if (match && !resolved) {
|
|
5348
5348
|
this.actualPort = parseInt(match[1], 10);
|
|
5349
5349
|
resolved = true;
|
|
5350
|
-
|
|
5350
|
+
resolve3();
|
|
5351
5351
|
}
|
|
5352
5352
|
});
|
|
5353
5353
|
}
|
|
@@ -5434,13 +5434,13 @@ stderr: ${stderrOutput}`));
|
|
|
5434
5434
|
if (!this.actualPort) {
|
|
5435
5435
|
throw new Error("Server port not available");
|
|
5436
5436
|
}
|
|
5437
|
-
return new Promise((
|
|
5437
|
+
return new Promise((resolve3, reject) => {
|
|
5438
5438
|
this.socket = new Socket;
|
|
5439
5439
|
this.socket.connect(this.actualPort, this.actualHost, () => {
|
|
5440
5440
|
this.connection = import_node.createMessageConnection(new import_node.StreamMessageReader(this.socket), new import_node.StreamMessageWriter(this.socket));
|
|
5441
5441
|
this.attachConnectionHandlers();
|
|
5442
5442
|
this.connection.listen();
|
|
5443
|
-
|
|
5443
|
+
resolve3();
|
|
5444
5444
|
});
|
|
5445
5445
|
this.socket.on("error", (error2) => {
|
|
5446
5446
|
reject(new Error(`Failed to connect to CLI server: ${error2.message}`));
|
|
@@ -7047,22 +7047,56 @@ async function runMain(cmd, opts = {}) {
|
|
|
7047
7047
|
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
7048
7048
|
|
|
7049
7049
|
// src/utils/config.ts
|
|
7050
|
-
import {
|
|
7051
|
-
|
|
7050
|
+
import {
|
|
7051
|
+
appendFileSync,
|
|
7052
|
+
existsSync,
|
|
7053
|
+
mkdirSync,
|
|
7054
|
+
readFileSync,
|
|
7055
|
+
statSync,
|
|
7056
|
+
writeFileSync
|
|
7057
|
+
} from "node:fs";
|
|
7058
|
+
import { dirname, join, resolve } from "node:path";
|
|
7052
7059
|
var CONFIG_FILENAME = ".contributerc.json";
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7060
|
+
var LOCAL_CONFIG_DIRNAME = "contribute-now";
|
|
7061
|
+
var LOCAL_CONFIG_FILENAME = "config.json";
|
|
7062
|
+
function findRepoRoot(cwd = process.cwd()) {
|
|
7063
|
+
let current = resolve(cwd);
|
|
7064
|
+
while (true) {
|
|
7065
|
+
if (existsSync(join(current, ".git"))) {
|
|
7066
|
+
return current;
|
|
7067
|
+
}
|
|
7068
|
+
const parent = dirname(current);
|
|
7069
|
+
if (parent === current) {
|
|
7070
|
+
return null;
|
|
7071
|
+
}
|
|
7072
|
+
current = parent;
|
|
7073
|
+
}
|
|
7058
7074
|
}
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7075
|
+
function resolveGitDir(cwd = process.cwd()) {
|
|
7076
|
+
const repoRoot = findRepoRoot(cwd);
|
|
7077
|
+
if (!repoRoot) {
|
|
7078
|
+
return null;
|
|
7079
|
+
}
|
|
7080
|
+
const dotGitPath = join(repoRoot, ".git");
|
|
7081
|
+
try {
|
|
7082
|
+
const stat = statSync(dotGitPath);
|
|
7083
|
+
if (stat.isDirectory()) {
|
|
7084
|
+
return dotGitPath;
|
|
7085
|
+
}
|
|
7086
|
+
if (!stat.isFile()) {
|
|
7087
|
+
return null;
|
|
7088
|
+
}
|
|
7089
|
+
const content = readFileSync(dotGitPath, "utf-8").trim();
|
|
7090
|
+
const match = /^gitdir:\s*(.+)$/i.exec(content);
|
|
7091
|
+
if (!match) {
|
|
7092
|
+
return null;
|
|
7093
|
+
}
|
|
7094
|
+
return resolve(repoRoot, match[1].trim());
|
|
7095
|
+
} catch {
|
|
7065
7096
|
return null;
|
|
7097
|
+
}
|
|
7098
|
+
}
|
|
7099
|
+
function parseConfigFile(path) {
|
|
7066
7100
|
try {
|
|
7067
7101
|
const raw = readFileSync(path, "utf-8");
|
|
7068
7102
|
const parsed = JSON.parse(raw);
|
|
@@ -7070,44 +7104,112 @@ function readConfig(cwd = process.cwd()) {
|
|
|
7070
7104
|
return null;
|
|
7071
7105
|
}
|
|
7072
7106
|
if (!VALID_WORKFLOWS.includes(parsed.workflow)) {
|
|
7073
|
-
console.error(`Invalid workflow "${parsed.workflow}" in .
|
|
7107
|
+
console.error(`Invalid workflow "${parsed.workflow}" in ${path.endsWith(CONFIG_FILENAME) ? CONFIG_FILENAME : LOCAL_CONFIG_FILENAME}. Valid: ${VALID_WORKFLOWS.join(", ")}`);
|
|
7074
7108
|
return null;
|
|
7075
7109
|
}
|
|
7076
7110
|
if (!VALID_ROLES.includes(parsed.role)) {
|
|
7077
|
-
console.error(`Invalid role "${parsed.role}" in .
|
|
7111
|
+
console.error(`Invalid role "${parsed.role}" in ${path.endsWith(CONFIG_FILENAME) ? CONFIG_FILENAME : LOCAL_CONFIG_FILENAME}. Valid: ${VALID_ROLES.join(", ")}`);
|
|
7078
7112
|
return null;
|
|
7079
7113
|
}
|
|
7080
7114
|
if (!VALID_CONVENTIONS.includes(parsed.commitConvention)) {
|
|
7081
|
-
console.error(`Invalid commitConvention "${parsed.commitConvention}" in .
|
|
7115
|
+
console.error(`Invalid commitConvention "${parsed.commitConvention}" in ${path.endsWith(CONFIG_FILENAME) ? CONFIG_FILENAME : LOCAL_CONFIG_FILENAME}. Valid: ${VALID_CONVENTIONS.join(", ")}`);
|
|
7082
7116
|
return null;
|
|
7083
7117
|
}
|
|
7084
7118
|
if (!parsed.mainBranch.trim()) {
|
|
7085
|
-
console.error(
|
|
7119
|
+
console.error(`Invalid config (${path}): mainBranch must not be empty.`);
|
|
7086
7120
|
return null;
|
|
7087
7121
|
}
|
|
7088
7122
|
if (!parsed.origin.trim()) {
|
|
7089
|
-
console.error(
|
|
7123
|
+
console.error(`Invalid config (${path}): origin must not be empty.`);
|
|
7090
7124
|
return null;
|
|
7091
7125
|
}
|
|
7092
7126
|
if (parsed.role === "contributor" && !parsed.upstream.trim()) {
|
|
7093
|
-
console.error(
|
|
7127
|
+
console.error(`Invalid config (${path}): upstream must not be empty for contributors.`);
|
|
7094
7128
|
return null;
|
|
7095
7129
|
}
|
|
7096
7130
|
if (parsed.branchPrefixes.length === 0) {
|
|
7097
|
-
console.error(
|
|
7131
|
+
console.error(`Invalid config (${path}): branchPrefixes must not be empty.`);
|
|
7098
7132
|
return null;
|
|
7099
7133
|
}
|
|
7100
7134
|
if (!parsed.branchPrefixes.every((p) => typeof p === "string" && p.trim().length > 0)) {
|
|
7101
|
-
console.error(
|
|
7135
|
+
console.error(`Invalid config (${path}): all branchPrefixes must be non-empty strings.`);
|
|
7102
7136
|
return null;
|
|
7103
7137
|
}
|
|
7104
|
-
return
|
|
7138
|
+
return {
|
|
7139
|
+
...parsed,
|
|
7140
|
+
aiEnabled: parsed.aiEnabled !== false,
|
|
7141
|
+
showTips: parsed.showTips !== false,
|
|
7142
|
+
guideRotation: parsed.guideRotation && typeof parsed.guideRotation === "object" ? parsed.guideRotation : {}
|
|
7143
|
+
};
|
|
7105
7144
|
} catch {
|
|
7106
7145
|
return null;
|
|
7107
7146
|
}
|
|
7108
7147
|
}
|
|
7148
|
+
function getConfigPath(cwd = process.cwd()) {
|
|
7149
|
+
const legacyPath = getLegacyConfigPath(cwd);
|
|
7150
|
+
if (existsSync(legacyPath)) {
|
|
7151
|
+
return legacyPath;
|
|
7152
|
+
}
|
|
7153
|
+
return getLocalConfigPath(cwd) ?? legacyPath;
|
|
7154
|
+
}
|
|
7155
|
+
function getLegacyConfigPath(cwd = process.cwd()) {
|
|
7156
|
+
return join(findRepoRoot(cwd) ?? cwd, CONFIG_FILENAME);
|
|
7157
|
+
}
|
|
7158
|
+
function getLocalConfigPath(cwd = process.cwd()) {
|
|
7159
|
+
const gitDir = resolveGitDir(cwd);
|
|
7160
|
+
if (!gitDir) {
|
|
7161
|
+
return null;
|
|
7162
|
+
}
|
|
7163
|
+
return join(gitDir, LOCAL_CONFIG_DIRNAME, LOCAL_CONFIG_FILENAME);
|
|
7164
|
+
}
|
|
7165
|
+
function getConfigSource(cwd = process.cwd()) {
|
|
7166
|
+
if (existsSync(getLegacyConfigPath(cwd))) {
|
|
7167
|
+
return "legacy";
|
|
7168
|
+
}
|
|
7169
|
+
const localPath = getLocalConfigPath(cwd);
|
|
7170
|
+
if (localPath && existsSync(localPath)) {
|
|
7171
|
+
return "local";
|
|
7172
|
+
}
|
|
7173
|
+
return null;
|
|
7174
|
+
}
|
|
7175
|
+
function hasLegacyConfig(cwd = process.cwd()) {
|
|
7176
|
+
return existsSync(getLegacyConfigPath(cwd));
|
|
7177
|
+
}
|
|
7178
|
+
function hasLocalConfig(cwd = process.cwd()) {
|
|
7179
|
+
const localPath = getLocalConfigPath(cwd);
|
|
7180
|
+
return !!localPath && existsSync(localPath);
|
|
7181
|
+
}
|
|
7182
|
+
function getConfigLocationLabel(cwd = process.cwd()) {
|
|
7183
|
+
const source = getConfigSource(cwd);
|
|
7184
|
+
if (source === "legacy") {
|
|
7185
|
+
return CONFIG_FILENAME;
|
|
7186
|
+
}
|
|
7187
|
+
return getLocalConfigPath(cwd) ? `.git/${LOCAL_CONFIG_DIRNAME}/${LOCAL_CONFIG_FILENAME}` : CONFIG_FILENAME;
|
|
7188
|
+
}
|
|
7189
|
+
function configExists(cwd = process.cwd()) {
|
|
7190
|
+
return getConfigSource(cwd) !== null;
|
|
7191
|
+
}
|
|
7192
|
+
var VALID_WORKFLOWS = ["clean-flow", "github-flow", "git-flow"];
|
|
7193
|
+
var VALID_ROLES = ["maintainer", "contributor"];
|
|
7194
|
+
var VALID_CONVENTIONS = ["conventional", "clean-commit", "none"];
|
|
7195
|
+
function isAIEnabled(config, cliNoAI = false) {
|
|
7196
|
+
return config.aiEnabled !== false && !cliNoAI;
|
|
7197
|
+
}
|
|
7198
|
+
function shouldShowTips(config) {
|
|
7199
|
+
return config?.showTips !== false;
|
|
7200
|
+
}
|
|
7201
|
+
function readConfig(cwd = process.cwd()) {
|
|
7202
|
+
const source = getConfigSource(cwd);
|
|
7203
|
+
if (!source)
|
|
7204
|
+
return null;
|
|
7205
|
+
const path = source === "local" ? getLocalConfigPath(cwd) : getLegacyConfigPath(cwd);
|
|
7206
|
+
if (!path)
|
|
7207
|
+
return null;
|
|
7208
|
+
return parseConfigFile(path);
|
|
7209
|
+
}
|
|
7109
7210
|
function writeConfig(config, cwd = process.cwd()) {
|
|
7110
7211
|
const path = getConfigPath(cwd);
|
|
7212
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
7111
7213
|
writeFileSync(path, `${JSON.stringify(config, null, 2)}
|
|
7112
7214
|
`, "utf-8");
|
|
7113
7215
|
}
|
|
@@ -7123,23 +7225,6 @@ function isGitignored(cwd = process.cwd()) {
|
|
|
7123
7225
|
return false;
|
|
7124
7226
|
}
|
|
7125
7227
|
}
|
|
7126
|
-
function ensureGitignored(cwd = process.cwd()) {
|
|
7127
|
-
if (isGitignored(cwd))
|
|
7128
|
-
return false;
|
|
7129
|
-
const gitignorePath = join(cwd, ".gitignore");
|
|
7130
|
-
const line = `${CONFIG_FILENAME}
|
|
7131
|
-
`;
|
|
7132
|
-
if (!existsSync(gitignorePath)) {
|
|
7133
|
-
writeFileSync(gitignorePath, line, "utf-8");
|
|
7134
|
-
return true;
|
|
7135
|
-
}
|
|
7136
|
-
const content = readFileSync(gitignorePath, "utf-8");
|
|
7137
|
-
const needsLeadingNewline = content.length > 0 && !content.endsWith(`
|
|
7138
|
-
`);
|
|
7139
|
-
appendFileSync(gitignorePath, `${needsLeadingNewline ? `
|
|
7140
|
-
` : ""}${line}`, "utf-8");
|
|
7141
|
-
return true;
|
|
7142
|
-
}
|
|
7143
7228
|
function getDefaultConfig() {
|
|
7144
7229
|
return {
|
|
7145
7230
|
workflow: "clean-flow",
|
|
@@ -7149,7 +7234,10 @@ function getDefaultConfig() {
|
|
|
7149
7234
|
upstream: "upstream",
|
|
7150
7235
|
origin: "origin",
|
|
7151
7236
|
branchPrefixes: ["feature", "fix", "docs", "chore", "test", "refactor"],
|
|
7152
|
-
commitConvention: "clean-commit"
|
|
7237
|
+
commitConvention: "clean-commit",
|
|
7238
|
+
aiEnabled: true,
|
|
7239
|
+
showTips: true,
|
|
7240
|
+
guideRotation: {}
|
|
7153
7241
|
};
|
|
7154
7242
|
}
|
|
7155
7243
|
|
|
@@ -7158,9 +7246,9 @@ import { execFile as execFileCb } from "node:child_process";
|
|
|
7158
7246
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
7159
7247
|
import { join as join2 } from "node:path";
|
|
7160
7248
|
function run(args) {
|
|
7161
|
-
return new Promise((
|
|
7249
|
+
return new Promise((resolve2) => {
|
|
7162
7250
|
execFileCb("git", args, (error, stdout2, stderr) => {
|
|
7163
|
-
|
|
7251
|
+
resolve2({
|
|
7164
7252
|
exitCode: error ? error.code === "ENOENT" ? 127 : error.status ?? 1 : 0,
|
|
7165
7253
|
stdout: stdout2 ?? "",
|
|
7166
7254
|
stderr: stderr ?? ""
|
|
@@ -7625,41 +7713,6 @@ async function getLocalCommitsEntries(options) {
|
|
|
7625
7713
|
return { hash: hash.trim(), subject: subject.trim(), refs: refs.trim() };
|
|
7626
7714
|
});
|
|
7627
7715
|
}
|
|
7628
|
-
async function getRemoteOnlyCommitsGraph(options) {
|
|
7629
|
-
const count = options?.count ?? 20;
|
|
7630
|
-
const upstream = options?.upstream;
|
|
7631
|
-
if (!upstream)
|
|
7632
|
-
return [];
|
|
7633
|
-
const args = [
|
|
7634
|
-
"log",
|
|
7635
|
-
"--oneline",
|
|
7636
|
-
"--graph",
|
|
7637
|
-
"--decorate",
|
|
7638
|
-
`--max-count=${count}`,
|
|
7639
|
-
"--color=never",
|
|
7640
|
-
`HEAD..${upstream}`
|
|
7641
|
-
];
|
|
7642
|
-
const { exitCode, stdout: stdout2 } = await run(args);
|
|
7643
|
-
if (exitCode !== 0)
|
|
7644
|
-
return [];
|
|
7645
|
-
return stdout2.trimEnd().split(`
|
|
7646
|
-
`).filter(Boolean);
|
|
7647
|
-
}
|
|
7648
|
-
async function getRemoteOnlyCommitsEntries(options) {
|
|
7649
|
-
const count = options?.count ?? 20;
|
|
7650
|
-
const upstream = options?.upstream;
|
|
7651
|
-
if (!upstream)
|
|
7652
|
-
return [];
|
|
7653
|
-
const args = ["log", `--format=%h||%s||%D`, `--max-count=${count}`, `HEAD..${upstream}`];
|
|
7654
|
-
const { exitCode, stdout: stdout2 } = await run(args);
|
|
7655
|
-
if (exitCode !== 0)
|
|
7656
|
-
return [];
|
|
7657
|
-
return stdout2.trimEnd().split(`
|
|
7658
|
-
`).filter(Boolean).map((line) => {
|
|
7659
|
-
const [hash = "", subject = "", refs = ""] = line.split("||");
|
|
7660
|
-
return { hash: hash.trim(), subject: subject.trim(), refs: refs.trim() };
|
|
7661
|
-
});
|
|
7662
|
-
}
|
|
7663
7716
|
async function getLocalBranches() {
|
|
7664
7717
|
const { exitCode, stdout: stdout2 } = await run(["branch", "-vv", "--no-color"]);
|
|
7665
7718
|
if (exitCode !== 0)
|
|
@@ -8756,6 +8809,233 @@ var LogEngine = {
|
|
|
8756
8809
|
|
|
8757
8810
|
// src/utils/logger.ts
|
|
8758
8811
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
8812
|
+
|
|
8813
|
+
// src/utils/tips.ts
|
|
8814
|
+
var COMMAND_GUIDES = {
|
|
8815
|
+
setup: {
|
|
8816
|
+
summary: "Initialize workflow rules, remotes, AI settings, and personal repo defaults.",
|
|
8817
|
+
examples: [
|
|
8818
|
+
{ command: "cn setup --help", description: "learn all setup options and prompts" },
|
|
8819
|
+
{ command: "cn setup", description: "initialize workflow, conventions, and remotes" },
|
|
8820
|
+
{
|
|
8821
|
+
command: "cn setup",
|
|
8822
|
+
description: "re-run setup to update repo preferences for this clone"
|
|
8823
|
+
},
|
|
8824
|
+
{
|
|
8825
|
+
command: "cn setup",
|
|
8826
|
+
description: "migrate legacy repo-root config into local Git storage"
|
|
8827
|
+
}
|
|
8828
|
+
]
|
|
8829
|
+
},
|
|
8830
|
+
start: {
|
|
8831
|
+
summary: "Create a new working branch from the correct base branch for your workflow.",
|
|
8832
|
+
examples: [
|
|
8833
|
+
{ command: "cn start --help", description: "learn branch naming and creation flags" },
|
|
8834
|
+
{
|
|
8835
|
+
command: "cn start feature/user-auth",
|
|
8836
|
+
description: "create a branch from an explicit name"
|
|
8837
|
+
},
|
|
8838
|
+
{
|
|
8839
|
+
command: 'cn start "fix login timeout"',
|
|
8840
|
+
description: "describe the work and let the CLI help"
|
|
8841
|
+
},
|
|
8842
|
+
{
|
|
8843
|
+
command: 'cn start "fix login timeout" --no-ai',
|
|
8844
|
+
description: "skip AI and keep control manual"
|
|
8845
|
+
}
|
|
8846
|
+
]
|
|
8847
|
+
},
|
|
8848
|
+
sync: {
|
|
8849
|
+
summary: "Sync your protected branches with the right upstream source for your role.",
|
|
8850
|
+
examples: [
|
|
8851
|
+
{ command: "cn sync --help", description: "learn sync modes and confirmation flags" },
|
|
8852
|
+
{ command: "cn sync", description: "pull the right base branch for your workflow" },
|
|
8853
|
+
{ command: "cn sync --yes", description: "skip the confirmation prompt" },
|
|
8854
|
+
{ command: "cn sync", description: "refresh local protected branches before feature work" }
|
|
8855
|
+
]
|
|
8856
|
+
},
|
|
8857
|
+
commit: {
|
|
8858
|
+
summary: "Stage changes, validate the message format, and create one or more commits.",
|
|
8859
|
+
examples: [
|
|
8860
|
+
{ command: "cn commit --help", description: "learn commit generation and grouping flags" },
|
|
8861
|
+
{ command: "cn commit", description: "stage and create one commit" },
|
|
8862
|
+
{ command: "cn commit --no-ai", description: "write the commit message yourself" },
|
|
8863
|
+
{ command: "cn commit --group", description: "split a large changeset into atomic commits" }
|
|
8864
|
+
]
|
|
8865
|
+
},
|
|
8866
|
+
update: {
|
|
8867
|
+
summary: "Rebase your current feature branch onto the latest configured base branch.",
|
|
8868
|
+
examples: [
|
|
8869
|
+
{ command: "cn update --help", description: "learn rebase and conflict guidance options" },
|
|
8870
|
+
{ command: "cn update", description: "rebase your branch onto the latest base branch" },
|
|
8871
|
+
{ command: "cn update --no-ai", description: "skip AI conflict guidance" },
|
|
8872
|
+
{ command: "cn update", description: "refresh your branch before pushing or opening a PR" }
|
|
8873
|
+
]
|
|
8874
|
+
},
|
|
8875
|
+
submit: {
|
|
8876
|
+
summary: "Push your branch and submit it through a pull request or local merge flow.",
|
|
8877
|
+
examples: [
|
|
8878
|
+
{ command: "cn submit --help", description: "learn PR and local submit modes" },
|
|
8879
|
+
{ command: "cn submit", description: "push and create or update a PR" },
|
|
8880
|
+
{ command: "cn submit --pullrequest", description: "go straight to the PR flow" },
|
|
8881
|
+
{ command: "cn submit -l", description: "maintainers can squash-merge locally" }
|
|
8882
|
+
]
|
|
8883
|
+
},
|
|
8884
|
+
switch: {
|
|
8885
|
+
summary: "Switch branches safely and protect uncommitted work before moving around.",
|
|
8886
|
+
examples: [
|
|
8887
|
+
{ command: "cn switch --help", description: "learn interactive and direct switch usage" },
|
|
8888
|
+
{ command: "cn switch", description: "pick a branch interactively" },
|
|
8889
|
+
{ command: "cn switch feature/login-fix", description: "switch directly to a named branch" },
|
|
8890
|
+
{ command: "cn switch dev", description: "jump back to a protected branch directly" }
|
|
8891
|
+
]
|
|
8892
|
+
},
|
|
8893
|
+
save: {
|
|
8894
|
+
summary: "Store uncommitted work for later and restore or delete saved change sets.",
|
|
8895
|
+
examples: [
|
|
8896
|
+
{ command: "cn save --help", description: "learn save, restore, list, and drop actions" },
|
|
8897
|
+
{ command: "cn save", description: "stash current uncommitted work" },
|
|
8898
|
+
{ command: "cn save --restore", description: "bring back a saved change set" },
|
|
8899
|
+
{ command: "cn save --list", description: "review what you saved before restoring" },
|
|
8900
|
+
{ command: "cn save --drop", description: "discard a saved change set you no longer need" }
|
|
8901
|
+
]
|
|
8902
|
+
},
|
|
8903
|
+
clean: {
|
|
8904
|
+
summary: "Delete merged or stale branches and keep the local repo tidy.",
|
|
8905
|
+
examples: [
|
|
8906
|
+
{ command: "cn clean --help", description: "learn cleanup behavior and shortcuts" },
|
|
8907
|
+
{ command: "cn clean", description: "review merged and stale branches before deleting" },
|
|
8908
|
+
{ command: "cn clean --yes", description: "skip the confirmation prompt" },
|
|
8909
|
+
{ command: "cn clean", description: "remove stale local branches after merge cleanup" }
|
|
8910
|
+
]
|
|
8911
|
+
},
|
|
8912
|
+
status: {
|
|
8913
|
+
summary: "Inspect branch alignment, working tree state, and next recommended actions.",
|
|
8914
|
+
examples: [
|
|
8915
|
+
{ command: "cn status --help", description: "learn what the status dashboard shows" },
|
|
8916
|
+
{ command: "cn status", description: "see branch alignment and working tree state" },
|
|
8917
|
+
{ command: "cn status", description: "check whether protected branches are aligned" },
|
|
8918
|
+
{ command: "cn status", description: "review staged, modified, and untracked files" }
|
|
8919
|
+
]
|
|
8920
|
+
},
|
|
8921
|
+
log: {
|
|
8922
|
+
summary: "View commit history for your current branch, remote diffs, or the full repo graph.",
|
|
8923
|
+
examples: [
|
|
8924
|
+
{ command: "cn log --help", description: "learn all log views and filtering flags" },
|
|
8925
|
+
{ command: "cn log", description: "show local and remote history in one split view" },
|
|
8926
|
+
{ command: "cn log --local", description: "show only local unpushed commits" },
|
|
8927
|
+
{ command: "cn log --remote", description: "show only remote branch history" },
|
|
8928
|
+
{ command: "cn log --full", description: "show full history for the current branch" },
|
|
8929
|
+
{ command: "cn log --all", description: "show history across all branches" },
|
|
8930
|
+
{ command: "cn log -b dev", description: "inspect a specific branch" }
|
|
8931
|
+
]
|
|
8932
|
+
},
|
|
8933
|
+
branch: {
|
|
8934
|
+
summary: "List branches with workflow-aware labels and local or remote tracking details.",
|
|
8935
|
+
examples: [
|
|
8936
|
+
{ command: "cn branch --help", description: "learn branch list modes and filters" },
|
|
8937
|
+
{ command: "cn branch", description: "list local branches and tracking info" },
|
|
8938
|
+
{ command: "cn branch --all", description: "include local and remote branches" },
|
|
8939
|
+
{ command: "cn branch --remote", description: "show only remote branches" }
|
|
8940
|
+
]
|
|
8941
|
+
},
|
|
8942
|
+
hook: {
|
|
8943
|
+
summary: "Install or remove a managed commit-msg hook for commit convention validation.",
|
|
8944
|
+
examples: [
|
|
8945
|
+
{ command: "cn hook --help", description: "learn hook install and uninstall usage" },
|
|
8946
|
+
{ command: "cn hook install", description: "validate commit messages automatically" },
|
|
8947
|
+
{ command: "cn hook uninstall", description: "remove the managed git hook" },
|
|
8948
|
+
{
|
|
8949
|
+
command: "cn hook install",
|
|
8950
|
+
description: "keep commit convention checks active in this clone"
|
|
8951
|
+
}
|
|
8952
|
+
]
|
|
8953
|
+
},
|
|
8954
|
+
validate: {
|
|
8955
|
+
summary: "Check a commit message against the repository commit convention rules.",
|
|
8956
|
+
examples: [
|
|
8957
|
+
{
|
|
8958
|
+
command: "cn validate --help",
|
|
8959
|
+
description: "learn direct and file-based validation usage"
|
|
8960
|
+
},
|
|
8961
|
+
{
|
|
8962
|
+
command: 'cn validate "\uD83D\uDD27 update: tidy config"',
|
|
8963
|
+
description: "validate one message inline"
|
|
8964
|
+
},
|
|
8965
|
+
{
|
|
8966
|
+
command: "cn validate --file .git/COMMIT_EDITMSG",
|
|
8967
|
+
description: "validate a commit message file"
|
|
8968
|
+
},
|
|
8969
|
+
{
|
|
8970
|
+
command: 'cn validate "\uD83D\uDCE6 new: add API client"',
|
|
8971
|
+
description: "check another message before committing"
|
|
8972
|
+
}
|
|
8973
|
+
]
|
|
8974
|
+
},
|
|
8975
|
+
doctor: {
|
|
8976
|
+
summary: "Run environment, dependency, config, and workflow diagnostics for the CLI.",
|
|
8977
|
+
examples: [
|
|
8978
|
+
{ command: "cn doctor --help", description: "learn human and JSON output modes" },
|
|
8979
|
+
{ command: "cn doctor", description: "run a full environment and config check" },
|
|
8980
|
+
{ command: "cn doctor --json", description: "export machine-readable diagnostics" },
|
|
8981
|
+
{
|
|
8982
|
+
command: "cn doctor",
|
|
8983
|
+
description: "check config, remotes, and workflow resolution together"
|
|
8984
|
+
}
|
|
8985
|
+
]
|
|
8986
|
+
}
|
|
8987
|
+
};
|
|
8988
|
+
var LOADING_TIPS = [
|
|
8989
|
+
"Manual commit mode: cn commit --no-ai",
|
|
8990
|
+
'Disable AI for this clone: set "aiEnabled": false',
|
|
8991
|
+
'Describe work for naming help: cn start "fix login timeout"',
|
|
8992
|
+
"Remote-only history: cn log --remote",
|
|
8993
|
+
"Skip PR mode prompt: cn submit --pullrequest"
|
|
8994
|
+
];
|
|
8995
|
+
function getVisibleCommandGuide(command, rotationIndex = 0) {
|
|
8996
|
+
const key = normalizeCommandKey(command);
|
|
8997
|
+
const guide = COMMAND_GUIDES[key];
|
|
8998
|
+
if (!guide)
|
|
8999
|
+
return null;
|
|
9000
|
+
const helpExample = guide.examples.find((example) => example.command.endsWith("--help")) ?? null;
|
|
9001
|
+
const rotatingExamples = dedupeGuideExamples(helpExample ? guide.examples.filter((example) => example !== helpExample) : [...guide.examples]);
|
|
9002
|
+
const visibleExamples = [];
|
|
9003
|
+
if (helpExample) {
|
|
9004
|
+
visibleExamples.push(helpExample);
|
|
9005
|
+
}
|
|
9006
|
+
const remainingSlots = Math.max(0, 3 - visibleExamples.length);
|
|
9007
|
+
if (rotatingExamples.length <= remainingSlots) {
|
|
9008
|
+
visibleExamples.push(...rotatingExamples);
|
|
9009
|
+
} else {
|
|
9010
|
+
for (let index = 0;index < remainingSlots; index++) {
|
|
9011
|
+
visibleExamples.push(rotatingExamples[(rotationIndex + index) % rotatingExamples.length]);
|
|
9012
|
+
}
|
|
9013
|
+
}
|
|
9014
|
+
return {
|
|
9015
|
+
guide,
|
|
9016
|
+
examples: visibleExamples.slice(0, 3),
|
|
9017
|
+
rotatableCount: rotatingExamples.length,
|
|
9018
|
+
key
|
|
9019
|
+
};
|
|
9020
|
+
}
|
|
9021
|
+
function normalizeCommandKey(command) {
|
|
9022
|
+
const normalized = command.replace(/\s*\(.+\)$/, "").trim();
|
|
9023
|
+
return normalized.split(/\s+/)[0] ?? normalized;
|
|
9024
|
+
}
|
|
9025
|
+
function dedupeGuideExamples(examples) {
|
|
9026
|
+
const seen = new Set;
|
|
9027
|
+
const deduped = [];
|
|
9028
|
+
for (const example of examples) {
|
|
9029
|
+
if (seen.has(example.command)) {
|
|
9030
|
+
continue;
|
|
9031
|
+
}
|
|
9032
|
+
seen.add(example.command);
|
|
9033
|
+
deduped.push(example);
|
|
9034
|
+
}
|
|
9035
|
+
return deduped;
|
|
9036
|
+
}
|
|
9037
|
+
|
|
9038
|
+
// src/utils/logger.ts
|
|
8759
9039
|
LogEngine.configure({
|
|
8760
9040
|
mode: LogMode.INFO,
|
|
8761
9041
|
format: {
|
|
@@ -8776,9 +9056,63 @@ function warn(msg, emoji = "⚠️") {
|
|
|
8776
9056
|
function info(msg, emoji = "ℹ️") {
|
|
8777
9057
|
LogEngine.info(msg, undefined, { emoji });
|
|
8778
9058
|
}
|
|
8779
|
-
function
|
|
8780
|
-
|
|
8781
|
-
${import_picocolors.default.bold(
|
|
9059
|
+
function projectHeading(command, emoji) {
|
|
9060
|
+
const prefix = emoji ? `${emoji} ` : "";
|
|
9061
|
+
console.log(` ${import_picocolors.default.bold(import_picocolors.default.cyan(`${prefix}${command}`))}`);
|
|
9062
|
+
const config = readConfig();
|
|
9063
|
+
const rotationIndex = config?.guideRotation?.[normalizeGuideKey(command)] ?? 0;
|
|
9064
|
+
const visibleGuide = getVisibleCommandGuide(command, rotationIndex);
|
|
9065
|
+
if (visibleGuide) {
|
|
9066
|
+
console.log(` ${import_picocolors.default.dim(visibleGuide.guide.summary)}`);
|
|
9067
|
+
}
|
|
9068
|
+
if (!shouldShowTips(config)) {
|
|
9069
|
+
return;
|
|
9070
|
+
}
|
|
9071
|
+
if (!visibleGuide || visibleGuide.examples.length === 0) {
|
|
9072
|
+
return;
|
|
9073
|
+
}
|
|
9074
|
+
if (config && visibleGuide.rotatableCount > 0) {
|
|
9075
|
+
config.guideRotation = config.guideRotation ?? {};
|
|
9076
|
+
config.guideRotation[visibleGuide.key] = (rotationIndex + 1) % visibleGuide.rotatableCount;
|
|
9077
|
+
writeConfig(config);
|
|
9078
|
+
}
|
|
9079
|
+
console.log();
|
|
9080
|
+
const terminalWidth = process.stdout.columns ?? 80;
|
|
9081
|
+
const maxContentWidth = Math.max(28, terminalWidth - 8);
|
|
9082
|
+
const commandWidth = visibleGuide.examples.reduce((max, example) => Math.max(max, example.command.length), 0);
|
|
9083
|
+
const descriptionWidth = Math.max(12, maxContentWidth - commandWidth - 2);
|
|
9084
|
+
const rows = visibleGuide.examples.map((example) => {
|
|
9085
|
+
const description = truncateText(example.description, descriptionWidth);
|
|
9086
|
+
const commandText = example.command.padEnd(commandWidth + 2);
|
|
9087
|
+
return {
|
|
9088
|
+
commandText,
|
|
9089
|
+
description,
|
|
9090
|
+
rawLength: commandText.length + description.length
|
|
9091
|
+
};
|
|
9092
|
+
});
|
|
9093
|
+
const contentWidth = Math.min(maxContentWidth, Math.max(28, ...rows.map((row) => row.rawLength)));
|
|
9094
|
+
const label = "─ quick guide ";
|
|
9095
|
+
const topBorder = `┌${label}${"─".repeat(Math.max(1, contentWidth - label.length + 1))}┐`;
|
|
9096
|
+
console.log(` ${import_picocolors.default.dim(topBorder)}`);
|
|
9097
|
+
for (const row of rows) {
|
|
9098
|
+
const left = import_picocolors.default.cyan(row.commandText);
|
|
9099
|
+
const right = import_picocolors.default.dim(row.description);
|
|
9100
|
+
const trailing = " ".repeat(Math.max(0, contentWidth - row.rawLength));
|
|
9101
|
+
console.log(` ${import_picocolors.default.dim("│")} ${left}${right}${trailing}${import_picocolors.default.dim("│")}`);
|
|
9102
|
+
}
|
|
9103
|
+
console.log(` ${import_picocolors.default.dim(`└${"─".repeat(contentWidth + 1)}┘`)}`);
|
|
9104
|
+
}
|
|
9105
|
+
function normalizeGuideKey(command) {
|
|
9106
|
+
return command.replace(/\s*\(.+\)$/, "").trim().split(/\s+/)[0] ?? command;
|
|
9107
|
+
}
|
|
9108
|
+
function truncateText(text, maxWidth) {
|
|
9109
|
+
if (text.length <= maxWidth) {
|
|
9110
|
+
return text;
|
|
9111
|
+
}
|
|
9112
|
+
if (maxWidth <= 1) {
|
|
9113
|
+
return text.slice(0, maxWidth);
|
|
9114
|
+
}
|
|
9115
|
+
return `${text.slice(0, maxWidth - 1)}…`;
|
|
8782
9116
|
}
|
|
8783
9117
|
|
|
8784
9118
|
// src/utils/workflow.ts
|
|
@@ -8871,7 +9205,7 @@ var branch_default = defineCommand({
|
|
|
8871
9205
|
const currentBranch = await getCurrentBranch();
|
|
8872
9206
|
const showRemoteOnly = args.remote;
|
|
8873
9207
|
const showAll = args.all;
|
|
8874
|
-
|
|
9208
|
+
projectHeading("branch", "\uD83C\uDF3F");
|
|
8875
9209
|
console.log();
|
|
8876
9210
|
if (!showRemoteOnly) {
|
|
8877
9211
|
const localBranches = await getLocalBranches();
|
|
@@ -8926,20 +9260,6 @@ var branch_default = defineCommand({
|
|
|
8926
9260
|
}
|
|
8927
9261
|
}
|
|
8928
9262
|
}
|
|
8929
|
-
const tips = [];
|
|
8930
|
-
if (!showAll && !showRemoteOnly) {
|
|
8931
|
-
tips.push(`Use ${import_picocolors2.default.bold("contrib branch -a")} to include remote branches`);
|
|
8932
|
-
}
|
|
8933
|
-
if (!showRemoteOnly) {
|
|
8934
|
-
tips.push(`Use ${import_picocolors2.default.bold("contrib start")} to create a new feature branch`);
|
|
8935
|
-
tips.push(`Use ${import_picocolors2.default.bold("contrib clean")} to remove merged/stale branches`);
|
|
8936
|
-
}
|
|
8937
|
-
if (tips.length > 0) {
|
|
8938
|
-
console.log(` ${import_picocolors2.default.dim("\uD83D\uDCA1 Tip:")}`);
|
|
8939
|
-
for (const tip of tips) {
|
|
8940
|
-
console.log(` ${import_picocolors2.default.dim(tip)}`);
|
|
8941
|
-
}
|
|
8942
|
-
}
|
|
8943
9263
|
console.log();
|
|
8944
9264
|
}
|
|
8945
9265
|
});
|
|
@@ -8981,6 +9301,9 @@ function groupByRemote(branches) {
|
|
|
8981
9301
|
}
|
|
8982
9302
|
|
|
8983
9303
|
// src/commands/clean.ts
|
|
9304
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
9305
|
+
|
|
9306
|
+
// src/utils/branchPrompt.ts
|
|
8984
9307
|
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
8985
9308
|
|
|
8986
9309
|
// src/utils/branch.ts
|
|
@@ -10168,11 +10491,11 @@ function suppressSubprocessWarnings() {
|
|
|
10168
10491
|
process.env.NODE_NO_WARNINGS = "1";
|
|
10169
10492
|
}
|
|
10170
10493
|
function withTimeout(promise, ms) {
|
|
10171
|
-
return new Promise((
|
|
10494
|
+
return new Promise((resolve3, reject) => {
|
|
10172
10495
|
const timer = setTimeout(() => reject(new Error(`Copilot request timed out after ${ms / 1000}s`)), ms);
|
|
10173
10496
|
promise.then((val) => {
|
|
10174
10497
|
clearTimeout(timer);
|
|
10175
|
-
|
|
10498
|
+
resolve3(val);
|
|
10176
10499
|
}, (err) => {
|
|
10177
10500
|
clearTimeout(timer);
|
|
10178
10501
|
reject(err);
|
|
@@ -10183,10 +10506,84 @@ var COPILOT_TIMEOUT_MS = 30000;
|
|
|
10183
10506
|
var COPILOT_LONG_TIMEOUT_MS = 90000;
|
|
10184
10507
|
var BATCH_CONFIG = {
|
|
10185
10508
|
LARGE_CHANGESET_THRESHOLD: 15,
|
|
10509
|
+
DIRECT_BATCH_THRESHOLD: 40,
|
|
10186
10510
|
COMPACT_PER_FILE_CHARS: 300,
|
|
10187
10511
|
MAX_COMPACT_PAYLOAD: 1e4,
|
|
10188
|
-
FALLBACK_BATCH_SIZE:
|
|
10512
|
+
FALLBACK_BATCH_SIZE: 8
|
|
10189
10513
|
};
|
|
10514
|
+
function getRecoveryGroupKey(file) {
|
|
10515
|
+
const parts = file.split("/").filter(Boolean);
|
|
10516
|
+
const [topLevel = "", secondLevel = ""] = parts;
|
|
10517
|
+
const extension = parts.at(-1)?.split(".").at(-1)?.toLowerCase() ?? "";
|
|
10518
|
+
if (topLevel === "src" || topLevel === "tests") {
|
|
10519
|
+
return `${topLevel}/${secondLevel || "root"}`;
|
|
10520
|
+
}
|
|
10521
|
+
if (topLevel === "docs" || topLevel === "landing") {
|
|
10522
|
+
return topLevel;
|
|
10523
|
+
}
|
|
10524
|
+
if (!topLevel.includes(".")) {
|
|
10525
|
+
return topLevel || "root";
|
|
10526
|
+
}
|
|
10527
|
+
if (extension === "md" || extension === "mdx") {
|
|
10528
|
+
return "root-docs";
|
|
10529
|
+
}
|
|
10530
|
+
if (["json", "yaml", "yml", "toml"].includes(extension)) {
|
|
10531
|
+
return "root-config";
|
|
10532
|
+
}
|
|
10533
|
+
return "root";
|
|
10534
|
+
}
|
|
10535
|
+
function createFallbackMessageForGroup(files, key, convention) {
|
|
10536
|
+
const countLabel = files.length === 1 ? "file" : "files";
|
|
10537
|
+
if (key === "docs" || key === "root-docs") {
|
|
10538
|
+
return convention === "conventional" ? `docs: update ${files.length} documentation ${countLabel}` : convention === "clean-commit" ? `\uD83D\uDCD6 docs: update ${files.length} documentation ${countLabel}` : `update ${files.length} documentation ${countLabel}`;
|
|
10539
|
+
}
|
|
10540
|
+
if (key.startsWith("tests/")) {
|
|
10541
|
+
const scope = key.split("/")[1];
|
|
10542
|
+
return convention === "conventional" ? `test${scope && scope !== "root" ? `(${scope})` : ""}: update ${files.length} test ${countLabel}` : convention === "clean-commit" ? `\uD83E\uDDEA test${scope && scope !== "root" ? ` (${scope})` : ""}: update ${files.length} test ${countLabel}` : `update ${files.length} test ${countLabel}`;
|
|
10543
|
+
}
|
|
10544
|
+
if (key === "landing") {
|
|
10545
|
+
return convention === "conventional" ? `chore(ui): update ${files.length} landing ${countLabel}` : convention === "clean-commit" ? `\uD83D\uDD27 update (ui): update ${files.length} landing ${countLabel}` : `update ${files.length} landing ${countLabel}`;
|
|
10546
|
+
}
|
|
10547
|
+
if (key === "root-config") {
|
|
10548
|
+
return convention === "conventional" ? `chore(config): update ${files.length} config ${countLabel}` : convention === "clean-commit" ? `⚙️ setup (config): update ${files.length} config ${countLabel}` : `update ${files.length} config ${countLabel}`;
|
|
10549
|
+
}
|
|
10550
|
+
if (key.startsWith("src/")) {
|
|
10551
|
+
const scope = key.split("/")[1];
|
|
10552
|
+
const scopeLabel = scope && scope !== "root" ? scope : "code";
|
|
10553
|
+
return convention === "conventional" ? `chore(${scopeLabel}): update ${files.length} source ${countLabel}` : convention === "clean-commit" ? `\uD83D\uDD27 update (${scopeLabel}): update ${files.length} source ${countLabel}` : `update ${files.length} source ${countLabel}`;
|
|
10554
|
+
}
|
|
10555
|
+
return convention === "conventional" ? `chore: update ${files.length} repo ${countLabel}` : convention === "clean-commit" ? `☕ chore: update ${files.length} repo ${countLabel}` : `update ${files.length} repo ${countLabel}`;
|
|
10556
|
+
}
|
|
10557
|
+
function createRecoveryCommitGroups(files, convention = "clean-commit") {
|
|
10558
|
+
if (files.length === 0) {
|
|
10559
|
+
return [];
|
|
10560
|
+
}
|
|
10561
|
+
const grouped = new Map;
|
|
10562
|
+
for (const file of files) {
|
|
10563
|
+
const key = getRecoveryGroupKey(file);
|
|
10564
|
+
const entry = grouped.get(key);
|
|
10565
|
+
if (entry) {
|
|
10566
|
+
entry.push(file);
|
|
10567
|
+
} else {
|
|
10568
|
+
grouped.set(key, [file]);
|
|
10569
|
+
}
|
|
10570
|
+
}
|
|
10571
|
+
const result = [];
|
|
10572
|
+
for (const [key, groupedFiles] of grouped.entries()) {
|
|
10573
|
+
for (let index = 0;index < groupedFiles.length; index += BATCH_CONFIG.FALLBACK_BATCH_SIZE) {
|
|
10574
|
+
const chunk = groupedFiles.slice(index, index + BATCH_CONFIG.FALLBACK_BATCH_SIZE);
|
|
10575
|
+
result.push({
|
|
10576
|
+
files: chunk,
|
|
10577
|
+
message: createFallbackMessageForGroup(chunk, key, convention)
|
|
10578
|
+
});
|
|
10579
|
+
}
|
|
10580
|
+
}
|
|
10581
|
+
return result;
|
|
10582
|
+
}
|
|
10583
|
+
function hasIncompleteDiffCoverage(files, rawDiff) {
|
|
10584
|
+
const diffSections = parseDiffByFile(rawDiff);
|
|
10585
|
+
return files.some((file) => !diffSections.has(file));
|
|
10586
|
+
}
|
|
10190
10587
|
function parseDiffByFile(rawDiff) {
|
|
10191
10588
|
const sections = new Map;
|
|
10192
10589
|
const headerPattern = /^diff --git a\/(.+?) b\/(.+?)$/gm;
|
|
@@ -10254,7 +10651,7 @@ ${truncated}
|
|
|
10254
10651
|
return result.length > maxTotalChars ? `${result.slice(0, maxTotalChars - 15)}
|
|
10255
10652
|
...(truncated)` : result;
|
|
10256
10653
|
}
|
|
10257
|
-
async function
|
|
10654
|
+
async function checkCopilotAvailable2() {
|
|
10258
10655
|
try {
|
|
10259
10656
|
const client = await getManagedClient();
|
|
10260
10657
|
try {
|
|
@@ -10358,13 +10755,14 @@ function sanitizeGeneratedCommitMessage(message) {
|
|
|
10358
10755
|
async function generateCommitMessage(diff, stagedFiles, model, convention = "clean-commit", context) {
|
|
10359
10756
|
try {
|
|
10360
10757
|
const isLarge = stagedFiles.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD;
|
|
10758
|
+
const hasMissingDiffCoverage = hasIncompleteDiffCoverage(stagedFiles, diff);
|
|
10361
10759
|
const multiFileHint = stagedFiles.length > 1 ? `
|
|
10362
10760
|
|
|
10363
10761
|
IMPORTANT: Multiple files are staged. Generate ONE commit message that captures the high-level purpose of ALL changes together. Focus on the overall intent, not individual file changes. Be specific but concise — do not list every file.` : "";
|
|
10364
10762
|
const squashHint = context === "squash-merge" ? `
|
|
10365
10763
|
|
|
10366
10764
|
CONTEXT: This is a squash merge of an entire feature branch into the base branch. All commits are being combined into ONE single commit. Generate a single high-level summary that describes the overall feature or change — NOT a list of individual commits. Think: what capability was added or what problem was solved? Be specific but concise.` : "";
|
|
10367
|
-
const diffContent = isLarge ? createCompactDiff(stagedFiles, diff) : diff.slice(0, 4000);
|
|
10765
|
+
const diffContent = isLarge || hasMissingDiffCoverage ? createCompactDiff(stagedFiles, diff) : diff.slice(0, 4000);
|
|
10368
10766
|
const userMessage = `Generate a commit message for these staged changes:
|
|
10369
10767
|
|
|
10370
10768
|
Files (${stagedFiles.length}): ${stagedFiles.join(", ")}
|
|
@@ -10453,9 +10851,15 @@ function normalizeCommitGroups(changedFiles, groups) {
|
|
|
10453
10851
|
unassignedFiles
|
|
10454
10852
|
};
|
|
10455
10853
|
}
|
|
10456
|
-
async function generateCommitGroups(files, diffs, model, convention = "clean-commit") {
|
|
10854
|
+
async function generateCommitGroups(files, diffs, model, convention = "clean-commit", onProgress) {
|
|
10457
10855
|
const isLarge = files.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD;
|
|
10458
|
-
const
|
|
10856
|
+
const shouldBatchImmediately = files.length >= BATCH_CONFIG.DIRECT_BATCH_THRESHOLD;
|
|
10857
|
+
const hasMissingDiffCoverage = hasIncompleteDiffCoverage(files, diffs);
|
|
10858
|
+
if (shouldBatchImmediately) {
|
|
10859
|
+
onProgress?.(`Large changeset detected. Grouping in focused batches of ${BATCH_CONFIG.FALLBACK_BATCH_SIZE} files...`);
|
|
10860
|
+
return generateCommitGroupsInBatches(files, diffs, model, convention, onProgress);
|
|
10861
|
+
}
|
|
10862
|
+
const diffContent = isLarge || hasMissingDiffCoverage ? createCompactDiff(files, diffs) : diffs.slice(0, 6000);
|
|
10459
10863
|
const largeHint = isLarge ? `
|
|
10460
10864
|
|
|
10461
10865
|
NOTE: This is a large changeset (${files.length} files). Compact diffs are provided for every file. Focus on creating well-organized logical groups.` : "";
|
|
@@ -10467,10 +10871,22 @@ ${files.join(`
|
|
|
10467
10871
|
|
|
10468
10872
|
Diffs:
|
|
10469
10873
|
${diffContent}${largeHint}`;
|
|
10470
|
-
|
|
10874
|
+
let result = null;
|
|
10875
|
+
try {
|
|
10876
|
+
onProgress?.(`Analyzing ${files.length} files together before batching fallback...`);
|
|
10877
|
+
result = await callCopilot(getGroupingSystemPrompt(convention), userMessage, model, COPILOT_LONG_TIMEOUT_MS);
|
|
10878
|
+
} catch {
|
|
10879
|
+
if (isLarge) {
|
|
10880
|
+
onProgress?.(`Initial grouping timed out. Switching to focused batches of ${BATCH_CONFIG.FALLBACK_BATCH_SIZE} files...`);
|
|
10881
|
+
return generateCommitGroupsInBatches(files, diffs, model, convention, onProgress);
|
|
10882
|
+
}
|
|
10883
|
+
throw new Error("AI grouping failed before a response was returned");
|
|
10884
|
+
}
|
|
10471
10885
|
if (!result) {
|
|
10472
|
-
if (isLarge)
|
|
10473
|
-
|
|
10886
|
+
if (isLarge) {
|
|
10887
|
+
onProgress?.(`AI returned an empty response. Switching to focused batches...`);
|
|
10888
|
+
return generateCommitGroupsInBatches(files, diffs, model, convention, onProgress);
|
|
10889
|
+
}
|
|
10474
10890
|
throw new Error("AI returned an empty response");
|
|
10475
10891
|
}
|
|
10476
10892
|
const cleaned = extractJson(result);
|
|
@@ -10478,14 +10894,18 @@ ${diffContent}${largeHint}`;
|
|
|
10478
10894
|
try {
|
|
10479
10895
|
parsed = JSON.parse(cleaned);
|
|
10480
10896
|
} catch {
|
|
10481
|
-
if (isLarge)
|
|
10482
|
-
|
|
10897
|
+
if (isLarge) {
|
|
10898
|
+
onProgress?.("AI returned invalid JSON for the full changeset. Switching to focused batches...");
|
|
10899
|
+
return generateCommitGroupsInBatches(files, diffs, model, convention, onProgress);
|
|
10900
|
+
}
|
|
10483
10901
|
throw new Error(`AI response is not valid JSON. Raw start: "${result.slice(0, 120)}..."`);
|
|
10484
10902
|
}
|
|
10485
10903
|
const groups = parsed;
|
|
10486
10904
|
if (!Array.isArray(groups) || groups.length === 0) {
|
|
10487
|
-
if (isLarge)
|
|
10488
|
-
|
|
10905
|
+
if (isLarge) {
|
|
10906
|
+
onProgress?.("AI returned no usable groups for the full changeset. Switching to focused batches...");
|
|
10907
|
+
return generateCommitGroupsInBatches(files, diffs, model, convention, onProgress);
|
|
10908
|
+
}
|
|
10489
10909
|
throw new Error("AI response was not a valid JSON array of commit groups");
|
|
10490
10910
|
}
|
|
10491
10911
|
for (const group of groups) {
|
|
@@ -10498,17 +10918,18 @@ ${diffContent}${largeHint}`;
|
|
|
10498
10918
|
message: sanitizeGeneratedCommitMessage(group.message)
|
|
10499
10919
|
}));
|
|
10500
10920
|
}
|
|
10501
|
-
async function generateCommitGroupsInBatches(files, diffs, model, convention = "clean-commit") {
|
|
10921
|
+
async function generateCommitGroupsInBatches(files, diffs, model, convention = "clean-commit", onProgress) {
|
|
10502
10922
|
const batchSize = BATCH_CONFIG.FALLBACK_BATCH_SIZE;
|
|
10503
10923
|
const allGroups = [];
|
|
10504
10924
|
const diffSections = parseDiffByFile(diffs);
|
|
10925
|
+
const totalBatches = Math.ceil(files.length / batchSize);
|
|
10505
10926
|
for (let i2 = 0;i2 < files.length; i2 += batchSize) {
|
|
10506
10927
|
const batchFiles = files.slice(i2, i2 + batchSize);
|
|
10507
10928
|
const batchDiff = batchFiles.map((f3) => diffSections.get(f3) ?? "").filter(Boolean).join(`
|
|
10508
10929
|
`);
|
|
10509
|
-
const batchDiffContent = batchFiles.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD ? createCompactDiff(batchFiles, batchDiff) : batchDiff.slice(0, 6000);
|
|
10930
|
+
const batchDiffContent = batchFiles.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD || hasIncompleteDiffCoverage(batchFiles, batchDiff) ? createCompactDiff(batchFiles, batchDiff) : batchDiff.slice(0, 6000);
|
|
10510
10931
|
const batchNum = Math.floor(i2 / batchSize) + 1;
|
|
10511
|
-
|
|
10932
|
+
onProgress?.(`Grouping batch ${batchNum}/${totalBatches} (${batchFiles.length} files)...`);
|
|
10512
10933
|
const userMessage = `Group these changed files into logical atomic commits:
|
|
10513
10934
|
|
|
10514
10935
|
Files:
|
|
@@ -10545,10 +10966,7 @@ NOTE: Processing batch ${batchNum}/${totalBatches} of a large changeset. Group o
|
|
|
10545
10966
|
const groupedFiles = new Set(allGroups.flatMap((g3) => g3.files));
|
|
10546
10967
|
const ungrouped = files.filter((f3) => !groupedFiles.has(f3));
|
|
10547
10968
|
if (ungrouped.length > 0) {
|
|
10548
|
-
allGroups.push(
|
|
10549
|
-
files: ungrouped,
|
|
10550
|
-
message: `chore: update ${ungrouped.length} remaining file${ungrouped.length !== 1 ? "s" : ""}`
|
|
10551
|
-
});
|
|
10969
|
+
allGroups.push(...createRecoveryCommitGroups(ungrouped, convention));
|
|
10552
10970
|
}
|
|
10553
10971
|
if (allGroups.length === 0) {
|
|
10554
10972
|
throw new Error("AI could not group any files even with batch processing");
|
|
@@ -10601,56 +11019,299 @@ ${diffContent}`;
|
|
|
10601
11019
|
}
|
|
10602
11020
|
}
|
|
10603
11021
|
|
|
10604
|
-
// src/utils/
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
stderr: stderr ?? ""
|
|
10613
|
-
});
|
|
10614
|
-
});
|
|
10615
|
-
});
|
|
10616
|
-
}
|
|
10617
|
-
async function checkGhInstalled() {
|
|
10618
|
-
try {
|
|
10619
|
-
const { exitCode } = await run2(["--version"]);
|
|
10620
|
-
return exitCode === 0;
|
|
10621
|
-
} catch {
|
|
10622
|
-
return false;
|
|
10623
|
-
}
|
|
10624
|
-
}
|
|
10625
|
-
async function checkGhAuth() {
|
|
10626
|
-
try {
|
|
10627
|
-
const { exitCode } = await run2(["auth", "status"]);
|
|
10628
|
-
return exitCode === 0;
|
|
10629
|
-
} catch {
|
|
10630
|
-
return false;
|
|
11022
|
+
// src/utils/spinner.ts
|
|
11023
|
+
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
11024
|
+
var FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
11025
|
+
var MIN_LINE_WIDTH = 20;
|
|
11026
|
+
var DEFAULT_TIP_INTERVAL_MS = 3960;
|
|
11027
|
+
function formatSpinnerLines(text, tip, maxWidth) {
|
|
11028
|
+
if (maxWidth <= 0) {
|
|
11029
|
+
return [];
|
|
10631
11030
|
}
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
if (!
|
|
10636
|
-
return
|
|
10637
|
-
const { exitCode, stdout: stdout2 } = await run2(["api", `repos/${owner}/${repo}`, "--jq", ".permissions"]);
|
|
10638
|
-
if (exitCode !== 0)
|
|
10639
|
-
return null;
|
|
10640
|
-
try {
|
|
10641
|
-
return JSON.parse(stdout2.trim());
|
|
10642
|
-
} catch {
|
|
10643
|
-
return null;
|
|
11031
|
+
const normalizedText = text.trim();
|
|
11032
|
+
const normalizedTip = formatSpinnerTip(tip);
|
|
11033
|
+
const primary = truncateText2(normalizedText, Math.max(MIN_LINE_WIDTH, maxWidth));
|
|
11034
|
+
if (!normalizedTip) {
|
|
11035
|
+
return [primary];
|
|
10644
11036
|
}
|
|
11037
|
+
const secondary = truncateText2(normalizedTip, Math.max(MIN_LINE_WIDTH, maxWidth - 2));
|
|
11038
|
+
return [primary, secondary];
|
|
10645
11039
|
}
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
11040
|
+
function createSpinner(text, options = {}) {
|
|
11041
|
+
let frameIdx = 0;
|
|
11042
|
+
let currentText = text;
|
|
11043
|
+
let stopped = false;
|
|
11044
|
+
let tipIdx = 0;
|
|
11045
|
+
let renderedLineCount = 0;
|
|
11046
|
+
let lastPrimaryLine = "";
|
|
11047
|
+
let lastSecondaryLine = "";
|
|
11048
|
+
const tips = options.tips?.filter(Boolean) ?? [];
|
|
11049
|
+
const tipIntervalMs = options.tipIntervalMs ?? DEFAULT_TIP_INTERVAL_MS;
|
|
11050
|
+
const clearBlock = () => {
|
|
11051
|
+
if (renderedLineCount === 0) {
|
|
11052
|
+
return;
|
|
11053
|
+
}
|
|
11054
|
+
for (let index = 0;index < renderedLineCount; index++) {
|
|
11055
|
+
process.stderr.write("\r\x1B[2K");
|
|
11056
|
+
if (index < renderedLineCount - 1) {
|
|
11057
|
+
process.stderr.write("\x1B[1A");
|
|
11058
|
+
}
|
|
11059
|
+
}
|
|
11060
|
+
process.stderr.write("\r");
|
|
11061
|
+
lastPrimaryLine = "";
|
|
11062
|
+
lastSecondaryLine = "";
|
|
11063
|
+
};
|
|
11064
|
+
const renderNextState = (primaryLine, secondaryLine) => {
|
|
11065
|
+
if (renderedLineCount === 0) {
|
|
11066
|
+
process.stderr.write(primaryLine);
|
|
11067
|
+
if (secondaryLine) {
|
|
11068
|
+
process.stderr.write(`
|
|
11069
|
+
${secondaryLine}`);
|
|
11070
|
+
renderedLineCount = 2;
|
|
11071
|
+
} else {
|
|
11072
|
+
renderedLineCount = 1;
|
|
11073
|
+
}
|
|
11074
|
+
lastPrimaryLine = primaryLine;
|
|
11075
|
+
lastSecondaryLine = secondaryLine ?? "";
|
|
11076
|
+
return;
|
|
11077
|
+
}
|
|
11078
|
+
if (renderedLineCount === 1) {
|
|
11079
|
+
if (lastPrimaryLine !== primaryLine) {
|
|
11080
|
+
process.stderr.write(`\r\x1B[2K${primaryLine}`);
|
|
11081
|
+
}
|
|
11082
|
+
if (secondaryLine) {
|
|
11083
|
+
process.stderr.write(`
|
|
11084
|
+
${secondaryLine}`);
|
|
11085
|
+
renderedLineCount = 2;
|
|
11086
|
+
}
|
|
11087
|
+
lastPrimaryLine = primaryLine;
|
|
11088
|
+
lastSecondaryLine = secondaryLine ?? "";
|
|
11089
|
+
return;
|
|
11090
|
+
}
|
|
11091
|
+
process.stderr.write(`\x1B[1A\r\x1B[2K${primaryLine}
|
|
11092
|
+
`);
|
|
11093
|
+
if (secondaryLine) {
|
|
11094
|
+
if (lastSecondaryLine !== secondaryLine) {
|
|
11095
|
+
process.stderr.write(`\r\x1B[2K${secondaryLine}`);
|
|
11096
|
+
}
|
|
11097
|
+
renderedLineCount = 2;
|
|
11098
|
+
} else {
|
|
11099
|
+
process.stderr.write("\r\x1B[2K\x1B[1A\r");
|
|
11100
|
+
renderedLineCount = 1;
|
|
11101
|
+
}
|
|
11102
|
+
lastPrimaryLine = primaryLine;
|
|
11103
|
+
lastSecondaryLine = secondaryLine ?? "";
|
|
11104
|
+
};
|
|
11105
|
+
const render = () => {
|
|
11106
|
+
if (stopped)
|
|
11107
|
+
return;
|
|
11108
|
+
const frame = import_picocolors6.default.cyan(FRAMES[frameIdx % FRAMES.length]);
|
|
11109
|
+
const width = Math.max(MIN_LINE_WIDTH, (process.stderr.columns ?? process.stdout.columns ?? 100) - 4);
|
|
11110
|
+
const lines = formatSpinnerLines(currentText, tips[tipIdx % tips.length], width);
|
|
11111
|
+
renderNextState(`${frame} ${import_picocolors6.default.cyan(lines[0] ?? "")}`, lines[1] ? ` ${import_picocolors6.default.dim(lines[1])}` : undefined);
|
|
11112
|
+
frameIdx++;
|
|
11113
|
+
};
|
|
11114
|
+
const timer = setInterval(render, 80);
|
|
11115
|
+
const tipTimer = tips.length > 1 ? setInterval(() => {
|
|
11116
|
+
tipIdx = (tipIdx + 1) % tips.length;
|
|
11117
|
+
}, tipIntervalMs) : null;
|
|
11118
|
+
render();
|
|
11119
|
+
const stop = () => {
|
|
11120
|
+
if (stopped)
|
|
11121
|
+
return;
|
|
11122
|
+
stopped = true;
|
|
11123
|
+
clearInterval(timer);
|
|
11124
|
+
if (tipTimer)
|
|
11125
|
+
clearInterval(tipTimer);
|
|
11126
|
+
clearBlock();
|
|
11127
|
+
renderedLineCount = 0;
|
|
11128
|
+
};
|
|
11129
|
+
return {
|
|
11130
|
+
update(newText) {
|
|
11131
|
+
currentText = newText;
|
|
11132
|
+
},
|
|
11133
|
+
success(msg) {
|
|
11134
|
+
stop();
|
|
11135
|
+
process.stderr.write(`${import_picocolors6.default.green("✔")} ${msg}
|
|
11136
|
+
`);
|
|
11137
|
+
},
|
|
11138
|
+
fail(msg) {
|
|
11139
|
+
stop();
|
|
11140
|
+
process.stderr.write(`${import_picocolors6.default.red("✖")} ${msg}
|
|
11141
|
+
`);
|
|
11142
|
+
},
|
|
11143
|
+
stop() {
|
|
11144
|
+
stop();
|
|
11145
|
+
}
|
|
11146
|
+
};
|
|
11147
|
+
}
|
|
11148
|
+
function formatSpinnerTip(tip) {
|
|
11149
|
+
const normalizedTip = tip?.trim() ?? "";
|
|
11150
|
+
return normalizedTip ? `\uD83D\uDCA1 TIP: ${normalizedTip}` : "";
|
|
11151
|
+
}
|
|
11152
|
+
function truncateText2(text, maxWidth) {
|
|
11153
|
+
if (text.length <= maxWidth) {
|
|
11154
|
+
return text;
|
|
11155
|
+
}
|
|
11156
|
+
if (maxWidth <= 1) {
|
|
11157
|
+
return text.slice(0, maxWidth);
|
|
11158
|
+
}
|
|
11159
|
+
return `${text.slice(0, maxWidth - 1)}…`;
|
|
11160
|
+
}
|
|
11161
|
+
|
|
11162
|
+
// src/utils/branchPrompt.ts
|
|
11163
|
+
async function promptForBranchName(options) {
|
|
11164
|
+
const promptMessage = options.promptMessage ?? "What are you going to work on?";
|
|
11165
|
+
let branchInput = options.initialValue?.trim() ?? "";
|
|
11166
|
+
while (!branchInput) {
|
|
11167
|
+
branchInput = (await inputPrompt(promptMessage)).trim();
|
|
11168
|
+
if (branchInput)
|
|
11169
|
+
break;
|
|
11170
|
+
warn("A branch name or description is required.");
|
|
11171
|
+
const action = await selectPrompt("What would you like to do?", ["Try again", "Cancel"]);
|
|
11172
|
+
if (action === "Cancel")
|
|
11173
|
+
return null;
|
|
11174
|
+
}
|
|
11175
|
+
let branchName = branchInput;
|
|
11176
|
+
const useAI = options.useAI !== false && looksLikeNaturalLanguage(branchInput);
|
|
11177
|
+
if (useAI) {
|
|
11178
|
+
const copilotError = await checkCopilotAvailable2();
|
|
11179
|
+
if (copilotError) {
|
|
11180
|
+
warn(`AI unavailable: ${copilotError}`);
|
|
11181
|
+
} else {
|
|
11182
|
+
while (true) {
|
|
11183
|
+
const spinner = createSpinner("Generating branch name suggestion...", {
|
|
11184
|
+
tips: LOADING_TIPS
|
|
11185
|
+
});
|
|
11186
|
+
const suggested = await suggestBranchName(branchInput, options.model);
|
|
11187
|
+
if (suggested) {
|
|
11188
|
+
spinner.success("Branch name suggestion ready.");
|
|
11189
|
+
console.log(`
|
|
11190
|
+
${import_picocolors7.default.dim("AI suggestion:")} ${import_picocolors7.default.bold(import_picocolors7.default.cyan(suggested))}`);
|
|
11191
|
+
const action2 = await selectPrompt("What would you like to do with this branch name?", [
|
|
11192
|
+
"Use this suggestion",
|
|
11193
|
+
"Try again with AI",
|
|
11194
|
+
"Enter branch name manually",
|
|
11195
|
+
"Use my original description",
|
|
11196
|
+
"Cancel"
|
|
11197
|
+
]);
|
|
11198
|
+
if (action2 === "Use this suggestion") {
|
|
11199
|
+
branchName = suggested;
|
|
11200
|
+
break;
|
|
11201
|
+
}
|
|
11202
|
+
if (action2 === "Try again with AI") {
|
|
11203
|
+
continue;
|
|
11204
|
+
}
|
|
11205
|
+
if (action2 === "Enter branch name manually") {
|
|
11206
|
+
branchName = (await inputPrompt("Enter branch name", branchInput)).trim();
|
|
11207
|
+
break;
|
|
11208
|
+
}
|
|
11209
|
+
if (action2 === "Use my original description") {
|
|
11210
|
+
branchName = branchInput;
|
|
11211
|
+
break;
|
|
11212
|
+
}
|
|
11213
|
+
return null;
|
|
11214
|
+
}
|
|
11215
|
+
spinner.fail("AI did not return a branch name suggestion.");
|
|
11216
|
+
const action = await selectPrompt("AI could not generate a branch name. What would you like to do?", [
|
|
11217
|
+
"Try again with AI",
|
|
11218
|
+
"Enter branch name manually",
|
|
11219
|
+
"Use my original description",
|
|
11220
|
+
"Cancel"
|
|
11221
|
+
]);
|
|
11222
|
+
if (action === "Try again with AI") {
|
|
11223
|
+
continue;
|
|
11224
|
+
}
|
|
11225
|
+
if (action === "Enter branch name manually") {
|
|
11226
|
+
branchName = (await inputPrompt("Enter branch name", branchInput)).trim();
|
|
11227
|
+
break;
|
|
11228
|
+
}
|
|
11229
|
+
if (action === "Use my original description") {
|
|
11230
|
+
branchName = branchInput;
|
|
11231
|
+
break;
|
|
11232
|
+
}
|
|
11233
|
+
return null;
|
|
11234
|
+
}
|
|
11235
|
+
}
|
|
11236
|
+
}
|
|
11237
|
+
while (true) {
|
|
11238
|
+
if (!branchName) {
|
|
11239
|
+
branchName = (await inputPrompt("Enter branch name", branchInput)).trim();
|
|
11240
|
+
if (!branchName) {
|
|
11241
|
+
const action = await selectPrompt("What would you like to do?", ["Try again", "Cancel"]);
|
|
11242
|
+
if (action === "Cancel")
|
|
11243
|
+
return null;
|
|
11244
|
+
continue;
|
|
11245
|
+
}
|
|
11246
|
+
}
|
|
11247
|
+
if (!hasPrefix(branchName, options.branchPrefixes)) {
|
|
11248
|
+
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors7.default.bold(branchName)}:`, options.branchPrefixes);
|
|
11249
|
+
branchName = formatBranchName(prefix, branchName);
|
|
11250
|
+
}
|
|
11251
|
+
if (!isValidBranchName(branchName)) {
|
|
11252
|
+
warn("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
11253
|
+
branchName = (await inputPrompt("Enter branch name", branchName)).trim();
|
|
11254
|
+
continue;
|
|
11255
|
+
}
|
|
11256
|
+
if (await branchExists(branchName)) {
|
|
11257
|
+
warn(`Branch ${import_picocolors7.default.bold(branchName)} already exists. Choose a different name.`);
|
|
11258
|
+
branchName = (await inputPrompt("Enter branch name", branchName)).trim();
|
|
11259
|
+
continue;
|
|
11260
|
+
}
|
|
11261
|
+
return branchName;
|
|
11262
|
+
}
|
|
11263
|
+
}
|
|
11264
|
+
|
|
11265
|
+
// src/utils/gh.ts
|
|
11266
|
+
import { execFile as execFileCb2 } from "node:child_process";
|
|
11267
|
+
function run2(args) {
|
|
11268
|
+
return new Promise((resolve3) => {
|
|
11269
|
+
execFileCb2("gh", args, (error2, stdout2, stderr) => {
|
|
11270
|
+
resolve3({
|
|
11271
|
+
exitCode: error2 ? error2.code === "ENOENT" ? 127 : error2.status ?? 1 : 0,
|
|
11272
|
+
stdout: stdout2 ?? "",
|
|
11273
|
+
stderr: stderr ?? ""
|
|
11274
|
+
});
|
|
11275
|
+
});
|
|
11276
|
+
});
|
|
11277
|
+
}
|
|
11278
|
+
async function checkGhInstalled() {
|
|
11279
|
+
try {
|
|
11280
|
+
const { exitCode } = await run2(["--version"]);
|
|
11281
|
+
return exitCode === 0;
|
|
11282
|
+
} catch {
|
|
11283
|
+
return false;
|
|
11284
|
+
}
|
|
11285
|
+
}
|
|
11286
|
+
async function checkGhAuth() {
|
|
11287
|
+
try {
|
|
11288
|
+
const { exitCode } = await run2(["auth", "status"]);
|
|
11289
|
+
return exitCode === 0;
|
|
11290
|
+
} catch {
|
|
11291
|
+
return false;
|
|
11292
|
+
}
|
|
11293
|
+
}
|
|
11294
|
+
var SAFE_SLUG = /^[\w.-]+$/;
|
|
11295
|
+
async function checkRepoPermissions(owner, repo) {
|
|
11296
|
+
if (!SAFE_SLUG.test(owner) || !SAFE_SLUG.test(repo))
|
|
11297
|
+
return null;
|
|
11298
|
+
const { exitCode, stdout: stdout2 } = await run2(["api", `repos/${owner}/${repo}`, "--jq", ".permissions"]);
|
|
11299
|
+
if (exitCode !== 0)
|
|
11300
|
+
return null;
|
|
11301
|
+
try {
|
|
11302
|
+
return JSON.parse(stdout2.trim());
|
|
11303
|
+
} catch {
|
|
11304
|
+
return null;
|
|
11305
|
+
}
|
|
11306
|
+
}
|
|
11307
|
+
async function isRepoFork() {
|
|
11308
|
+
const { exitCode, stdout: stdout2 } = await run2(["repo", "view", "--json", "isFork", "-q", ".isFork"]);
|
|
11309
|
+
if (exitCode !== 0)
|
|
11310
|
+
return null;
|
|
11311
|
+
const val = stdout2.trim();
|
|
11312
|
+
if (val === "true")
|
|
11313
|
+
return true;
|
|
11314
|
+
if (val === "false")
|
|
10654
11315
|
return false;
|
|
10655
11316
|
return null;
|
|
10656
11317
|
}
|
|
@@ -10739,53 +11400,6 @@ async function getMergedPRForBranch(headBranch) {
|
|
|
10739
11400
|
}
|
|
10740
11401
|
}
|
|
10741
11402
|
|
|
10742
|
-
// src/utils/spinner.ts
|
|
10743
|
-
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
10744
|
-
var FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
10745
|
-
function createSpinner(text) {
|
|
10746
|
-
let frameIdx = 0;
|
|
10747
|
-
let currentText = text;
|
|
10748
|
-
let stopped = false;
|
|
10749
|
-
const clearLine = () => {
|
|
10750
|
-
process.stderr.write("\r\x1B[K");
|
|
10751
|
-
};
|
|
10752
|
-
const render = () => {
|
|
10753
|
-
if (stopped)
|
|
10754
|
-
return;
|
|
10755
|
-
const frame = import_picocolors6.default.cyan(FRAMES[frameIdx % FRAMES.length]);
|
|
10756
|
-
clearLine();
|
|
10757
|
-
process.stderr.write(`${frame} ${currentText}`);
|
|
10758
|
-
frameIdx++;
|
|
10759
|
-
};
|
|
10760
|
-
const timer = setInterval(render, 80);
|
|
10761
|
-
render();
|
|
10762
|
-
const stop = () => {
|
|
10763
|
-
if (stopped)
|
|
10764
|
-
return;
|
|
10765
|
-
stopped = true;
|
|
10766
|
-
clearInterval(timer);
|
|
10767
|
-
clearLine();
|
|
10768
|
-
};
|
|
10769
|
-
return {
|
|
10770
|
-
update(newText) {
|
|
10771
|
-
currentText = newText;
|
|
10772
|
-
},
|
|
10773
|
-
success(msg) {
|
|
10774
|
-
stop();
|
|
10775
|
-
process.stderr.write(`${import_picocolors6.default.green("✔")} ${msg}
|
|
10776
|
-
`);
|
|
10777
|
-
},
|
|
10778
|
-
fail(msg) {
|
|
10779
|
-
stop();
|
|
10780
|
-
process.stderr.write(`${import_picocolors6.default.red("✖")} ${msg}
|
|
10781
|
-
`);
|
|
10782
|
-
},
|
|
10783
|
-
stop() {
|
|
10784
|
-
stop();
|
|
10785
|
-
}
|
|
10786
|
-
};
|
|
10787
|
-
}
|
|
10788
|
-
|
|
10789
11403
|
// src/commands/clean.ts
|
|
10790
11404
|
async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
10791
11405
|
if (!config)
|
|
@@ -10798,44 +11412,22 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
10798
11412
|
warn("You have uncommitted changes in your working tree.");
|
|
10799
11413
|
}
|
|
10800
11414
|
if (localWork.unpushedCommits > 0) {
|
|
10801
|
-
warn(`You have ${
|
|
11415
|
+
warn(`You have ${import_picocolors8.default.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not pushed.`);
|
|
10802
11416
|
}
|
|
10803
11417
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
10804
11418
|
const DISCARD = "Discard all changes and clean up";
|
|
10805
11419
|
const CANCEL = "Skip this branch";
|
|
10806
|
-
const action = await selectPrompt(`${
|
|
11420
|
+
const action = await selectPrompt(`${import_picocolors8.default.bold(currentBranch)} has local changes. What would you like to do?`, [SAVE_NEW_BRANCH, DISCARD, CANCEL]);
|
|
10807
11421
|
if (action === CANCEL)
|
|
10808
11422
|
return "skipped";
|
|
10809
11423
|
if (action === SAVE_NEW_BRANCH) {
|
|
10810
11424
|
if (!config)
|
|
10811
11425
|
return "skipped";
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
const suggested = await suggestBranchName(description);
|
|
10818
|
-
if (suggested) {
|
|
10819
|
-
spinner.success("Branch name suggestion ready.");
|
|
10820
|
-
console.log(`
|
|
10821
|
-
${import_picocolors7.default.dim("AI suggestion:")} ${import_picocolors7.default.bold(import_picocolors7.default.cyan(suggested))}`);
|
|
10822
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors7.default.bold(suggested)} as your branch name?`);
|
|
10823
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
10824
|
-
} else {
|
|
10825
|
-
spinner.fail("AI did not return a suggestion.");
|
|
10826
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
10827
|
-
}
|
|
10828
|
-
}
|
|
10829
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
10830
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors7.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
10831
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
10832
|
-
}
|
|
10833
|
-
if (!isValidBranchName(newBranchName)) {
|
|
10834
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
10835
|
-
return "skipped";
|
|
10836
|
-
}
|
|
10837
|
-
if (await branchExists(newBranchName)) {
|
|
10838
|
-
error(`Branch ${import_picocolors7.default.bold(newBranchName)} already exists. Choose a different name.`);
|
|
11426
|
+
const newBranchName = await promptForBranchName({
|
|
11427
|
+
branchPrefixes: config.branchPrefixes,
|
|
11428
|
+
useAI: isAIEnabled(config)
|
|
11429
|
+
});
|
|
11430
|
+
if (!newBranchName) {
|
|
10839
11431
|
return "skipped";
|
|
10840
11432
|
}
|
|
10841
11433
|
const renameResult = await renameBranch(currentBranch, newBranchName);
|
|
@@ -10843,7 +11435,7 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
10843
11435
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
10844
11436
|
return "skipped";
|
|
10845
11437
|
}
|
|
10846
|
-
success(`Renamed ${
|
|
11438
|
+
success(`Renamed ${import_picocolors8.default.bold(currentBranch)} → ${import_picocolors8.default.bold(newBranchName)}`);
|
|
10847
11439
|
const syncSource2 = getSyncSource(config);
|
|
10848
11440
|
await fetchRemote(syncSource2.remote);
|
|
10849
11441
|
const savedUpstreamRef = await getUpstreamRef();
|
|
@@ -10851,10 +11443,10 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
10851
11443
|
if (rebaseResult.exitCode !== 0) {
|
|
10852
11444
|
await rebaseAbort();
|
|
10853
11445
|
warn("Rebase had conflicts — aborted to keep the repo in a clean state.");
|
|
10854
|
-
info(`Your work is saved on ${
|
|
10855
|
-
info(` ${
|
|
11446
|
+
info(`Your work is saved on ${import_picocolors8.default.bold(newBranchName)}. After cleanup, rebase manually:`, "");
|
|
11447
|
+
info(` ${import_picocolors8.default.bold(`git checkout ${newBranchName} && git rebase ${syncSource2.ref}`)}`, "");
|
|
10856
11448
|
} else {
|
|
10857
|
-
success(`Rebased ${
|
|
11449
|
+
success(`Rebased ${import_picocolors8.default.bold(newBranchName)} onto ${import_picocolors8.default.bold(syncSource2.ref)}.`);
|
|
10858
11450
|
}
|
|
10859
11451
|
const coResult2 = await checkoutBranch(baseBranch);
|
|
10860
11452
|
if (coResult2.exitCode !== 0) {
|
|
@@ -10862,12 +11454,12 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
10862
11454
|
return "saved";
|
|
10863
11455
|
}
|
|
10864
11456
|
await updateLocalBranch(baseBranch, syncSource2.ref);
|
|
10865
|
-
success(`Synced ${
|
|
11457
|
+
success(`Synced ${import_picocolors8.default.bold(baseBranch)} with ${import_picocolors8.default.bold(syncSource2.ref)}.`);
|
|
10866
11458
|
return "saved";
|
|
10867
11459
|
}
|
|
10868
11460
|
}
|
|
10869
11461
|
const syncSource = getSyncSource(config);
|
|
10870
|
-
info(`Switching to ${
|
|
11462
|
+
info(`Switching to ${import_picocolors8.default.bold(baseBranch)} and syncing...`);
|
|
10871
11463
|
await fetchRemote(syncSource.remote);
|
|
10872
11464
|
await resetHard("HEAD");
|
|
10873
11465
|
const coResult = await checkoutBranch(baseBranch);
|
|
@@ -10876,7 +11468,7 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
10876
11468
|
return "skipped";
|
|
10877
11469
|
}
|
|
10878
11470
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
10879
|
-
success(`Synced ${
|
|
11471
|
+
success(`Synced ${import_picocolors8.default.bold(baseBranch)} with ${import_picocolors8.default.bold(syncSource.ref)}.`);
|
|
10880
11472
|
return "switched";
|
|
10881
11473
|
}
|
|
10882
11474
|
var clean_default = defineCommand({
|
|
@@ -10900,13 +11492,13 @@ var clean_default = defineCommand({
|
|
|
10900
11492
|
await assertCleanGitState("cleaning");
|
|
10901
11493
|
const config = readConfig();
|
|
10902
11494
|
if (!config) {
|
|
10903
|
-
error("No
|
|
11495
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
10904
11496
|
process.exit(1);
|
|
10905
11497
|
}
|
|
10906
11498
|
const { origin } = config;
|
|
10907
11499
|
const baseBranch = getBaseBranch(config);
|
|
10908
11500
|
let currentBranch = await getCurrentBranch();
|
|
10909
|
-
|
|
11501
|
+
projectHeading("clean", "\uD83E\uDDF9");
|
|
10910
11502
|
info(`Pruning ${origin} remote refs...`);
|
|
10911
11503
|
const pruneResult = await pruneRemote(origin);
|
|
10912
11504
|
if (pruneResult.exitCode === 0) {
|
|
@@ -10926,21 +11518,21 @@ var clean_default = defineCommand({
|
|
|
10926
11518
|
if (ghInstalled && ghAuthed) {
|
|
10927
11519
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
10928
11520
|
if (mergedPR) {
|
|
10929
|
-
warn(`PR #${mergedPR.number} (${
|
|
10930
|
-
info(`Link: ${
|
|
11521
|
+
warn(`PR #${mergedPR.number} (${import_picocolors8.default.bold(mergedPR.title)}) has already been merged.`);
|
|
11522
|
+
info(`Link: ${import_picocolors8.default.underline(mergedPR.url)}`, "");
|
|
10931
11523
|
goneCandidates.push(currentBranch);
|
|
10932
11524
|
}
|
|
10933
11525
|
}
|
|
10934
11526
|
}
|
|
10935
11527
|
if (mergedCandidates.length > 0) {
|
|
10936
11528
|
console.log(`
|
|
10937
|
-
${
|
|
11529
|
+
${import_picocolors8.default.bold("Merged branches to delete:")}`);
|
|
10938
11530
|
for (const b2 of mergedCandidates) {
|
|
10939
|
-
const marker = b2 === currentBranch ?
|
|
10940
|
-
console.log(` ${
|
|
11531
|
+
const marker = b2 === currentBranch ? import_picocolors8.default.yellow(" (current)") : "";
|
|
11532
|
+
console.log(` ${import_picocolors8.default.dim("•")} ${b2}${marker}`);
|
|
10941
11533
|
}
|
|
10942
11534
|
console.log();
|
|
10943
|
-
const ok = args.yes || await confirmPrompt(`Delete ${
|
|
11535
|
+
const ok = args.yes || await confirmPrompt(`Delete ${import_picocolors8.default.bold(String(mergedCandidates.length))} merged branch${mergedCandidates.length !== 1 ? "es" : ""}?`);
|
|
10944
11536
|
if (ok) {
|
|
10945
11537
|
for (const branch of mergedCandidates) {
|
|
10946
11538
|
if (branch === currentBranch) {
|
|
@@ -10957,7 +11549,7 @@ ${import_picocolors7.default.bold("Merged branches to delete:")}`);
|
|
|
10957
11549
|
}
|
|
10958
11550
|
const result = await deleteBranch(branch);
|
|
10959
11551
|
if (result.exitCode === 0) {
|
|
10960
|
-
success(` Deleted ${
|
|
11552
|
+
success(` Deleted ${import_picocolors8.default.bold(branch)}`);
|
|
10961
11553
|
} else {
|
|
10962
11554
|
warn(` Failed to delete ${branch}: ${result.stderr.trim()}`);
|
|
10963
11555
|
}
|
|
@@ -10968,13 +11560,13 @@ ${import_picocolors7.default.bold("Merged branches to delete:")}`);
|
|
|
10968
11560
|
}
|
|
10969
11561
|
if (goneCandidates.length > 0) {
|
|
10970
11562
|
console.log(`
|
|
10971
|
-
${
|
|
11563
|
+
${import_picocolors8.default.bold("Stale branches (remote deleted, likely squash-merged):")}`);
|
|
10972
11564
|
for (const b2 of goneCandidates) {
|
|
10973
|
-
const marker = b2 === currentBranch ?
|
|
10974
|
-
console.log(` ${
|
|
11565
|
+
const marker = b2 === currentBranch ? import_picocolors8.default.yellow(" (current)") : "";
|
|
11566
|
+
console.log(` ${import_picocolors8.default.dim("•")} ${b2}${marker}`);
|
|
10975
11567
|
}
|
|
10976
11568
|
console.log();
|
|
10977
|
-
const ok = args.yes || await confirmPrompt(`Delete ${
|
|
11569
|
+
const ok = args.yes || await confirmPrompt(`Delete ${import_picocolors8.default.bold(String(goneCandidates.length))} stale branch${goneCandidates.length !== 1 ? "es" : ""}?`);
|
|
10978
11570
|
if (ok) {
|
|
10979
11571
|
for (const branch of goneCandidates) {
|
|
10980
11572
|
if (branch === currentBranch) {
|
|
@@ -10991,7 +11583,7 @@ ${import_picocolors7.default.bold("Stale branches (remote deleted, likely squash
|
|
|
10991
11583
|
}
|
|
10992
11584
|
const result = await forceDeleteBranch(branch);
|
|
10993
11585
|
if (result.exitCode === 0) {
|
|
10994
|
-
success(` Deleted ${
|
|
11586
|
+
success(` Deleted ${import_picocolors8.default.bold(branch)}`);
|
|
10995
11587
|
} else {
|
|
10996
11588
|
warn(` Failed to delete ${branch}: ${result.stderr.trim()}`);
|
|
10997
11589
|
}
|
|
@@ -11006,13 +11598,13 @@ ${import_picocolors7.default.bold("Stale branches (remote deleted, likely squash
|
|
|
11006
11598
|
const finalBranch = await getCurrentBranch();
|
|
11007
11599
|
if (finalBranch && protectedBranches.has(finalBranch)) {
|
|
11008
11600
|
console.log();
|
|
11009
|
-
info(`You're on ${
|
|
11601
|
+
info(`You're on ${import_picocolors8.default.bold(finalBranch)}. Run ${import_picocolors8.default.bold("contrib start")} to begin a new feature.`);
|
|
11010
11602
|
}
|
|
11011
11603
|
}
|
|
11012
11604
|
});
|
|
11013
11605
|
|
|
11014
11606
|
// src/commands/commit.ts
|
|
11015
|
-
var
|
|
11607
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
11016
11608
|
|
|
11017
11609
|
// src/utils/convention.ts
|
|
11018
11610
|
var CLEAN_COMMIT_PATTERN = /^(📦|🔧|🗑\uFE0F?|🔒|⚙\uFE0F?|☕|🧪|📖|🚀) (new|update|remove|security|setup|chore|test|docs|release)(!?)( \([a-zA-Z0-9][a-zA-Z0-9-]*\))?: .{1,72}$/u;
|
|
@@ -11066,6 +11658,9 @@ function getValidationError(convention) {
|
|
|
11066
11658
|
}
|
|
11067
11659
|
|
|
11068
11660
|
// src/commands/commit.ts
|
|
11661
|
+
function isEmptyGroupCommitResult(detail) {
|
|
11662
|
+
return /no changes added to commit|nothing to commit/i.test(detail);
|
|
11663
|
+
}
|
|
11069
11664
|
var commit_default = defineCommand({
|
|
11070
11665
|
meta: {
|
|
11071
11666
|
name: "commit",
|
|
@@ -11095,11 +11690,16 @@ var commit_default = defineCommand({
|
|
|
11095
11690
|
await assertCleanGitState("committing");
|
|
11096
11691
|
const config = readConfig();
|
|
11097
11692
|
if (!config) {
|
|
11098
|
-
error("No
|
|
11693
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
11099
11694
|
process.exit(1);
|
|
11100
11695
|
}
|
|
11101
|
-
|
|
11696
|
+
projectHeading("commit", "\uD83D\uDCBE");
|
|
11697
|
+
const aiEnabled = isAIEnabled(config, args["no-ai"]);
|
|
11102
11698
|
if (args.group) {
|
|
11699
|
+
if (!aiEnabled) {
|
|
11700
|
+
error("AI group commit is unavailable because AI is disabled. Re-run without --group or enable AI in your repo config.");
|
|
11701
|
+
process.exit(1);
|
|
11702
|
+
}
|
|
11103
11703
|
await runGroupCommit(args.model, config);
|
|
11104
11704
|
return;
|
|
11105
11705
|
}
|
|
@@ -11111,9 +11711,9 @@ var commit_default = defineCommand({
|
|
|
11111
11711
|
process.exit(1);
|
|
11112
11712
|
}
|
|
11113
11713
|
console.log(`
|
|
11114
|
-
${
|
|
11714
|
+
${import_picocolors9.default.bold("Changed files:")}`);
|
|
11115
11715
|
for (const f3 of changedFiles) {
|
|
11116
|
-
console.log(` ${
|
|
11716
|
+
console.log(` ${import_picocolors9.default.dim("•")} ${f3}`);
|
|
11117
11717
|
}
|
|
11118
11718
|
const stageAction = await selectPrompt("No staged changes. How would you like to stage?", [
|
|
11119
11719
|
"Stage all changes",
|
|
@@ -11155,8 +11755,8 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11155
11755
|
const dirs = new Set(stagedFiles.map((f3) => f3.split("/")[0]));
|
|
11156
11756
|
if (dirs.size > 1) {
|
|
11157
11757
|
console.log();
|
|
11158
|
-
warn(`You're staging ${
|
|
11159
|
-
info(
|
|
11758
|
+
warn(`You're staging ${import_picocolors9.default.bold(String(stagedFiles.length))} files across ${import_picocolors9.default.bold(String(dirs.size))} directories in a single commit.`);
|
|
11759
|
+
info(import_picocolors9.default.dim("Large commits mixing different topics make history harder to read and bisect. " + "For cleaner history, consider splitting into atomic commits."));
|
|
11160
11760
|
const choice = await selectPrompt("How would you like to proceed?", [
|
|
11161
11761
|
"Continue as single commit",
|
|
11162
11762
|
"Switch to group mode (AI splits into atomic commits)",
|
|
@@ -11172,20 +11772,22 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11172
11772
|
}
|
|
11173
11773
|
}
|
|
11174
11774
|
let commitMessage = null;
|
|
11175
|
-
const useAI =
|
|
11775
|
+
const useAI = aiEnabled;
|
|
11176
11776
|
if (useAI) {
|
|
11177
|
-
const [copilotError, diff] = await Promise.all([
|
|
11777
|
+
const [copilotError, diff] = await Promise.all([checkCopilotAvailable2(), getStagedDiff()]);
|
|
11178
11778
|
if (copilotError) {
|
|
11179
11779
|
warn(`AI unavailable: ${copilotError}`);
|
|
11180
11780
|
warn("Falling back to manual commit message entry.");
|
|
11181
11781
|
} else {
|
|
11182
11782
|
const spinnerMsg = stagedFiles.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD ? `Generating commit message with AI (${stagedFiles.length} files — using optimized batching)...` : "Generating commit message with AI...";
|
|
11183
|
-
const spinner = createSpinner(spinnerMsg
|
|
11783
|
+
const spinner = createSpinner(spinnerMsg, {
|
|
11784
|
+
tips: LOADING_TIPS
|
|
11785
|
+
});
|
|
11184
11786
|
commitMessage = await generateCommitMessage(diff, stagedFiles, args.model, config.commitConvention);
|
|
11185
11787
|
if (commitMessage) {
|
|
11186
11788
|
spinner.success("AI commit message generated.");
|
|
11187
11789
|
console.log(`
|
|
11188
|
-
${
|
|
11790
|
+
${import_picocolors9.default.dim("AI suggestion:")} ${import_picocolors9.default.bold(import_picocolors9.default.cyan(commitMessage))}`);
|
|
11189
11791
|
} else {
|
|
11190
11792
|
spinner.fail("AI did not return a commit message.");
|
|
11191
11793
|
warn("Falling back to manual entry.");
|
|
@@ -11205,13 +11807,15 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11205
11807
|
} else if (action === "Edit this message") {
|
|
11206
11808
|
finalMessage = await inputPrompt("Edit commit message", commitMessage);
|
|
11207
11809
|
} else if (action === "Regenerate") {
|
|
11208
|
-
const spinner = createSpinner("Regenerating commit message..."
|
|
11810
|
+
const spinner = createSpinner("Regenerating commit message...", {
|
|
11811
|
+
tips: LOADING_TIPS
|
|
11812
|
+
});
|
|
11209
11813
|
const diff = await getStagedDiff();
|
|
11210
11814
|
const regen = await generateCommitMessage(diff, stagedFiles, args.model, config.commitConvention);
|
|
11211
11815
|
if (regen) {
|
|
11212
11816
|
spinner.success("Commit message regenerated.");
|
|
11213
11817
|
console.log(`
|
|
11214
|
-
${
|
|
11818
|
+
${import_picocolors9.default.dim("AI suggestion:")} ${import_picocolors9.default.bold(import_picocolors9.default.cyan(regen))}`);
|
|
11215
11819
|
const ok = await confirmPrompt("Use this message?");
|
|
11216
11820
|
finalMessage = ok ? regen : await inputPrompt("Enter commit message manually");
|
|
11217
11821
|
} else {
|
|
@@ -11226,7 +11830,7 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11226
11830
|
if (convention2 !== "none") {
|
|
11227
11831
|
console.log();
|
|
11228
11832
|
for (const hint of CONVENTION_FORMAT_HINTS[convention2]) {
|
|
11229
|
-
console.log(
|
|
11833
|
+
console.log(import_picocolors9.default.dim(hint));
|
|
11230
11834
|
}
|
|
11231
11835
|
console.log();
|
|
11232
11836
|
}
|
|
@@ -11250,21 +11854,12 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11250
11854
|
error(`Failed to commit: ${result.stderr}`);
|
|
11251
11855
|
process.exit(1);
|
|
11252
11856
|
}
|
|
11253
|
-
success(`Committed: ${
|
|
11857
|
+
success(`Committed: ${import_picocolors9.default.bold(finalMessage)}`);
|
|
11254
11858
|
}
|
|
11255
11859
|
});
|
|
11256
|
-
function getFallbackGroupMessage(convention) {
|
|
11257
|
-
if (convention === "conventional") {
|
|
11258
|
-
return "chore: commit remaining changes";
|
|
11259
|
-
}
|
|
11260
|
-
if (convention === "clean-commit") {
|
|
11261
|
-
return "☕ chore: commit remaining changes";
|
|
11262
|
-
}
|
|
11263
|
-
return "commit remaining changes";
|
|
11264
|
-
}
|
|
11265
11860
|
async function runGroupCommit(model, config) {
|
|
11266
11861
|
const [copilotError, changedFiles] = await Promise.all([
|
|
11267
|
-
|
|
11862
|
+
checkCopilotAvailable2(),
|
|
11268
11863
|
getChangedFiles()
|
|
11269
11864
|
]);
|
|
11270
11865
|
if (copilotError) {
|
|
@@ -11276,11 +11871,13 @@ async function runGroupCommit(model, config) {
|
|
|
11276
11871
|
process.exit(1);
|
|
11277
11872
|
}
|
|
11278
11873
|
console.log(`
|
|
11279
|
-
${
|
|
11874
|
+
${import_picocolors9.default.bold("Changed files:")}`);
|
|
11280
11875
|
for (const f3 of changedFiles) {
|
|
11281
|
-
console.log(` ${
|
|
11876
|
+
console.log(` ${import_picocolors9.default.dim("•")} ${f3}`);
|
|
11282
11877
|
}
|
|
11283
|
-
const spinner = createSpinner(changedFiles.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD ? `Asking AI to group ${changedFiles.length} file(s) into logical commits (using optimized batching)...` : `Asking AI to group ${changedFiles.length} file(s) into logical commits
|
|
11878
|
+
const spinner = createSpinner(changedFiles.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD ? `Asking AI to group ${changedFiles.length} file(s) into logical commits (using optimized batching)...` : `Asking AI to group ${changedFiles.length} file(s) into logical commits...`, {
|
|
11879
|
+
tips: LOADING_TIPS
|
|
11880
|
+
});
|
|
11284
11881
|
const diffs = await getFullDiffForFiles(changedFiles);
|
|
11285
11882
|
if (!diffs.trim()) {
|
|
11286
11883
|
spinner.stop();
|
|
@@ -11288,7 +11885,7 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11288
11885
|
}
|
|
11289
11886
|
let groups;
|
|
11290
11887
|
try {
|
|
11291
|
-
groups = await generateCommitGroups(changedFiles, diffs, model, config.commitConvention);
|
|
11888
|
+
groups = await generateCommitGroups(changedFiles, diffs, model, config.commitConvention, (message) => spinner.update(message));
|
|
11292
11889
|
spinner.success(`AI generated ${groups.length} commit group(s).`);
|
|
11293
11890
|
} catch (err) {
|
|
11294
11891
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -11308,14 +11905,10 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11308
11905
|
}
|
|
11309
11906
|
let validGroups = normalized.groups;
|
|
11310
11907
|
if (normalized.unassignedFiles.length > 0) {
|
|
11311
|
-
warn(`AI left ${normalized.unassignedFiles.length} file(s) ungrouped: ${normalized.unassignedFiles.join(", ")}.
|
|
11312
|
-
const fallbackMessage = await regenerateGroupMessage(normalized.unassignedFiles, diffs, model, config.commitConvention) ?? getFallbackGroupMessage(config.commitConvention);
|
|
11908
|
+
warn(`AI left ${normalized.unassignedFiles.length} file(s) ungrouped: ${normalized.unassignedFiles.join(", ")}. Auto-resolving recovery groups.`);
|
|
11313
11909
|
validGroups = [
|
|
11314
11910
|
...validGroups,
|
|
11315
|
-
|
|
11316
|
-
files: normalized.unassignedFiles,
|
|
11317
|
-
message: fallbackMessage
|
|
11318
|
-
}
|
|
11911
|
+
...createRecoveryCommitGroups(normalized.unassignedFiles, config.commitConvention)
|
|
11319
11912
|
];
|
|
11320
11913
|
}
|
|
11321
11914
|
if (validGroups.length === 0) {
|
|
@@ -11326,13 +11919,13 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11326
11919
|
let commitAll = false;
|
|
11327
11920
|
while (!proceedToCommit) {
|
|
11328
11921
|
console.log(`
|
|
11329
|
-
${
|
|
11922
|
+
${import_picocolors9.default.bold(`AI suggested ${validGroups.length} commit group(s):`)}
|
|
11330
11923
|
`);
|
|
11331
11924
|
for (let i2 = 0;i2 < validGroups.length; i2++) {
|
|
11332
11925
|
const g3 = validGroups[i2];
|
|
11333
|
-
console.log(` ${
|
|
11926
|
+
console.log(` ${import_picocolors9.default.cyan(`Group ${i2 + 1}:`)} ${import_picocolors9.default.bold(g3.message)}`);
|
|
11334
11927
|
for (const f3 of g3.files) {
|
|
11335
|
-
console.log(` ${
|
|
11928
|
+
console.log(` ${import_picocolors9.default.dim("•")} ${f3}`);
|
|
11336
11929
|
}
|
|
11337
11930
|
console.log();
|
|
11338
11931
|
}
|
|
@@ -11347,7 +11940,9 @@ ${import_picocolors8.default.bold(`AI suggested ${validGroups.length} commit gro
|
|
|
11347
11940
|
process.exit(0);
|
|
11348
11941
|
}
|
|
11349
11942
|
if (summaryAction === "Regenerate all messages") {
|
|
11350
|
-
const regenSpinner = createSpinner("Regenerating all commit messages..."
|
|
11943
|
+
const regenSpinner = createSpinner("Regenerating all commit messages...", {
|
|
11944
|
+
tips: LOADING_TIPS
|
|
11945
|
+
});
|
|
11351
11946
|
try {
|
|
11352
11947
|
validGroups = await regenerateAllGroupMessages(validGroups, diffs, model, config.commitConvention);
|
|
11353
11948
|
regenSpinner.success("All commit messages regenerated.");
|
|
@@ -11378,24 +11973,34 @@ ${import_picocolors8.default.bold(`AI suggested ${validGroups.length} commit gro
|
|
|
11378
11973
|
error(`Failed to stage group ${i2 + 1}: ${stageResult.stderr}`);
|
|
11379
11974
|
continue;
|
|
11380
11975
|
}
|
|
11976
|
+
const stagedFiles = new Set(await getStagedFiles());
|
|
11977
|
+
const stagedGroupFiles = stageableFiles.filter((file) => stagedFiles.has(file));
|
|
11978
|
+
if (stagedGroupFiles.length === 0) {
|
|
11979
|
+
warn(`Skipped group ${i2 + 1}: no files were staged for commit.`);
|
|
11980
|
+
continue;
|
|
11981
|
+
}
|
|
11381
11982
|
const commitResult = await commitWithMessage(group.message);
|
|
11382
11983
|
if (commitResult.exitCode !== 0) {
|
|
11383
11984
|
const detail = (commitResult.stderr || commitResult.stdout).trim();
|
|
11985
|
+
if (isEmptyGroupCommitResult(detail)) {
|
|
11986
|
+
warn(`Skipped group ${i2 + 1}: nothing remained to commit.`);
|
|
11987
|
+
continue;
|
|
11988
|
+
}
|
|
11384
11989
|
error(`Failed to commit group ${i2 + 1}: ${detail}`);
|
|
11385
11990
|
await unstageFiles(stageableFiles);
|
|
11386
11991
|
continue;
|
|
11387
11992
|
}
|
|
11388
11993
|
committed++;
|
|
11389
|
-
success(`Committed group ${i2 + 1}: ${
|
|
11994
|
+
success(`Committed group ${i2 + 1}: ${import_picocolors9.default.bold(group.message)}`);
|
|
11390
11995
|
}
|
|
11391
11996
|
} else {
|
|
11392
11997
|
for (let i2 = 0;i2 < validGroups.length; i2++) {
|
|
11393
11998
|
const group = validGroups[i2];
|
|
11394
|
-
console.log(
|
|
11999
|
+
console.log(import_picocolors9.default.bold(`
|
|
11395
12000
|
── Group ${i2 + 1}/${validGroups.length} ──`));
|
|
11396
|
-
console.log(` ${
|
|
12001
|
+
console.log(` ${import_picocolors9.default.cyan(group.message)}`);
|
|
11397
12002
|
for (const f3 of group.files) {
|
|
11398
|
-
console.log(` ${
|
|
12003
|
+
console.log(` ${import_picocolors9.default.dim("•")} ${f3}`);
|
|
11399
12004
|
}
|
|
11400
12005
|
let message = group.message;
|
|
11401
12006
|
let actionDone = false;
|
|
@@ -11412,12 +12017,14 @@ ${import_picocolors8.default.bold(`AI suggested ${validGroups.length} commit gro
|
|
|
11412
12017
|
continue;
|
|
11413
12018
|
}
|
|
11414
12019
|
if (action === "Regenerate message") {
|
|
11415
|
-
const regenSpinner = createSpinner("Regenerating commit message for this group..."
|
|
12020
|
+
const regenSpinner = createSpinner("Regenerating commit message for this group...", {
|
|
12021
|
+
tips: LOADING_TIPS
|
|
12022
|
+
});
|
|
11416
12023
|
const newMsg = await regenerateGroupMessage(group.files, diffs, model, config.commitConvention);
|
|
11417
12024
|
if (newMsg) {
|
|
11418
12025
|
message = newMsg;
|
|
11419
12026
|
group.message = newMsg;
|
|
11420
|
-
regenSpinner.success(`New message: ${
|
|
12027
|
+
regenSpinner.success(`New message: ${import_picocolors9.default.bold(message)}`);
|
|
11421
12028
|
} else {
|
|
11422
12029
|
regenSpinner.fail("AI could not generate a new message. Keeping current one.");
|
|
11423
12030
|
}
|
|
@@ -11459,16 +12066,28 @@ ${import_picocolors8.default.bold(`AI suggested ${validGroups.length} commit gro
|
|
|
11459
12066
|
actionDone = true;
|
|
11460
12067
|
continue;
|
|
11461
12068
|
}
|
|
12069
|
+
const stagedFiles = new Set(await getStagedFiles());
|
|
12070
|
+
const stagedGroupFiles = stageableFiles.filter((file) => stagedFiles.has(file));
|
|
12071
|
+
if (stagedGroupFiles.length === 0) {
|
|
12072
|
+
warn(`Skipped group ${i2 + 1}: no files were staged for commit.`);
|
|
12073
|
+
actionDone = true;
|
|
12074
|
+
continue;
|
|
12075
|
+
}
|
|
11462
12076
|
const commitResult = await commitWithMessage(message);
|
|
11463
12077
|
if (commitResult.exitCode !== 0) {
|
|
11464
12078
|
const detail = (commitResult.stderr || commitResult.stdout).trim();
|
|
12079
|
+
if (isEmptyGroupCommitResult(detail)) {
|
|
12080
|
+
warn(`Skipped group ${i2 + 1}: nothing remained to commit.`);
|
|
12081
|
+
actionDone = true;
|
|
12082
|
+
continue;
|
|
12083
|
+
}
|
|
11465
12084
|
error(`Failed to commit group ${i2 + 1}: ${detail}`);
|
|
11466
12085
|
await unstageFiles(stageableFiles);
|
|
11467
12086
|
actionDone = true;
|
|
11468
12087
|
continue;
|
|
11469
12088
|
}
|
|
11470
12089
|
committed++;
|
|
11471
|
-
success(`Committed group ${i2 + 1}: ${
|
|
12090
|
+
success(`Committed group ${i2 + 1}: ${import_picocolors9.default.bold(message)}`);
|
|
11472
12091
|
actionDone = true;
|
|
11473
12092
|
}
|
|
11474
12093
|
}
|
|
@@ -11484,11 +12103,11 @@ ${import_picocolors8.default.bold(`AI suggested ${validGroups.length} commit gro
|
|
|
11484
12103
|
|
|
11485
12104
|
// src/commands/doctor.ts
|
|
11486
12105
|
import { execFile as execFileCb3 } from "node:child_process";
|
|
11487
|
-
var
|
|
12106
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
11488
12107
|
// package.json
|
|
11489
12108
|
var package_default = {
|
|
11490
12109
|
name: "contribute-now",
|
|
11491
|
-
version: "0.6.2-dev.
|
|
12110
|
+
version: "0.6.2-dev.1320af4",
|
|
11492
12111
|
description: "Developer CLI that automates git workflows — branching, syncing, committing, and PRs — with multi-workflow and commit convention support.",
|
|
11493
12112
|
type: "module",
|
|
11494
12113
|
bin: {
|
|
@@ -11579,16 +12198,16 @@ async function getRepoInfoFromRemote(remote = "origin") {
|
|
|
11579
12198
|
}
|
|
11580
12199
|
|
|
11581
12200
|
// src/commands/doctor.ts
|
|
11582
|
-
var PASS = ` ${
|
|
11583
|
-
var FAIL = ` ${
|
|
11584
|
-
var WARN = ` ${
|
|
12201
|
+
var PASS = ` ${import_picocolors10.default.green("✔")} `;
|
|
12202
|
+
var FAIL = ` ${import_picocolors10.default.red("✗")} `;
|
|
12203
|
+
var WARN = ` ${import_picocolors10.default.yellow("⚠")} `;
|
|
11585
12204
|
function printReport(report) {
|
|
11586
12205
|
for (const section of report.sections) {
|
|
11587
12206
|
console.log(`
|
|
11588
|
-
${
|
|
12207
|
+
${import_picocolors10.default.bold(import_picocolors10.default.underline(section.title))}`);
|
|
11589
12208
|
for (const check of section.checks) {
|
|
11590
12209
|
const prefix = check.ok ? check.warning ? WARN : PASS : FAIL;
|
|
11591
|
-
const text = check.detail ? `${check.label} ${
|
|
12210
|
+
const text = check.detail ? `${check.label} ${import_picocolors10.default.dim(`— ${check.detail}`)}` : check.label;
|
|
11592
12211
|
console.log(`${prefix}${text}`);
|
|
11593
12212
|
}
|
|
11594
12213
|
}
|
|
@@ -11606,9 +12225,9 @@ function toJson(report) {
|
|
|
11606
12225
|
})), null, 2);
|
|
11607
12226
|
}
|
|
11608
12227
|
function runCmd(cmd, args) {
|
|
11609
|
-
return new Promise((
|
|
12228
|
+
return new Promise((resolve3) => {
|
|
11610
12229
|
execFileCb3(cmd, args, (error2, stdout2) => {
|
|
11611
|
-
|
|
12230
|
+
resolve3({
|
|
11612
12231
|
ok: !error2,
|
|
11613
12232
|
stdout: (stdout2 ?? "").trim()
|
|
11614
12233
|
});
|
|
@@ -11670,18 +12289,32 @@ async function configSection() {
|
|
|
11670
12289
|
const exists = configExists();
|
|
11671
12290
|
if (!exists) {
|
|
11672
12291
|
checks.push({
|
|
11673
|
-
label: "
|
|
12292
|
+
label: "Repo config not found",
|
|
11674
12293
|
ok: false,
|
|
11675
|
-
detail: "run `contrib setup` to create
|
|
12294
|
+
detail: "run `contrib setup` to create local config for this clone"
|
|
11676
12295
|
});
|
|
11677
12296
|
return { title: "Config", checks };
|
|
11678
12297
|
}
|
|
11679
12298
|
const config = readConfig();
|
|
11680
12299
|
if (!config) {
|
|
11681
|
-
checks.push({ label: "
|
|
12300
|
+
checks.push({ label: "Repo config found but invalid", ok: false });
|
|
11682
12301
|
return { title: "Config", checks };
|
|
11683
12302
|
}
|
|
11684
|
-
|
|
12303
|
+
const configSource = getConfigSource();
|
|
12304
|
+
const hasBothConfigSources = hasLegacyConfig() && hasLocalConfig();
|
|
12305
|
+
checks.push({
|
|
12306
|
+
label: `${configSource === "local" ? "Local Git config" : "Legacy repo config"} found and valid`,
|
|
12307
|
+
ok: true,
|
|
12308
|
+
detail: getConfigLocationLabel()
|
|
12309
|
+
});
|
|
12310
|
+
if (hasBothConfigSources) {
|
|
12311
|
+
checks.push({
|
|
12312
|
+
label: "Both legacy and local config files exist",
|
|
12313
|
+
ok: true,
|
|
12314
|
+
warning: true,
|
|
12315
|
+
detail: "legacy .contributerc.json currently takes precedence"
|
|
12316
|
+
});
|
|
12317
|
+
}
|
|
11685
12318
|
const desc = WORKFLOW_DESCRIPTIONS[config.workflow] ?? config.workflow;
|
|
11686
12319
|
checks.push({
|
|
11687
12320
|
label: `Workflow: ${config.workflow}`,
|
|
@@ -11696,13 +12329,21 @@ async function configSection() {
|
|
|
11696
12329
|
ok: !!config.devBranch
|
|
11697
12330
|
});
|
|
11698
12331
|
}
|
|
11699
|
-
|
|
11700
|
-
|
|
11701
|
-
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11705
|
-
|
|
12332
|
+
if (configSource === "legacy") {
|
|
12333
|
+
const ignored = isGitignored();
|
|
12334
|
+
checks.push({
|
|
12335
|
+
label: ignored ? ".contributerc.json in .gitignore" : ".contributerc.json NOT in .gitignore",
|
|
12336
|
+
ok: true,
|
|
12337
|
+
warning: !ignored,
|
|
12338
|
+
detail: ignored ? undefined : "consider adding it to .gitignore"
|
|
12339
|
+
});
|
|
12340
|
+
} else {
|
|
12341
|
+
checks.push({
|
|
12342
|
+
label: "Config stored in local Git metadata",
|
|
12343
|
+
ok: true,
|
|
12344
|
+
detail: "does not modify tracked files or require .gitignore changes"
|
|
12345
|
+
});
|
|
12346
|
+
}
|
|
11706
12347
|
return { title: "Config", checks };
|
|
11707
12348
|
}
|
|
11708
12349
|
async function gitSection() {
|
|
@@ -11851,20 +12492,20 @@ var doctor_default = defineCommand({
|
|
|
11851
12492
|
console.log(toJson(report));
|
|
11852
12493
|
return;
|
|
11853
12494
|
}
|
|
11854
|
-
|
|
12495
|
+
projectHeading("doctor", "\uD83E\uDE7A");
|
|
11855
12496
|
printReport(report);
|
|
11856
12497
|
const total = report.sections.flatMap((s2) => s2.checks);
|
|
11857
12498
|
const failures = total.filter((c3) => !c3.ok);
|
|
11858
12499
|
const warnings = total.filter((c3) => c3.ok && c3.warning);
|
|
11859
12500
|
if (failures.length === 0 && warnings.length === 0) {
|
|
11860
|
-
console.log(` ${
|
|
12501
|
+
console.log(` ${import_picocolors10.default.green("All checks passed!")} No issues detected.
|
|
11861
12502
|
`);
|
|
11862
12503
|
} else {
|
|
11863
12504
|
if (failures.length > 0) {
|
|
11864
|
-
console.log(` ${
|
|
12505
|
+
console.log(` ${import_picocolors10.default.red(`${failures.length} issue${failures.length !== 1 ? "s" : ""} found.`)}`);
|
|
11865
12506
|
}
|
|
11866
12507
|
if (warnings.length > 0) {
|
|
11867
|
-
console.log(` ${
|
|
12508
|
+
console.log(` ${import_picocolors10.default.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}.`)}`);
|
|
11868
12509
|
}
|
|
11869
12510
|
console.log();
|
|
11870
12511
|
}
|
|
@@ -11872,9 +12513,9 @@ var doctor_default = defineCommand({
|
|
|
11872
12513
|
});
|
|
11873
12514
|
|
|
11874
12515
|
// src/commands/hook.ts
|
|
11875
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
12516
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
11876
12517
|
import { join as join4 } from "node:path";
|
|
11877
|
-
var
|
|
12518
|
+
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
11878
12519
|
var HOOK_MARKER = "# managed by contribute-now";
|
|
11879
12520
|
function getHooksDir(cwd = process.cwd()) {
|
|
11880
12521
|
return join4(cwd, ".git", "hooks");
|
|
@@ -11942,10 +12583,10 @@ var hook_default = defineCommand({
|
|
|
11942
12583
|
}
|
|
11943
12584
|
});
|
|
11944
12585
|
async function installHook() {
|
|
11945
|
-
|
|
12586
|
+
projectHeading("hook install", "\uD83E\uDE9D");
|
|
11946
12587
|
const config = readConfig();
|
|
11947
12588
|
if (!config) {
|
|
11948
|
-
error("No
|
|
12589
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
11949
12590
|
process.exit(1);
|
|
11950
12591
|
}
|
|
11951
12592
|
if (config.commitConvention === "none") {
|
|
@@ -11966,16 +12607,16 @@ async function installHook() {
|
|
|
11966
12607
|
info("Updating existing contribute-now hook...");
|
|
11967
12608
|
}
|
|
11968
12609
|
if (!existsSync4(hooksDir)) {
|
|
11969
|
-
|
|
12610
|
+
mkdirSync3(hooksDir, { recursive: true });
|
|
11970
12611
|
}
|
|
11971
12612
|
writeFileSync3(hookPath, generateHookScript(), { mode: 493 });
|
|
11972
12613
|
success(`commit-msg hook installed.`);
|
|
11973
|
-
info(`Convention: ${
|
|
11974
|
-
info(`Path: ${
|
|
12614
|
+
info(`Convention: ${import_picocolors11.default.bold(CONVENTION_LABELS[config.commitConvention])}`, "");
|
|
12615
|
+
info(`Path: ${import_picocolors11.default.dim(hookPath)}`, "");
|
|
11975
12616
|
warn("Note: hooks can be bypassed with `git commit --no-verify`.");
|
|
11976
12617
|
}
|
|
11977
12618
|
async function uninstallHook() {
|
|
11978
|
-
|
|
12619
|
+
projectHeading("hook uninstall", "\uD83E\uDE9D");
|
|
11979
12620
|
const hookPath = getHookPath();
|
|
11980
12621
|
if (!existsSync4(hookPath)) {
|
|
11981
12622
|
info("No commit-msg hook found. Nothing to uninstall.");
|
|
@@ -11991,7 +12632,10 @@ async function uninstallHook() {
|
|
|
11991
12632
|
}
|
|
11992
12633
|
|
|
11993
12634
|
// src/commands/log.ts
|
|
11994
|
-
var
|
|
12635
|
+
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
12636
|
+
function getDefaultOverviewRemoteCommitCount(hasLocalUnpushedCommits) {
|
|
12637
|
+
return hasLocalUnpushedCommits ? 10 : 20;
|
|
12638
|
+
}
|
|
11995
12639
|
var log_default = defineCommand({
|
|
11996
12640
|
meta: {
|
|
11997
12641
|
name: "log",
|
|
@@ -12012,7 +12656,13 @@ var log_default = defineCommand({
|
|
|
12012
12656
|
remote: {
|
|
12013
12657
|
type: "boolean",
|
|
12014
12658
|
alias: "r",
|
|
12015
|
-
description: "Show only remote
|
|
12659
|
+
description: "Show only the remote branch history",
|
|
12660
|
+
default: false
|
|
12661
|
+
},
|
|
12662
|
+
local: {
|
|
12663
|
+
type: "boolean",
|
|
12664
|
+
alias: "l",
|
|
12665
|
+
description: "Show only local unpushed commits",
|
|
12016
12666
|
default: false
|
|
12017
12667
|
},
|
|
12018
12668
|
full: {
|
|
@@ -12039,12 +12689,15 @@ var log_default = defineCommand({
|
|
|
12039
12689
|
process.exit(1);
|
|
12040
12690
|
}
|
|
12041
12691
|
const config = readConfig();
|
|
12042
|
-
const
|
|
12692
|
+
const requestedCount = args.count ? Number.parseInt(args.count, 10) : null;
|
|
12693
|
+
const localCount = requestedCount ?? 20;
|
|
12043
12694
|
const showGraph = args.graph;
|
|
12044
12695
|
const targetBranch = args.branch;
|
|
12045
|
-
let mode = "
|
|
12696
|
+
let mode = "overview";
|
|
12046
12697
|
if (args.all)
|
|
12047
12698
|
mode = "all";
|
|
12699
|
+
else if (args.local)
|
|
12700
|
+
mode = "local";
|
|
12048
12701
|
else if (args.remote)
|
|
12049
12702
|
mode = "remote";
|
|
12050
12703
|
else if (args.full || targetBranch)
|
|
@@ -12060,33 +12713,74 @@ var log_default = defineCommand({
|
|
|
12060
12713
|
compareRef = fallback;
|
|
12061
12714
|
usingFallback = true;
|
|
12062
12715
|
}
|
|
12063
|
-
}
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12716
|
+
}
|
|
12717
|
+
const overviewRemoteCount = requestedCount ?? await getOverviewRemoteCommitCount(currentBranch, compareRef);
|
|
12718
|
+
projectHeading("log", "\uD83D\uDCDC");
|
|
12719
|
+
printModeHeader(mode, currentBranch, compareRef, usingFallback);
|
|
12720
|
+
if (mode === "overview") {
|
|
12721
|
+
await renderOverviewLog({
|
|
12722
|
+
localCount,
|
|
12723
|
+
remoteCount: overviewRemoteCount,
|
|
12724
|
+
compareRef,
|
|
12725
|
+
showGraph,
|
|
12726
|
+
protectedBranches,
|
|
12727
|
+
currentBranch,
|
|
12728
|
+
usingFallback
|
|
12729
|
+
});
|
|
12730
|
+
} else if (mode === "local") {
|
|
12731
|
+
if (!compareRef) {
|
|
12732
|
+
console.log();
|
|
12733
|
+
console.log(import_picocolors12.default.yellow(" ⚠ Could not determine a comparison branch."));
|
|
12734
|
+
console.log(import_picocolors12.default.dim(" No upstream tracking set and no remote base branch found."));
|
|
12735
|
+
console.log(import_picocolors12.default.dim(` Use ${import_picocolors12.default.bold("contrib log --full")} to see the full commit history instead.`));
|
|
12736
|
+
console.log();
|
|
12737
|
+
return;
|
|
12738
|
+
}
|
|
12739
|
+
const hasCommits = await renderScopedLog({
|
|
12740
|
+
mode,
|
|
12741
|
+
count: localCount,
|
|
12742
|
+
upstream: compareRef,
|
|
12743
|
+
showGraph,
|
|
12744
|
+
protectedBranches,
|
|
12745
|
+
currentBranch
|
|
12746
|
+
});
|
|
12747
|
+
if (!hasCommits) {
|
|
12748
|
+
return;
|
|
12749
|
+
}
|
|
12750
|
+
} else if (mode === "remote") {
|
|
12751
|
+
const remoteBranch = compareRef ?? targetBranch;
|
|
12752
|
+
if (!remoteBranch) {
|
|
12068
12753
|
console.log();
|
|
12069
|
-
console.log(
|
|
12070
|
-
console.log(
|
|
12071
|
-
console.log(import_picocolors11.default.dim(` Use ${import_picocolors11.default.bold("contrib log --full")} to see the full commit history instead.`));
|
|
12754
|
+
console.log(import_picocolors12.default.yellow(" ⚠ Could not determine a remote branch to display."));
|
|
12755
|
+
console.log(import_picocolors12.default.dim(" Set an upstream tracking branch or configure your base branch first."));
|
|
12072
12756
|
console.log();
|
|
12073
|
-
printGuidance();
|
|
12074
12757
|
return;
|
|
12075
12758
|
}
|
|
12076
|
-
const hasCommits = await
|
|
12759
|
+
const hasCommits = await renderFullLog({
|
|
12760
|
+
count: localCount,
|
|
12761
|
+
all: false,
|
|
12762
|
+
showGraph,
|
|
12763
|
+
targetBranch: remoteBranch,
|
|
12764
|
+
protectedBranches,
|
|
12765
|
+
currentBranch
|
|
12766
|
+
});
|
|
12077
12767
|
if (!hasCommits) {
|
|
12078
|
-
printGuidance();
|
|
12079
12768
|
return;
|
|
12080
12769
|
}
|
|
12081
12770
|
} else {
|
|
12082
|
-
const hasCommits = await renderFullLog({
|
|
12771
|
+
const hasCommits = await renderFullLog({
|
|
12772
|
+
count: localCount,
|
|
12773
|
+
all: mode === "all",
|
|
12774
|
+
showGraph,
|
|
12775
|
+
targetBranch,
|
|
12776
|
+
protectedBranches,
|
|
12777
|
+
currentBranch
|
|
12778
|
+
});
|
|
12083
12779
|
if (!hasCommits) {
|
|
12084
|
-
printGuidance();
|
|
12085
12780
|
return;
|
|
12086
12781
|
}
|
|
12087
12782
|
}
|
|
12088
|
-
printFooter(mode,
|
|
12089
|
-
printGuidance();
|
|
12783
|
+
printFooter(mode, localCount, overviewRemoteCount, targetBranch);
|
|
12090
12784
|
}
|
|
12091
12785
|
});
|
|
12092
12786
|
async function resolveBaseBranchRef(config) {
|
|
@@ -12108,38 +12802,49 @@ async function resolveBaseBranchRef(config) {
|
|
|
12108
12802
|
}
|
|
12109
12803
|
return null;
|
|
12110
12804
|
}
|
|
12805
|
+
async function getOverviewRemoteCommitCount(currentBranch, compareRef) {
|
|
12806
|
+
if (!currentBranch || !compareRef) {
|
|
12807
|
+
return getDefaultOverviewRemoteCommitCount(false);
|
|
12808
|
+
}
|
|
12809
|
+
const divergence = await getDivergence(currentBranch, compareRef);
|
|
12810
|
+
return getDefaultOverviewRemoteCommitCount(divergence.ahead > 0);
|
|
12811
|
+
}
|
|
12111
12812
|
function printModeHeader(mode, currentBranch, compareRef, usingFallback = false) {
|
|
12112
12813
|
const branch = currentBranch ?? "HEAD";
|
|
12113
|
-
const fallbackNote = usingFallback ?
|
|
12114
|
-
console.log();
|
|
12814
|
+
const fallbackNote = usingFallback ? import_picocolors12.default.yellow(" (no upstream — comparing against base branch)") : "";
|
|
12115
12815
|
switch (mode) {
|
|
12816
|
+
case "overview":
|
|
12817
|
+
console.log(import_picocolors12.default.dim(` mode: ${import_picocolors12.default.bold("overview")} — local unpushed commits and remote branch history for ${import_picocolors12.default.bold(branch)}`) + fallbackNote);
|
|
12818
|
+
if (compareRef) {
|
|
12819
|
+
console.log(import_picocolors12.default.dim(` remote source: ${import_picocolors12.default.bold(compareRef)}`));
|
|
12820
|
+
}
|
|
12821
|
+
break;
|
|
12116
12822
|
case "local":
|
|
12117
|
-
console.log(
|
|
12823
|
+
console.log(import_picocolors12.default.dim(` mode: ${import_picocolors12.default.bold("local")} — unpushed commits on ${import_picocolors12.default.bold(branch)}`) + fallbackNote);
|
|
12118
12824
|
if (compareRef) {
|
|
12119
|
-
console.log(
|
|
12825
|
+
console.log(import_picocolors12.default.dim(` comparing: ${import_picocolors12.default.bold(compareRef)} ➜ ${import_picocolors12.default.bold("HEAD")}`));
|
|
12120
12826
|
}
|
|
12121
12827
|
break;
|
|
12122
12828
|
case "remote":
|
|
12123
|
-
console.log(
|
|
12829
|
+
console.log(import_picocolors12.default.dim(` mode: ${import_picocolors12.default.bold("remote")} — remote branch history relevant to ${import_picocolors12.default.bold(branch)}`) + fallbackNote);
|
|
12124
12830
|
if (compareRef) {
|
|
12125
|
-
console.log(
|
|
12831
|
+
console.log(import_picocolors12.default.dim(` branch: ${import_picocolors12.default.bold(compareRef)}`));
|
|
12126
12832
|
}
|
|
12127
12833
|
break;
|
|
12128
12834
|
case "full":
|
|
12129
|
-
console.log(
|
|
12835
|
+
console.log(import_picocolors12.default.dim(` mode: ${import_picocolors12.default.bold("full")} — complete commit history for ${import_picocolors12.default.bold(branch)}`));
|
|
12130
12836
|
break;
|
|
12131
12837
|
case "all":
|
|
12132
|
-
console.log(
|
|
12838
|
+
console.log(import_picocolors12.default.dim(` mode: ${import_picocolors12.default.bold("all")} — commits across all branches`));
|
|
12133
12839
|
break;
|
|
12134
12840
|
}
|
|
12135
12841
|
}
|
|
12136
12842
|
async function renderScopedLog(options) {
|
|
12137
|
-
const {
|
|
12843
|
+
const { count, upstream, showGraph, protectedBranches, currentBranch } = options;
|
|
12138
12844
|
if (showGraph) {
|
|
12139
|
-
const
|
|
12140
|
-
const lines = await graphFn({ count, upstream });
|
|
12845
|
+
const lines = await getLocalCommitsGraph({ count, upstream });
|
|
12141
12846
|
if (lines.length === 0) {
|
|
12142
|
-
printEmptyState(
|
|
12847
|
+
printEmptyState("local");
|
|
12143
12848
|
return false;
|
|
12144
12849
|
}
|
|
12145
12850
|
console.log();
|
|
@@ -12147,15 +12852,14 @@ async function renderScopedLog(options) {
|
|
|
12147
12852
|
console.log(` ${colorizeGraphLine(line, protectedBranches, currentBranch)}`);
|
|
12148
12853
|
}
|
|
12149
12854
|
} else {
|
|
12150
|
-
const
|
|
12151
|
-
const entries = await entryFn({ count, upstream });
|
|
12855
|
+
const entries = await getLocalCommitsEntries({ count, upstream });
|
|
12152
12856
|
if (entries.length === 0) {
|
|
12153
|
-
printEmptyState(
|
|
12857
|
+
printEmptyState("local");
|
|
12154
12858
|
return false;
|
|
12155
12859
|
}
|
|
12156
12860
|
console.log();
|
|
12157
12861
|
for (const entry of entries) {
|
|
12158
|
-
const hashStr =
|
|
12862
|
+
const hashStr = import_picocolors12.default.yellow(entry.hash);
|
|
12159
12863
|
const refsStr = entry.refs ? ` ${colorizeRefs(entry.refs, protectedBranches, currentBranch)}` : "";
|
|
12160
12864
|
const subjectStr = colorizeSubject(entry.subject);
|
|
12161
12865
|
console.log(` ${hashStr}${refsStr} ${subjectStr}`);
|
|
@@ -12163,12 +12867,58 @@ async function renderScopedLog(options) {
|
|
|
12163
12867
|
}
|
|
12164
12868
|
return true;
|
|
12165
12869
|
}
|
|
12870
|
+
async function renderOverviewLog(options) {
|
|
12871
|
+
const {
|
|
12872
|
+
localCount,
|
|
12873
|
+
remoteCount,
|
|
12874
|
+
compareRef,
|
|
12875
|
+
showGraph,
|
|
12876
|
+
protectedBranches,
|
|
12877
|
+
currentBranch,
|
|
12878
|
+
usingFallback
|
|
12879
|
+
} = options;
|
|
12880
|
+
console.log();
|
|
12881
|
+
console.log(import_picocolors12.default.bold(import_picocolors12.default.cyan(" Local Unpushed Commits")));
|
|
12882
|
+
if (!compareRef) {
|
|
12883
|
+
console.log(import_picocolors12.default.dim(" No comparison branch detected for local commit status."));
|
|
12884
|
+
console.log(import_picocolors12.default.dim(" Set an upstream tracking branch or run cn log --full to inspect the current branch history."));
|
|
12885
|
+
} else {
|
|
12886
|
+
await renderScopedLog({
|
|
12887
|
+
mode: "local",
|
|
12888
|
+
count: localCount,
|
|
12889
|
+
upstream: compareRef,
|
|
12890
|
+
showGraph,
|
|
12891
|
+
protectedBranches,
|
|
12892
|
+
currentBranch
|
|
12893
|
+
});
|
|
12894
|
+
}
|
|
12895
|
+
console.log();
|
|
12896
|
+
console.log(import_picocolors12.default.bold(import_picocolors12.default.cyan(" Remote Branch History")));
|
|
12897
|
+
if (!compareRef) {
|
|
12898
|
+
console.log(import_picocolors12.default.dim(" No remote branch detected."));
|
|
12899
|
+
if (usingFallback) {
|
|
12900
|
+
console.log(import_picocolors12.default.dim(" Configure your base branch or upstream tracking to enable the split view."));
|
|
12901
|
+
}
|
|
12902
|
+
return;
|
|
12903
|
+
}
|
|
12904
|
+
const hasRemoteHistory = await renderFullLog({
|
|
12905
|
+
count: remoteCount,
|
|
12906
|
+
all: false,
|
|
12907
|
+
showGraph,
|
|
12908
|
+
targetBranch: compareRef,
|
|
12909
|
+
protectedBranches,
|
|
12910
|
+
currentBranch
|
|
12911
|
+
});
|
|
12912
|
+
if (!hasRemoteHistory) {
|
|
12913
|
+
console.log(import_picocolors12.default.dim(" No remote history found for the selected branch."));
|
|
12914
|
+
}
|
|
12915
|
+
}
|
|
12166
12916
|
function printEmptyState(mode) {
|
|
12167
12917
|
console.log();
|
|
12168
12918
|
if (mode === "local") {
|
|
12169
|
-
console.log(
|
|
12919
|
+
console.log(import_picocolors12.default.dim(" No local unpushed commits — you're up to date with remote!"));
|
|
12170
12920
|
} else {
|
|
12171
|
-
console.log(
|
|
12921
|
+
console.log(import_picocolors12.default.dim(" No remote-only commits — your local branch is up to date!"));
|
|
12172
12922
|
}
|
|
12173
12923
|
console.log();
|
|
12174
12924
|
}
|
|
@@ -12177,7 +12927,7 @@ async function renderFullLog(options) {
|
|
|
12177
12927
|
if (showGraph) {
|
|
12178
12928
|
const lines = await getLogGraph({ count, all, branch: targetBranch });
|
|
12179
12929
|
if (lines.length === 0) {
|
|
12180
|
-
console.log(
|
|
12930
|
+
console.log(import_picocolors12.default.dim(" No commits found."));
|
|
12181
12931
|
console.log();
|
|
12182
12932
|
return false;
|
|
12183
12933
|
}
|
|
@@ -12188,13 +12938,13 @@ async function renderFullLog(options) {
|
|
|
12188
12938
|
} else {
|
|
12189
12939
|
const entries = await getLogEntries({ count, all, branch: targetBranch });
|
|
12190
12940
|
if (entries.length === 0) {
|
|
12191
|
-
console.log(
|
|
12941
|
+
console.log(import_picocolors12.default.dim(" No commits found."));
|
|
12192
12942
|
console.log();
|
|
12193
12943
|
return false;
|
|
12194
12944
|
}
|
|
12195
12945
|
console.log();
|
|
12196
12946
|
for (const entry of entries) {
|
|
12197
|
-
const hashStr =
|
|
12947
|
+
const hashStr = import_picocolors12.default.yellow(entry.hash);
|
|
12198
12948
|
const refsStr = entry.refs ? ` ${colorizeRefs(entry.refs, protectedBranches, currentBranch)}` : "";
|
|
12199
12949
|
const subjectStr = colorizeSubject(entry.subject);
|
|
12200
12950
|
console.log(` ${hashStr}${refsStr} ${subjectStr}`);
|
|
@@ -12202,46 +12952,37 @@ async function renderFullLog(options) {
|
|
|
12202
12952
|
}
|
|
12203
12953
|
return true;
|
|
12204
12954
|
}
|
|
12205
|
-
function printFooter(mode, count, targetBranch) {
|
|
12955
|
+
function printFooter(mode, count, overviewRemoteCount, targetBranch) {
|
|
12206
12956
|
console.log();
|
|
12207
12957
|
switch (mode) {
|
|
12958
|
+
case "overview":
|
|
12959
|
+
console.log(import_picocolors12.default.dim(` Showing up to ${count} local commits and ${overviewRemoteCount} remote commits`));
|
|
12960
|
+
break;
|
|
12208
12961
|
case "local":
|
|
12209
|
-
console.log(
|
|
12962
|
+
console.log(import_picocolors12.default.dim(` Showing up to ${count} unpushed commits`));
|
|
12210
12963
|
break;
|
|
12211
12964
|
case "remote":
|
|
12212
|
-
console.log(
|
|
12965
|
+
console.log(import_picocolors12.default.dim(` Showing ${count} most recent commits from the remote branch`));
|
|
12213
12966
|
break;
|
|
12214
12967
|
case "full":
|
|
12215
|
-
console.log(
|
|
12968
|
+
console.log(import_picocolors12.default.dim(` Showing ${count} most recent commits${targetBranch ? ` (${targetBranch})` : ""}`));
|
|
12216
12969
|
break;
|
|
12217
12970
|
case "all":
|
|
12218
|
-
console.log(
|
|
12971
|
+
console.log(import_picocolors12.default.dim(` Showing ${count} most recent commits (all branches)`));
|
|
12219
12972
|
break;
|
|
12220
12973
|
}
|
|
12221
12974
|
}
|
|
12222
|
-
function printGuidance() {
|
|
12223
|
-
console.log();
|
|
12224
|
-
console.log(import_picocolors11.default.dim(" ─── quick guide ───"));
|
|
12225
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log")} local unpushed commits (default)`));
|
|
12226
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log --remote")} commits on remote not yet pulled`));
|
|
12227
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log --full")} full history for the current branch`));
|
|
12228
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log --all")} commits across all branches`));
|
|
12229
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log -n 50")} change the commit limit (default: 20)`));
|
|
12230
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log -b dev")} view log for a specific branch`));
|
|
12231
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log --no-graph")} flat list without graph lines`));
|
|
12232
|
-
console.log();
|
|
12233
|
-
}
|
|
12234
12975
|
function colorizeGraphLine(line, protectedBranches, currentBranch) {
|
|
12235
12976
|
const match = line.match(/^([|/\\*\s_.-]*)([a-f0-9]{7,12})(\s+\(([^)]+)\))?\s*(.*)/);
|
|
12236
12977
|
if (!match) {
|
|
12237
|
-
return
|
|
12978
|
+
return import_picocolors12.default.cyan(line);
|
|
12238
12979
|
}
|
|
12239
12980
|
const [, graphPart = "", hash, , refs, subject = ""] = match;
|
|
12240
12981
|
const parts = [];
|
|
12241
12982
|
if (graphPart) {
|
|
12242
12983
|
parts.push(colorizeGraphChars(graphPart));
|
|
12243
12984
|
}
|
|
12244
|
-
parts.push(
|
|
12985
|
+
parts.push(import_picocolors12.default.yellow(hash));
|
|
12245
12986
|
if (refs) {
|
|
12246
12987
|
parts.push(` (${colorizeRefs(refs, protectedBranches, currentBranch)})`);
|
|
12247
12988
|
}
|
|
@@ -12252,15 +12993,15 @@ function colorizeGraphChars(graphPart) {
|
|
|
12252
12993
|
return graphPart.split("").map((ch) => {
|
|
12253
12994
|
switch (ch) {
|
|
12254
12995
|
case "*":
|
|
12255
|
-
return
|
|
12996
|
+
return import_picocolors12.default.green(ch);
|
|
12256
12997
|
case "|":
|
|
12257
|
-
return
|
|
12998
|
+
return import_picocolors12.default.cyan(ch);
|
|
12258
12999
|
case "/":
|
|
12259
13000
|
case "\\":
|
|
12260
|
-
return
|
|
13001
|
+
return import_picocolors12.default.cyan(ch);
|
|
12261
13002
|
case "-":
|
|
12262
13003
|
case "_":
|
|
12263
|
-
return
|
|
13004
|
+
return import_picocolors12.default.cyan(ch);
|
|
12264
13005
|
default:
|
|
12265
13006
|
return ch;
|
|
12266
13007
|
}
|
|
@@ -12272,50 +13013,50 @@ function colorizeRefs(refs, protectedBranches, currentBranch) {
|
|
|
12272
13013
|
if (trimmed.startsWith("HEAD ->") || trimmed === "HEAD") {
|
|
12273
13014
|
const branchName = trimmed.replace("HEAD -> ", "");
|
|
12274
13015
|
if (trimmed === "HEAD") {
|
|
12275
|
-
return
|
|
13016
|
+
return import_picocolors12.default.bold(import_picocolors12.default.cyan("HEAD"));
|
|
12276
13017
|
}
|
|
12277
|
-
return `${
|
|
13018
|
+
return `${import_picocolors12.default.bold(import_picocolors12.default.cyan("HEAD"))} ${import_picocolors12.default.dim("->")} ${colorizeRefName(branchName, protectedBranches, currentBranch)}`;
|
|
12278
13019
|
}
|
|
12279
13020
|
if (trimmed.startsWith("tag:")) {
|
|
12280
|
-
return
|
|
13021
|
+
return import_picocolors12.default.bold(import_picocolors12.default.magenta(trimmed));
|
|
12281
13022
|
}
|
|
12282
13023
|
return colorizeRefName(trimmed, protectedBranches, currentBranch);
|
|
12283
|
-
}).join(
|
|
13024
|
+
}).join(import_picocolors12.default.dim(", "));
|
|
12284
13025
|
}
|
|
12285
13026
|
function colorizeRefName(name, protectedBranches, currentBranch) {
|
|
12286
13027
|
const isRemote = name.includes("/");
|
|
12287
13028
|
const localName = isRemote ? name.split("/").slice(1).join("/") : name;
|
|
12288
13029
|
if (protectedBranches.includes(localName)) {
|
|
12289
|
-
return isRemote ?
|
|
13030
|
+
return isRemote ? import_picocolors12.default.bold(import_picocolors12.default.red(name)) : import_picocolors12.default.bold(import_picocolors12.default.red(name));
|
|
12290
13031
|
}
|
|
12291
13032
|
if (localName === currentBranch) {
|
|
12292
|
-
return
|
|
13033
|
+
return import_picocolors12.default.bold(import_picocolors12.default.green(name));
|
|
12293
13034
|
}
|
|
12294
13035
|
if (isRemote) {
|
|
12295
|
-
return
|
|
13036
|
+
return import_picocolors12.default.blue(name);
|
|
12296
13037
|
}
|
|
12297
|
-
return
|
|
13038
|
+
return import_picocolors12.default.green(name);
|
|
12298
13039
|
}
|
|
12299
13040
|
function colorizeSubject(subject) {
|
|
12300
13041
|
const emojiMatch = subject.match(/^((?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)+\s*)/u);
|
|
12301
13042
|
if (emojiMatch) {
|
|
12302
13043
|
const emoji = emojiMatch[1];
|
|
12303
13044
|
const rest = subject.slice(emoji.length);
|
|
12304
|
-
return `${emoji}${
|
|
13045
|
+
return `${emoji}${import_picocolors12.default.white(rest)}`;
|
|
12305
13046
|
}
|
|
12306
13047
|
if (subject.startsWith("Merge ")) {
|
|
12307
|
-
return
|
|
13048
|
+
return import_picocolors12.default.dim(subject);
|
|
12308
13049
|
}
|
|
12309
|
-
return
|
|
13050
|
+
return import_picocolors12.default.white(subject);
|
|
12310
13051
|
}
|
|
12311
13052
|
|
|
12312
13053
|
// src/commands/save.ts
|
|
12313
|
-
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
12314
13054
|
import { execFile as execFileCb4 } from "node:child_process";
|
|
13055
|
+
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
12315
13056
|
function gitRun(args) {
|
|
12316
|
-
return new Promise((
|
|
13057
|
+
return new Promise((resolve3) => {
|
|
12317
13058
|
execFileCb4("git", args, (err, stdout2, stderr) => {
|
|
12318
|
-
|
|
13059
|
+
resolve3({
|
|
12319
13060
|
exitCode: err ? err.code === "ENOENT" ? 127 : err.status ?? 1 : 0,
|
|
12320
13061
|
stdout: stdout2 ?? "",
|
|
12321
13062
|
stderr: stderr ?? ""
|
|
@@ -12372,7 +13113,7 @@ var save_default = defineCommand({
|
|
|
12372
13113
|
}
|
|
12373
13114
|
});
|
|
12374
13115
|
async function handleSave(message) {
|
|
12375
|
-
|
|
13116
|
+
projectHeading("save", "\uD83D\uDCBE");
|
|
12376
13117
|
const currentBranch = await getCurrentBranch();
|
|
12377
13118
|
const label = message ?? `work-in-progress on ${currentBranch ?? "unknown"}`;
|
|
12378
13119
|
const stashMsg = `contrib-save: ${label}`;
|
|
@@ -12385,11 +13126,11 @@ async function handleSave(message) {
|
|
|
12385
13126
|
info("No uncommitted changes to save.");
|
|
12386
13127
|
return;
|
|
12387
13128
|
}
|
|
12388
|
-
success(`Saved: ${
|
|
12389
|
-
info(`Use ${
|
|
13129
|
+
success(`Saved: ${import_picocolors13.default.dim(label)}`);
|
|
13130
|
+
info(`Use ${import_picocolors13.default.bold("contrib save --restore")} to bring them back.`, "");
|
|
12390
13131
|
}
|
|
12391
13132
|
async function handleRestore() {
|
|
12392
|
-
|
|
13133
|
+
projectHeading("save --restore", "\uD83D\uDCBE");
|
|
12393
13134
|
const stashes = await getStashList();
|
|
12394
13135
|
if (stashes.length === 0) {
|
|
12395
13136
|
info("No saved changes found.");
|
|
@@ -12402,7 +13143,7 @@ async function handleRestore() {
|
|
|
12402
13143
|
warn("You may have conflicts. Resolve them and run `git stash drop` when done.");
|
|
12403
13144
|
process.exit(1);
|
|
12404
13145
|
}
|
|
12405
|
-
success(`Restored: ${
|
|
13146
|
+
success(`Restored: ${import_picocolors13.default.dim(stashes[0].message)}`);
|
|
12406
13147
|
return;
|
|
12407
13148
|
}
|
|
12408
13149
|
const choices = stashes.map((s2) => `${s2.index} ${s2.message}`);
|
|
@@ -12415,10 +13156,10 @@ async function handleRestore() {
|
|
|
12415
13156
|
process.exit(1);
|
|
12416
13157
|
}
|
|
12417
13158
|
const match = stashes.find((s2) => String(s2.index) === idx);
|
|
12418
|
-
success(`Restored: ${
|
|
13159
|
+
success(`Restored: ${import_picocolors13.default.dim(match?.message ?? "saved changes")}`);
|
|
12419
13160
|
}
|
|
12420
13161
|
async function handleList() {
|
|
12421
|
-
|
|
13162
|
+
projectHeading("save --list", "\uD83D\uDCBE");
|
|
12422
13163
|
const stashes = await getStashList();
|
|
12423
13164
|
if (stashes.length === 0) {
|
|
12424
13165
|
info("No saved changes.");
|
|
@@ -12426,16 +13167,16 @@ async function handleList() {
|
|
|
12426
13167
|
}
|
|
12427
13168
|
console.log();
|
|
12428
13169
|
for (const s2 of stashes) {
|
|
12429
|
-
const idx =
|
|
13170
|
+
const idx = import_picocolors13.default.dim(`[${s2.index}]`);
|
|
12430
13171
|
const msg = s2.message;
|
|
12431
13172
|
console.log(` ${idx} ${msg}`);
|
|
12432
13173
|
}
|
|
12433
13174
|
console.log();
|
|
12434
|
-
info(`Use ${
|
|
12435
|
-
info(`Use ${
|
|
13175
|
+
info(`Use ${import_picocolors13.default.bold("contrib save --restore")} to bring changes back.`, "");
|
|
13176
|
+
info(`Use ${import_picocolors13.default.bold("contrib save --drop")} to discard saved changes.`, "");
|
|
12436
13177
|
}
|
|
12437
13178
|
async function handleDrop() {
|
|
12438
|
-
|
|
13179
|
+
projectHeading("save --drop", "\uD83D\uDCBE");
|
|
12439
13180
|
const stashes = await getStashList();
|
|
12440
13181
|
if (stashes.length === 0) {
|
|
12441
13182
|
info("No saved changes to drop.");
|
|
@@ -12450,7 +13191,7 @@ async function handleDrop() {
|
|
|
12450
13191
|
process.exit(1);
|
|
12451
13192
|
}
|
|
12452
13193
|
const match = stashes.find((s2) => String(s2.index) === idx);
|
|
12453
|
-
success(`Dropped: ${
|
|
13194
|
+
success(`Dropped: ${import_picocolors13.default.dim(match?.message ?? "saved changes")}`);
|
|
12454
13195
|
}
|
|
12455
13196
|
async function getStashList() {
|
|
12456
13197
|
const result = await gitRun(["stash", "list"]);
|
|
@@ -12467,38 +13208,23 @@ async function getStashList() {
|
|
|
12467
13208
|
}
|
|
12468
13209
|
|
|
12469
13210
|
// src/commands/setup.ts
|
|
12470
|
-
var
|
|
13211
|
+
var import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
12471
13212
|
async function shouldContinueSetupWithExistingConfig(options) {
|
|
12472
|
-
const {
|
|
12473
|
-
existingConfig,
|
|
12474
|
-
hasConfigFile,
|
|
12475
|
-
confirm,
|
|
12476
|
-
ensureIgnored,
|
|
12477
|
-
onInfo,
|
|
12478
|
-
onWarn,
|
|
12479
|
-
onSuccess,
|
|
12480
|
-
summary
|
|
12481
|
-
} = options;
|
|
13213
|
+
const { existingConfig, hasConfigFile, confirm, onInfo, onWarn, onSuccess, summary } = options;
|
|
12482
13214
|
if (existingConfig) {
|
|
12483
|
-
onInfo("Existing
|
|
13215
|
+
onInfo("Existing repo config detected:");
|
|
12484
13216
|
summary(existingConfig);
|
|
12485
13217
|
const shouldContinue = await confirm("Continue setup and overwrite existing config?");
|
|
12486
13218
|
if (!shouldContinue) {
|
|
12487
|
-
if (ensureIgnored()) {
|
|
12488
|
-
onInfo("Added .contributerc.json to .gitignore to avoid committing personal config.");
|
|
12489
|
-
}
|
|
12490
13219
|
onSuccess("Keeping existing setup.");
|
|
12491
13220
|
return false;
|
|
12492
13221
|
}
|
|
12493
13222
|
return true;
|
|
12494
13223
|
}
|
|
12495
13224
|
if (hasConfigFile) {
|
|
12496
|
-
onWarn("Found
|
|
13225
|
+
onWarn("Found an existing repo config but it appears invalid.");
|
|
12497
13226
|
const shouldContinue = await confirm("Continue setup and overwrite invalid config?");
|
|
12498
13227
|
if (!shouldContinue) {
|
|
12499
|
-
if (ensureIgnored()) {
|
|
12500
|
-
onInfo("Added .contributerc.json to .gitignore to avoid committing personal config.");
|
|
12501
|
-
}
|
|
12502
13228
|
onInfo("Keeping existing file. Run setup again when ready to repair it.");
|
|
12503
13229
|
return false;
|
|
12504
13230
|
}
|
|
@@ -12508,20 +13234,19 @@ async function shouldContinueSetupWithExistingConfig(options) {
|
|
|
12508
13234
|
var setup_default = defineCommand({
|
|
12509
13235
|
meta: {
|
|
12510
13236
|
name: "setup",
|
|
12511
|
-
description: "Initialize contribute-now config for this repo
|
|
13237
|
+
description: "Initialize contribute-now config for this repo using local Git storage"
|
|
12512
13238
|
},
|
|
12513
13239
|
async run() {
|
|
12514
13240
|
if (!await isGitRepo()) {
|
|
12515
13241
|
error("Not inside a git repository. Run this command from within a git repo.");
|
|
12516
13242
|
process.exit(1);
|
|
12517
13243
|
}
|
|
12518
|
-
|
|
13244
|
+
projectHeading("setup", "\uD83D\uDD27");
|
|
12519
13245
|
const existingConfig = readConfig();
|
|
12520
13246
|
const shouldContinue = await shouldContinueSetupWithExistingConfig({
|
|
12521
13247
|
existingConfig,
|
|
12522
13248
|
hasConfigFile: configExists(),
|
|
12523
13249
|
confirm: confirmPrompt,
|
|
12524
|
-
ensureIgnored: ensureGitignored,
|
|
12525
13250
|
onInfo: info,
|
|
12526
13251
|
onWarn: warn,
|
|
12527
13252
|
onSuccess: success,
|
|
@@ -12540,7 +13265,7 @@ var setup_default = defineCommand({
|
|
|
12540
13265
|
workflow = "github-flow";
|
|
12541
13266
|
else if (workflowChoice.startsWith("Git Flow"))
|
|
12542
13267
|
workflow = "git-flow";
|
|
12543
|
-
info(`Workflow: ${
|
|
13268
|
+
info(`Workflow: ${import_picocolors14.default.bold(WORKFLOW_DESCRIPTIONS[workflow])}`);
|
|
12544
13269
|
const conventionChoice = await selectPrompt("Which commit convention should this project use?", [
|
|
12545
13270
|
`${CONVENTION_DESCRIPTIONS["clean-commit"]} (recommended)`,
|
|
12546
13271
|
CONVENTION_DESCRIPTIONS.conventional,
|
|
@@ -12551,6 +13276,8 @@ var setup_default = defineCommand({
|
|
|
12551
13276
|
commitConvention = "conventional";
|
|
12552
13277
|
else if (conventionChoice.includes("No commit"))
|
|
12553
13278
|
commitConvention = "none";
|
|
13279
|
+
const enableAI = await confirmPrompt("Enable AI-assisted features like commit messages, branch naming, PR text, and conflict guidance?");
|
|
13280
|
+
const showTips = await confirmPrompt("Show beginner quick guides and loading tips in command output?");
|
|
12554
13281
|
const remotes = await getRemotes();
|
|
12555
13282
|
if (remotes.length === 0) {
|
|
12556
13283
|
error("No git remotes found. Add a remote first (e.g., git remote add origin <url>).");
|
|
@@ -12604,15 +13331,15 @@ var setup_default = defineCommand({
|
|
|
12604
13331
|
detectedRole = roleChoice;
|
|
12605
13332
|
detectionSource = "user selection";
|
|
12606
13333
|
} else {
|
|
12607
|
-
info(`Detected role: ${
|
|
12608
|
-
const confirmed = await confirmPrompt(`Role detected as ${
|
|
13334
|
+
info(`Detected role: ${import_picocolors14.default.bold(detectedRole)} (via ${detectionSource})`);
|
|
13335
|
+
const confirmed = await confirmPrompt(`Role detected as ${import_picocolors14.default.bold(detectedRole)}. Is this correct?`);
|
|
12609
13336
|
if (!confirmed) {
|
|
12610
13337
|
const roleChoice = await selectPrompt("Select your role:", ["maintainer", "contributor"]);
|
|
12611
13338
|
detectedRole = roleChoice;
|
|
12612
13339
|
}
|
|
12613
13340
|
}
|
|
12614
13341
|
const defaultConfig = getDefaultConfig();
|
|
12615
|
-
info(
|
|
13342
|
+
info(import_picocolors14.default.dim("Tip: press Enter to keep the default branch name shown in each prompt."));
|
|
12616
13343
|
const mainBranchDefault = defaultConfig.mainBranch;
|
|
12617
13344
|
const mainBranch = await inputPrompt(`Main branch name (default: ${mainBranchDefault} — press Enter to keep)`, mainBranchDefault);
|
|
12618
13345
|
let devBranch;
|
|
@@ -12638,7 +13365,7 @@ var setup_default = defineCommand({
|
|
|
12638
13365
|
error("Setup cannot continue without the upstream remote for contributors.");
|
|
12639
13366
|
process.exit(1);
|
|
12640
13367
|
}
|
|
12641
|
-
success(`Added remote ${
|
|
13368
|
+
success(`Added remote ${import_picocolors14.default.bold(upstreamRemote)} → ${upstreamUrl}`);
|
|
12642
13369
|
} else {
|
|
12643
13370
|
error("An upstream remote URL is required for contributors.");
|
|
12644
13371
|
info("Add it manually: git remote add upstream <url>", "");
|
|
@@ -12654,54 +13381,58 @@ var setup_default = defineCommand({
|
|
|
12654
13381
|
upstream: upstreamRemote,
|
|
12655
13382
|
origin: originRemote,
|
|
12656
13383
|
branchPrefixes: defaultConfig.branchPrefixes,
|
|
12657
|
-
commitConvention
|
|
13384
|
+
commitConvention,
|
|
13385
|
+
aiEnabled: enableAI,
|
|
13386
|
+
showTips
|
|
12658
13387
|
};
|
|
12659
13388
|
writeConfig(config);
|
|
12660
|
-
success(`Config written to .
|
|
13389
|
+
success(`Config written to ${import_picocolors14.default.bold(getConfigLocationLabel())}`);
|
|
13390
|
+
info("This setup is stored locally for this clone and does not modify tracked files.", "");
|
|
12661
13391
|
const syncRemote = config.role === "contributor" ? config.upstream : config.origin;
|
|
12662
|
-
info(`Fetching ${
|
|
13392
|
+
info(`Fetching ${import_picocolors14.default.bold(syncRemote)} to verify branch configuration...`, "");
|
|
12663
13393
|
await fetchRemote(syncRemote);
|
|
12664
13394
|
const mainRef = `${syncRemote}/${config.mainBranch}`;
|
|
12665
13395
|
if (!await refExists(mainRef)) {
|
|
12666
|
-
warn(`Main branch ref ${
|
|
13396
|
+
warn(`Main branch ref ${import_picocolors14.default.bold(mainRef)} not found on remote.`);
|
|
12667
13397
|
warn("Config was saved — verify the branch name and re-run setup if needed.");
|
|
12668
13398
|
}
|
|
12669
13399
|
if (config.devBranch) {
|
|
12670
13400
|
const devRef = `${syncRemote}/${config.devBranch}`;
|
|
12671
13401
|
if (!await refExists(devRef)) {
|
|
12672
|
-
warn(`Dev branch ref ${
|
|
13402
|
+
warn(`Dev branch ref ${import_picocolors14.default.bold(devRef)} not found on remote.`);
|
|
12673
13403
|
warn("Config was saved — verify the branch name and re-run setup if needed.");
|
|
12674
13404
|
}
|
|
12675
13405
|
}
|
|
12676
|
-
if (ensureGitignored()) {
|
|
12677
|
-
info("Added .contributerc.json to .gitignore to avoid committing personal config.");
|
|
12678
|
-
}
|
|
12679
13406
|
console.log();
|
|
12680
|
-
info(`Workflow: ${
|
|
12681
|
-
info(`Convention: ${
|
|
12682
|
-
info(`
|
|
13407
|
+
info(`Workflow: ${import_picocolors14.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
13408
|
+
info(`Convention: ${import_picocolors14.default.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
|
|
13409
|
+
info(`AI: ${import_picocolors14.default.bold(isAIEnabled(config) ? "enabled" : "disabled")}`);
|
|
13410
|
+
info(`Guides: ${import_picocolors14.default.bold(shouldShowTips(config) ? "shown" : "hidden")}`);
|
|
13411
|
+
info(`Role: ${import_picocolors14.default.bold(config.role)}`);
|
|
12683
13412
|
if (config.devBranch) {
|
|
12684
|
-
info(`Main: ${
|
|
13413
|
+
info(`Main: ${import_picocolors14.default.bold(config.mainBranch)} | Dev: ${import_picocolors14.default.bold(config.devBranch)}`);
|
|
12685
13414
|
} else {
|
|
12686
|
-
info(`Main: ${
|
|
13415
|
+
info(`Main: ${import_picocolors14.default.bold(config.mainBranch)}`);
|
|
12687
13416
|
}
|
|
12688
|
-
info(`Origin: ${
|
|
13417
|
+
info(`Origin: ${import_picocolors14.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors14.default.bold(config.upstream)}` : ""}`);
|
|
12689
13418
|
}
|
|
12690
13419
|
});
|
|
12691
13420
|
function logConfigSummary(config) {
|
|
12692
|
-
info(`Workflow: ${
|
|
12693
|
-
info(`Convention: ${
|
|
12694
|
-
info(`
|
|
13421
|
+
info(`Workflow: ${import_picocolors14.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
13422
|
+
info(`Convention: ${import_picocolors14.default.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
|
|
13423
|
+
info(`AI: ${import_picocolors14.default.bold(isAIEnabled(config) ? "enabled" : "disabled")}`);
|
|
13424
|
+
info(`Guides: ${import_picocolors14.default.bold(shouldShowTips(config) ? "shown" : "hidden")}`);
|
|
13425
|
+
info(`Role: ${import_picocolors14.default.bold(config.role)}`);
|
|
12695
13426
|
if (config.devBranch) {
|
|
12696
|
-
info(`Main: ${
|
|
13427
|
+
info(`Main: ${import_picocolors14.default.bold(config.mainBranch)} | Dev: ${import_picocolors14.default.bold(config.devBranch)}`);
|
|
12697
13428
|
} else {
|
|
12698
|
-
info(`Main: ${
|
|
13429
|
+
info(`Main: ${import_picocolors14.default.bold(config.mainBranch)}`);
|
|
12699
13430
|
}
|
|
12700
|
-
info(`Origin: ${
|
|
13431
|
+
info(`Origin: ${import_picocolors14.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors14.default.bold(config.upstream)}` : ""}`);
|
|
12701
13432
|
}
|
|
12702
13433
|
|
|
12703
13434
|
// src/commands/start.ts
|
|
12704
|
-
var
|
|
13435
|
+
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
12705
13436
|
var start_default = defineCommand({
|
|
12706
13437
|
meta: {
|
|
12707
13438
|
name: "start",
|
|
@@ -12731,7 +13462,7 @@ var start_default = defineCommand({
|
|
|
12731
13462
|
await assertCleanGitState("starting a new branch");
|
|
12732
13463
|
const config = readConfig();
|
|
12733
13464
|
if (!config) {
|
|
12734
|
-
error("No
|
|
13465
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
12735
13466
|
process.exit(1);
|
|
12736
13467
|
}
|
|
12737
13468
|
if (await hasUncommittedChanges()) {
|
|
@@ -12741,57 +13472,28 @@ var start_default = defineCommand({
|
|
|
12741
13472
|
const { branchPrefixes } = config;
|
|
12742
13473
|
const baseBranch = getBaseBranch(config);
|
|
12743
13474
|
const syncSource = getSyncSource(config);
|
|
12744
|
-
let branchName = args.name;
|
|
12745
|
-
|
|
13475
|
+
let branchName = args.name?.trim();
|
|
13476
|
+
projectHeading("start", "\uD83C\uDF3F");
|
|
13477
|
+
branchName = await promptForBranchName({
|
|
13478
|
+
initialValue: branchName,
|
|
13479
|
+
branchPrefixes,
|
|
13480
|
+
useAI: isAIEnabled(config, args["no-ai"]),
|
|
13481
|
+
model: args.model
|
|
13482
|
+
});
|
|
12746
13483
|
if (!branchName) {
|
|
12747
|
-
|
|
12748
|
-
|
|
12749
|
-
error("A branch name or description is required.");
|
|
12750
|
-
process.exit(1);
|
|
12751
|
-
}
|
|
12752
|
-
branchName = branchName.trim();
|
|
12753
|
-
}
|
|
12754
|
-
const useAI = !args["no-ai"] && looksLikeNaturalLanguage(branchName);
|
|
12755
|
-
if (useAI) {
|
|
12756
|
-
const spinner = createSpinner("Generating branch name suggestion...");
|
|
12757
|
-
const suggested = await suggestBranchName(branchName, args.model);
|
|
12758
|
-
if (suggested) {
|
|
12759
|
-
spinner.success("Branch name suggestion ready.");
|
|
12760
|
-
console.log(`
|
|
12761
|
-
${import_picocolors14.default.dim("AI suggestion:")} ${import_picocolors14.default.bold(import_picocolors14.default.cyan(suggested))}`);
|
|
12762
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors14.default.bold(suggested)} as your branch name?`);
|
|
12763
|
-
if (accepted) {
|
|
12764
|
-
branchName = suggested;
|
|
12765
|
-
} else {
|
|
12766
|
-
branchName = await inputPrompt("Enter branch name", branchName);
|
|
12767
|
-
}
|
|
12768
|
-
} else {
|
|
12769
|
-
spinner.fail("AI did not return a branch name suggestion.");
|
|
12770
|
-
}
|
|
12771
|
-
}
|
|
12772
|
-
if (!hasPrefix(branchName, branchPrefixes)) {
|
|
12773
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors14.default.bold(branchName)}:`, branchPrefixes);
|
|
12774
|
-
branchName = formatBranchName(prefix, branchName);
|
|
12775
|
-
}
|
|
12776
|
-
if (!isValidBranchName(branchName)) {
|
|
12777
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
12778
|
-
process.exit(1);
|
|
12779
|
-
}
|
|
12780
|
-
info(`Creating branch: ${import_picocolors14.default.bold(branchName)}`);
|
|
12781
|
-
if (await branchExists(branchName)) {
|
|
12782
|
-
error(`Branch ${import_picocolors14.default.bold(branchName)} already exists.`);
|
|
12783
|
-
info(` Use ${import_picocolors14.default.bold(`git checkout ${branchName}`)} to switch to it, or choose a different name.`, "");
|
|
12784
|
-
process.exit(1);
|
|
13484
|
+
warn("Start cancelled.");
|
|
13485
|
+
process.exit(0);
|
|
12785
13486
|
}
|
|
13487
|
+
info(`Creating branch: ${import_picocolors15.default.bold(branchName)}`);
|
|
12786
13488
|
await fetchRemote(syncSource.remote);
|
|
12787
13489
|
if (!await refExists(syncSource.ref)) {
|
|
12788
|
-
warn(`Remote ref ${
|
|
13490
|
+
warn(`Remote ref ${import_picocolors15.default.bold(syncSource.ref)} not found. Creating branch from local ${import_picocolors15.default.bold(baseBranch)}.`);
|
|
12789
13491
|
}
|
|
12790
13492
|
const currentBranch = await getCurrentBranch();
|
|
12791
13493
|
if (currentBranch === baseBranch && await refExists(syncSource.ref)) {
|
|
12792
13494
|
const ahead = await countCommitsAhead(baseBranch, syncSource.ref);
|
|
12793
13495
|
if (ahead > 0) {
|
|
12794
|
-
warn(`You are on ${
|
|
13496
|
+
warn(`You are on ${import_picocolors15.default.bold(baseBranch)} with ${import_picocolors15.default.bold(String(ahead))} local commit${ahead > 1 ? "s" : ""} not in ${import_picocolors15.default.bold(syncSource.ref)}.`);
|
|
12795
13497
|
info(" Syncing will discard those commits. Consider backing them up first (e.g. create a branch).");
|
|
12796
13498
|
const proceed = await confirmPrompt("Discard local commits and sync to remote?");
|
|
12797
13499
|
if (!proceed) {
|
|
@@ -12808,10 +13510,10 @@ var start_default = defineCommand({
|
|
|
12808
13510
|
error(`Failed to create branch: ${result2.stderr}`);
|
|
12809
13511
|
process.exit(1);
|
|
12810
13512
|
}
|
|
12811
|
-
success(`Created ${
|
|
13513
|
+
success(`Created ${import_picocolors15.default.bold(branchName)} from ${import_picocolors15.default.bold(syncSource.ref)}`);
|
|
12812
13514
|
return;
|
|
12813
13515
|
}
|
|
12814
|
-
error(`Failed to update ${
|
|
13516
|
+
error(`Failed to update ${import_picocolors15.default.bold(baseBranch)}: ${updateResult.stderr}`);
|
|
12815
13517
|
info("Make sure your base branch exists locally or the remote ref is available.", "");
|
|
12816
13518
|
process.exit(1);
|
|
12817
13519
|
}
|
|
@@ -12820,12 +13522,12 @@ var start_default = defineCommand({
|
|
|
12820
13522
|
error(`Failed to create branch: ${result.stderr}`);
|
|
12821
13523
|
process.exit(1);
|
|
12822
13524
|
}
|
|
12823
|
-
success(`Created ${
|
|
13525
|
+
success(`Created ${import_picocolors15.default.bold(branchName)} from latest ${import_picocolors15.default.bold(baseBranch)}`);
|
|
12824
13526
|
}
|
|
12825
13527
|
});
|
|
12826
13528
|
|
|
12827
13529
|
// src/commands/status.ts
|
|
12828
|
-
var
|
|
13530
|
+
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
12829
13531
|
var status_default = defineCommand({
|
|
12830
13532
|
meta: {
|
|
12831
13533
|
name: "status",
|
|
@@ -12838,12 +13540,12 @@ var status_default = defineCommand({
|
|
|
12838
13540
|
}
|
|
12839
13541
|
const config = readConfig();
|
|
12840
13542
|
if (!config) {
|
|
12841
|
-
error("No
|
|
13543
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
12842
13544
|
process.exit(1);
|
|
12843
13545
|
}
|
|
12844
|
-
|
|
12845
|
-
console.log(` ${
|
|
12846
|
-
console.log(` ${
|
|
13546
|
+
projectHeading("status", "\uD83D\uDCCA");
|
|
13547
|
+
console.log(` ${import_picocolors16.default.dim("Workflow:")} ${import_picocolors16.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
13548
|
+
console.log(` ${import_picocolors16.default.dim("Role:")} ${import_picocolors16.default.bold(config.role)}`);
|
|
12847
13549
|
console.log();
|
|
12848
13550
|
await fetchAll();
|
|
12849
13551
|
const currentBranch = await getCurrentBranch();
|
|
@@ -12852,7 +13554,7 @@ var status_default = defineCommand({
|
|
|
12852
13554
|
const isContributor = config.role === "contributor";
|
|
12853
13555
|
const [dirty, fileStatus] = await Promise.all([hasUncommittedChanges(), getFileStatus()]);
|
|
12854
13556
|
if (dirty) {
|
|
12855
|
-
console.log(` ${
|
|
13557
|
+
console.log(` ${import_picocolors16.default.yellow("⚠")} ${import_picocolors16.default.yellow("Uncommitted changes in working tree")}`);
|
|
12856
13558
|
console.log();
|
|
12857
13559
|
}
|
|
12858
13560
|
const mainRemote = `${origin}/${mainBranch}`;
|
|
@@ -12871,28 +13573,32 @@ var status_default = defineCommand({
|
|
|
12871
13573
|
if (isFeatureBranch) {
|
|
12872
13574
|
const branchDiv = await getDivergence(currentBranch, baseBranch);
|
|
12873
13575
|
const branchLine = formatStatus(currentBranch, baseBranch, branchDiv.ahead, branchDiv.behind);
|
|
12874
|
-
console.log(branchLine +
|
|
13576
|
+
console.log(branchLine + import_picocolors16.default.dim(` (current ${import_picocolors16.default.green("*")})`));
|
|
12875
13577
|
branchStatus = await detectBranchStatus(currentBranch, baseBranch);
|
|
12876
13578
|
if (branchStatus.merged) {
|
|
12877
|
-
console.log(` ${
|
|
13579
|
+
console.log(` ${import_picocolors16.default.green("✓")} ${import_picocolors16.default.green("Branch merged")} — ${import_picocolors16.default.dim(branchStatus.mergedReason ?? "all commits reachable from base")}`);
|
|
12878
13580
|
}
|
|
12879
13581
|
if (branchStatus.stale) {
|
|
12880
|
-
console.log(` ${
|
|
13582
|
+
console.log(` ${import_picocolors16.default.yellow("⏳")} ${import_picocolors16.default.yellow("Branch is stale")} — ${import_picocolors16.default.dim(`last commit ${branchStatus.staleDaysAgo} days ago`)}`);
|
|
12881
13583
|
}
|
|
12882
13584
|
} else if (currentBranch) {
|
|
12883
|
-
console.log(
|
|
13585
|
+
console.log(import_picocolors16.default.dim(` (on ${import_picocolors16.default.bold(currentBranch)} branch)`));
|
|
12884
13586
|
}
|
|
12885
13587
|
let branchesAligned = true;
|
|
12886
13588
|
{
|
|
12887
13589
|
const alignRefs = [];
|
|
12888
13590
|
const devRemote = isContributor ? upstream : origin;
|
|
13591
|
+
const devBranch = hasDevBranch(workflow) ? config.devBranch : undefined;
|
|
12889
13592
|
const hashResults = await Promise.all([
|
|
12890
13593
|
getCommitHash(mainBranch).then((h2) => ({ name: mainBranch, hash: h2 })),
|
|
12891
|
-
getCommitHash(`${origin}/${mainBranch}`).then((h2) => ({
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12895
|
-
|
|
13594
|
+
getCommitHash(`${origin}/${mainBranch}`).then((h2) => ({
|
|
13595
|
+
name: `${origin}/${mainBranch}`,
|
|
13596
|
+
hash: h2
|
|
13597
|
+
})),
|
|
13598
|
+
...devBranch ? [
|
|
13599
|
+
getCommitHash(devBranch).then((h2) => ({ name: devBranch, hash: h2 })),
|
|
13600
|
+
getCommitHash(`${devRemote}/${devBranch}`).then((h2) => ({
|
|
13601
|
+
name: `${devRemote}/${devBranch}`,
|
|
12896
13602
|
hash: h2
|
|
12897
13603
|
}))
|
|
12898
13604
|
] : []
|
|
@@ -12906,24 +13612,27 @@ var status_default = defineCommand({
|
|
|
12906
13612
|
for (const { name, hash } of alignRefs) {
|
|
12907
13613
|
if (!groups.has(hash))
|
|
12908
13614
|
groups.set(hash, []);
|
|
12909
|
-
groups.get(hash)
|
|
13615
|
+
const group = groups.get(hash);
|
|
13616
|
+
if (group) {
|
|
13617
|
+
group.push(name);
|
|
13618
|
+
}
|
|
12910
13619
|
}
|
|
12911
13620
|
branchesAligned = groups.size === 1;
|
|
12912
13621
|
console.log();
|
|
12913
|
-
console.log(` ${
|
|
13622
|
+
console.log(` ${import_picocolors16.default.bold("\uD83D\uDD17 Branch Alignment")}`);
|
|
12914
13623
|
for (const [hash, names] of groups) {
|
|
12915
13624
|
const short = hash.slice(0, 7);
|
|
12916
|
-
const nameStr = names.map((n2) =>
|
|
12917
|
-
console.log(` ${
|
|
13625
|
+
const nameStr = names.map((n2) => import_picocolors16.default.bold(n2)).join(import_picocolors16.default.dim(" · "));
|
|
13626
|
+
console.log(` ${import_picocolors16.default.yellow(short)} ${import_picocolors16.default.dim("──")} ${nameStr}`);
|
|
12918
13627
|
const subject = await getCommitSubject(hash);
|
|
12919
13628
|
if (subject) {
|
|
12920
|
-
console.log(` ${
|
|
13629
|
+
console.log(` ${import_picocolors16.default.dim(subject)}`);
|
|
12921
13630
|
}
|
|
12922
13631
|
}
|
|
12923
13632
|
if (branchesAligned) {
|
|
12924
|
-
console.log(` ${
|
|
13633
|
+
console.log(` ${import_picocolors16.default.green("✓")} ${import_picocolors16.default.green("All branches aligned")} ${import_picocolors16.default.dim("— ready to start")}`);
|
|
12925
13634
|
} else {
|
|
12926
|
-
console.log(` ${
|
|
13635
|
+
console.log(` ${import_picocolors16.default.yellow("⚠")} ${import_picocolors16.default.yellow("Branches are not fully aligned")}`);
|
|
12927
13636
|
}
|
|
12928
13637
|
}
|
|
12929
13638
|
}
|
|
@@ -12931,74 +13640,50 @@ var status_default = defineCommand({
|
|
|
12931
13640
|
if (hasFiles) {
|
|
12932
13641
|
console.log();
|
|
12933
13642
|
if (fileStatus.staged.length > 0) {
|
|
12934
|
-
console.log(` ${
|
|
13643
|
+
console.log(` ${import_picocolors16.default.green("Staged for commit:")}`);
|
|
12935
13644
|
for (const { file, status } of fileStatus.staged) {
|
|
12936
|
-
console.log(` ${
|
|
13645
|
+
console.log(` ${import_picocolors16.default.green("+")} ${import_picocolors16.default.dim(`${status}:`)} ${file}`);
|
|
12937
13646
|
}
|
|
12938
13647
|
}
|
|
12939
13648
|
if (fileStatus.modified.length > 0) {
|
|
12940
|
-
console.log(` ${
|
|
13649
|
+
console.log(` ${import_picocolors16.default.yellow("Unstaged changes:")}`);
|
|
12941
13650
|
for (const { file, status } of fileStatus.modified) {
|
|
12942
|
-
console.log(` ${
|
|
13651
|
+
console.log(` ${import_picocolors16.default.yellow("~")} ${import_picocolors16.default.dim(`${status}:`)} ${file}`);
|
|
12943
13652
|
}
|
|
12944
13653
|
}
|
|
12945
13654
|
if (fileStatus.untracked.length > 0) {
|
|
12946
|
-
console.log(` ${
|
|
13655
|
+
console.log(` ${import_picocolors16.default.red("Untracked files:")}`);
|
|
12947
13656
|
for (const file of fileStatus.untracked) {
|
|
12948
|
-
console.log(` ${
|
|
13657
|
+
console.log(` ${import_picocolors16.default.red("?")} ${file}`);
|
|
12949
13658
|
}
|
|
12950
13659
|
}
|
|
12951
13660
|
} else if (!dirty) {
|
|
12952
|
-
console.log(` ${
|
|
12953
|
-
}
|
|
12954
|
-
const tips = [];
|
|
12955
|
-
if (!branchesAligned) {
|
|
12956
|
-
tips.push(`Run ${import_picocolors15.default.bold("contrib sync")} to align your local branches with the remote`);
|
|
12957
|
-
}
|
|
12958
|
-
if (fileStatus.staged.length > 0) {
|
|
12959
|
-
tips.push(`Run ${import_picocolors15.default.bold("contrib commit")} to commit staged changes`);
|
|
12960
|
-
}
|
|
12961
|
-
if (fileStatus.modified.length > 0 || fileStatus.untracked.length > 0) {
|
|
12962
|
-
tips.push(`Run ${import_picocolors15.default.bold("contrib commit")} to stage and commit changes`);
|
|
12963
|
-
}
|
|
12964
|
-
if (isFeatureBranch && branchStatus) {
|
|
12965
|
-
if (branchStatus.merged) {
|
|
12966
|
-
tips.push(`Run ${import_picocolors15.default.bold("contrib clean")} to delete this merged branch`);
|
|
12967
|
-
} else if (branchStatus.stale) {
|
|
12968
|
-
tips.push(`Run ${import_picocolors15.default.bold("contrib sync")} to rebase on latest changes, or ${import_picocolors15.default.bold("contrib clean")} if no longer needed`);
|
|
12969
|
-
} else if (fileStatus.staged.length === 0 && fileStatus.modified.length === 0 && fileStatus.untracked.length === 0) {
|
|
12970
|
-
const branchDiv = await getDivergence(currentBranch, `${origin}/${currentBranch}`);
|
|
12971
|
-
if (branchDiv.ahead > 0) {
|
|
12972
|
-
tips.push(`Run ${import_picocolors15.default.bold("contrib submit")} to push and create/update your PR`);
|
|
12973
|
-
}
|
|
12974
|
-
}
|
|
12975
|
-
}
|
|
12976
|
-
if (tips.length > 0) {
|
|
12977
|
-
console.log();
|
|
12978
|
-
console.log(` ${import_picocolors15.default.dim("\uD83D\uDCA1 Tip:")}`);
|
|
12979
|
-
for (const tip of tips) {
|
|
12980
|
-
console.log(` ${import_picocolors15.default.dim(tip)}`);
|
|
12981
|
-
}
|
|
13661
|
+
console.log(` ${import_picocolors16.default.green("✓")} ${import_picocolors16.default.dim("Working tree clean")}`);
|
|
12982
13662
|
}
|
|
12983
13663
|
console.log();
|
|
12984
13664
|
}
|
|
12985
13665
|
});
|
|
12986
13666
|
function formatStatus(branch, base, ahead, behind) {
|
|
12987
|
-
const label =
|
|
13667
|
+
const label = import_picocolors16.default.bold(branch.padEnd(20));
|
|
12988
13668
|
if (ahead === 0 && behind === 0) {
|
|
12989
|
-
return ` ${
|
|
13669
|
+
return ` ${import_picocolors16.default.green("✓")} ${label} ${import_picocolors16.default.dim(`in sync with ${base}`)}`;
|
|
12990
13670
|
}
|
|
12991
13671
|
if (ahead > 0 && behind === 0) {
|
|
12992
|
-
return ` ${
|
|
13672
|
+
return ` ${import_picocolors16.default.yellow("↑")} ${label} ${import_picocolors16.default.yellow(`${ahead} commit${ahead !== 1 ? "s" : ""} ahead of ${base}`)}`;
|
|
12993
13673
|
}
|
|
12994
13674
|
if (behind > 0 && ahead === 0) {
|
|
12995
|
-
return ` ${
|
|
13675
|
+
return ` ${import_picocolors16.default.red("↓")} ${label} ${import_picocolors16.default.red(`${behind} commit${behind !== 1 ? "s" : ""} behind ${base}`)}`;
|
|
12996
13676
|
}
|
|
12997
|
-
return ` ${
|
|
13677
|
+
return ` ${import_picocolors16.default.red("⚡")} ${label} ${import_picocolors16.default.yellow(`${ahead} ahead`)}${import_picocolors16.default.dim(", ")}${import_picocolors16.default.red(`${behind} behind`)} ${import_picocolors16.default.dim(base)}`;
|
|
12998
13678
|
}
|
|
12999
13679
|
var STALE_THRESHOLD_DAYS = 14;
|
|
13000
13680
|
async function detectBranchStatus(branch, baseBranch) {
|
|
13001
|
-
const result = {
|
|
13681
|
+
const result = {
|
|
13682
|
+
merged: false,
|
|
13683
|
+
mergedReason: null,
|
|
13684
|
+
stale: false,
|
|
13685
|
+
staleDaysAgo: null
|
|
13686
|
+
};
|
|
13002
13687
|
const div = await getDivergence(branch, baseBranch);
|
|
13003
13688
|
const hasWork = div.ahead > 0;
|
|
13004
13689
|
if (hasWork) {
|
|
@@ -13040,34 +13725,47 @@ async function detectBranchStatus(branch, baseBranch) {
|
|
|
13040
13725
|
}
|
|
13041
13726
|
|
|
13042
13727
|
// src/commands/submit.ts
|
|
13043
|
-
var
|
|
13728
|
+
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
13044
13729
|
async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
13045
|
-
info(`Checking out ${
|
|
13730
|
+
info(`Checking out ${import_picocolors17.default.bold(baseBranch)}...`);
|
|
13046
13731
|
const coResult = await checkoutBranch(baseBranch);
|
|
13047
13732
|
if (coResult.exitCode !== 0) {
|
|
13048
13733
|
error(`Failed to checkout ${baseBranch}: ${coResult.stderr}`);
|
|
13049
13734
|
process.exit(1);
|
|
13050
13735
|
}
|
|
13051
|
-
info(`Squash merging ${
|
|
13736
|
+
info(`Squash merging ${import_picocolors17.default.bold(featureBranch)} into ${import_picocolors17.default.bold(baseBranch)}...`);
|
|
13052
13737
|
const mergeResult = await mergeSquash(featureBranch);
|
|
13053
13738
|
if (mergeResult.exitCode !== 0) {
|
|
13054
13739
|
error(`Squash merge failed: ${mergeResult.stderr}`);
|
|
13055
13740
|
process.exit(1);
|
|
13056
13741
|
}
|
|
13057
13742
|
let message = options?.defaultMsg;
|
|
13058
|
-
if (!message) {
|
|
13059
|
-
const copilotError = await
|
|
13743
|
+
if (!message && options?.useAI !== false) {
|
|
13744
|
+
const copilotError = await checkCopilotAvailable2();
|
|
13060
13745
|
if (!copilotError) {
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
|
|
13068
|
-
|
|
13069
|
-
|
|
13746
|
+
while (!message) {
|
|
13747
|
+
const spinner = createSpinner("Generating AI commit message for squash merge...", {
|
|
13748
|
+
tips: LOADING_TIPS
|
|
13749
|
+
});
|
|
13750
|
+
const [stagedDiff, stagedFiles] = await Promise.all([getStagedDiff(), getStagedFiles()]);
|
|
13751
|
+
const aiMsg = await generateCommitMessage(stagedDiff, stagedFiles, options?.model, options?.convention ?? "clean-commit", "squash-merge");
|
|
13752
|
+
if (aiMsg) {
|
|
13753
|
+
message = aiMsg;
|
|
13754
|
+
spinner.success("AI commit message generated.");
|
|
13755
|
+
console.log(`
|
|
13756
|
+
${import_picocolors17.default.dim("AI suggestion:")} ${import_picocolors17.default.bold(import_picocolors17.default.cyan(message))}`);
|
|
13757
|
+
break;
|
|
13758
|
+
}
|
|
13070
13759
|
spinner.fail("AI did not return a commit message.");
|
|
13760
|
+
const retryAction = await selectPrompt("AI could not generate a commit message. What would you like to do?", ["Try again with AI", "Write manually", "Cancel"]);
|
|
13761
|
+
if (retryAction === "Try again with AI") {
|
|
13762
|
+
continue;
|
|
13763
|
+
}
|
|
13764
|
+
if (retryAction === "Cancel") {
|
|
13765
|
+
warn("Squash merge commit cancelled.");
|
|
13766
|
+
process.exit(0);
|
|
13767
|
+
}
|
|
13768
|
+
break;
|
|
13071
13769
|
}
|
|
13072
13770
|
} else {
|
|
13073
13771
|
warn(`AI unavailable: ${copilotError}`);
|
|
@@ -13076,28 +13774,28 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
13076
13774
|
let finalMsg = null;
|
|
13077
13775
|
if (message) {
|
|
13078
13776
|
while (!finalMsg) {
|
|
13079
|
-
const
|
|
13080
|
-
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
]);
|
|
13777
|
+
const actions = ["Accept this message", "Edit this message", "Write manually"];
|
|
13778
|
+
if (options?.useAI !== false) {
|
|
13779
|
+
actions.splice(2, 0, "Regenerate");
|
|
13780
|
+
}
|
|
13781
|
+
const action = await selectPrompt("What would you like to do?", actions);
|
|
13085
13782
|
if (action === "Accept this message") {
|
|
13086
13783
|
finalMsg = message;
|
|
13087
13784
|
} else if (action === "Edit this message") {
|
|
13088
13785
|
finalMsg = await inputPrompt("Edit commit message", message);
|
|
13089
13786
|
} else if (action === "Regenerate") {
|
|
13090
|
-
const spinner = createSpinner("Regenerating commit message..."
|
|
13787
|
+
const spinner = createSpinner("Regenerating commit message...", {
|
|
13788
|
+
tips: LOADING_TIPS
|
|
13789
|
+
});
|
|
13091
13790
|
const [stagedDiff, stagedFiles] = await Promise.all([getStagedDiff(), getStagedFiles()]);
|
|
13092
13791
|
const regen = await generateCommitMessage(stagedDiff, stagedFiles, options?.model, options?.convention ?? "clean-commit", "squash-merge");
|
|
13093
13792
|
if (regen) {
|
|
13094
13793
|
message = regen;
|
|
13095
13794
|
spinner.success("Commit message regenerated.");
|
|
13096
13795
|
console.log(`
|
|
13097
|
-
${
|
|
13796
|
+
${import_picocolors17.default.dim("AI suggestion:")} ${import_picocolors17.default.bold(import_picocolors17.default.cyan(regen))}`);
|
|
13098
13797
|
} else {
|
|
13099
13798
|
spinner.fail("Regeneration failed.");
|
|
13100
|
-
finalMsg = await inputPrompt("Enter commit message");
|
|
13101
13799
|
}
|
|
13102
13800
|
} else {
|
|
13103
13801
|
finalMsg = await inputPrompt("Enter commit message");
|
|
@@ -13111,13 +13809,13 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
13111
13809
|
error(`Commit failed: ${commitResult.stderr}`);
|
|
13112
13810
|
process.exit(1);
|
|
13113
13811
|
}
|
|
13114
|
-
info(`Pushing ${
|
|
13812
|
+
info(`Pushing ${import_picocolors17.default.bold(baseBranch)} to ${origin}...`);
|
|
13115
13813
|
const pushResult = await pushBranch(origin, baseBranch);
|
|
13116
13814
|
if (pushResult.exitCode !== 0) {
|
|
13117
13815
|
error(`Failed to push ${baseBranch}: ${pushResult.stderr}`);
|
|
13118
13816
|
process.exit(1);
|
|
13119
13817
|
}
|
|
13120
|
-
info(`Deleting local branch ${
|
|
13818
|
+
info(`Deleting local branch ${import_picocolors17.default.bold(featureBranch)}...`);
|
|
13121
13819
|
const delLocal = await forceDeleteBranch(featureBranch);
|
|
13122
13820
|
if (delLocal.exitCode !== 0) {
|
|
13123
13821
|
warn(`Could not delete local branch: ${delLocal.stderr.trim()}`);
|
|
@@ -13125,14 +13823,14 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
13125
13823
|
const remoteBranchRef = `${origin}/${featureBranch}`;
|
|
13126
13824
|
const remoteExists = await branchExists(remoteBranchRef);
|
|
13127
13825
|
if (remoteExists) {
|
|
13128
|
-
info(`Deleting remote branch ${
|
|
13826
|
+
info(`Deleting remote branch ${import_picocolors17.default.bold(featureBranch)}...`);
|
|
13129
13827
|
const delRemote = await deleteRemoteBranch(origin, featureBranch);
|
|
13130
13828
|
if (delRemote.exitCode !== 0) {
|
|
13131
13829
|
warn(`Could not delete remote branch: ${delRemote.stderr.trim()}`);
|
|
13132
13830
|
}
|
|
13133
13831
|
}
|
|
13134
|
-
success(`Squash merged ${
|
|
13135
|
-
info(`Run ${
|
|
13832
|
+
success(`Squash merged ${import_picocolors17.default.bold(featureBranch)} into ${import_picocolors17.default.bold(baseBranch)} and pushed.`);
|
|
13833
|
+
info(`Run ${import_picocolors17.default.bold("contrib start")} to begin a new feature.`, "");
|
|
13136
13834
|
}
|
|
13137
13835
|
var submit_default = defineCommand({
|
|
13138
13836
|
meta: {
|
|
@@ -13145,6 +13843,18 @@ var submit_default = defineCommand({
|
|
|
13145
13843
|
description: "Create PR as draft",
|
|
13146
13844
|
default: false
|
|
13147
13845
|
},
|
|
13846
|
+
pullrequest: {
|
|
13847
|
+
type: "boolean",
|
|
13848
|
+
alias: "pr",
|
|
13849
|
+
description: "Submit directly to PR flow without prompting for mode",
|
|
13850
|
+
default: false
|
|
13851
|
+
},
|
|
13852
|
+
local: {
|
|
13853
|
+
type: "boolean",
|
|
13854
|
+
alias: "l",
|
|
13855
|
+
description: "Squash merge locally without PR (maintainers only)",
|
|
13856
|
+
default: false
|
|
13857
|
+
},
|
|
13148
13858
|
"no-ai": {
|
|
13149
13859
|
type: "boolean",
|
|
13150
13860
|
description: "Skip AI PR description generation",
|
|
@@ -13163,10 +13873,11 @@ var submit_default = defineCommand({
|
|
|
13163
13873
|
await assertCleanGitState("submitting");
|
|
13164
13874
|
const config = readConfig();
|
|
13165
13875
|
if (!config) {
|
|
13166
|
-
error("No
|
|
13876
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
13167
13877
|
process.exit(1);
|
|
13168
13878
|
}
|
|
13169
13879
|
const { origin } = config;
|
|
13880
|
+
const aiEnabled = isAIEnabled(config, args["no-ai"]);
|
|
13170
13881
|
const baseBranch = getBaseBranch(config);
|
|
13171
13882
|
const protectedBranches = getProtectedBranches(config);
|
|
13172
13883
|
const currentBranch = await getCurrentBranch();
|
|
@@ -13175,8 +13886,8 @@ var submit_default = defineCommand({
|
|
|
13175
13886
|
process.exit(1);
|
|
13176
13887
|
}
|
|
13177
13888
|
if (protectedBranches.includes(currentBranch)) {
|
|
13178
|
-
|
|
13179
|
-
warn(`You're on ${
|
|
13889
|
+
projectHeading("submit", "\uD83D\uDE80");
|
|
13890
|
+
warn(`You're on ${import_picocolors17.default.bold(currentBranch)}, which is a protected branch. PRs should come from feature branches.`);
|
|
13180
13891
|
await fetchAll();
|
|
13181
13892
|
const remoteRef = `${origin}/${currentBranch}`;
|
|
13182
13893
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
@@ -13185,11 +13896,11 @@ var submit_default = defineCommand({
|
|
|
13185
13896
|
const hasAnything = hasCommits || dirty;
|
|
13186
13897
|
if (!hasAnything) {
|
|
13187
13898
|
error("No local changes or commits to move. Switch to a feature branch first.");
|
|
13188
|
-
info(` Run ${
|
|
13899
|
+
info(` Run ${import_picocolors17.default.bold("contrib start")} to create a new feature branch.`, "");
|
|
13189
13900
|
process.exit(1);
|
|
13190
13901
|
}
|
|
13191
13902
|
if (hasCommits) {
|
|
13192
|
-
info(`Found ${
|
|
13903
|
+
info(`Found ${import_picocolors17.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors17.default.bold(currentBranch)}.`);
|
|
13193
13904
|
}
|
|
13194
13905
|
if (dirty) {
|
|
13195
13906
|
info("You also have uncommitted changes in the working tree.");
|
|
@@ -13205,58 +13916,35 @@ var submit_default = defineCommand({
|
|
|
13205
13916
|
info("No changes made. You are still on your current branch.");
|
|
13206
13917
|
return;
|
|
13207
13918
|
}
|
|
13208
|
-
|
|
13209
|
-
|
|
13210
|
-
|
|
13211
|
-
|
|
13212
|
-
|
|
13213
|
-
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
if (suggested) {
|
|
13217
|
-
spinner.success("Branch name suggestion ready.");
|
|
13218
|
-
console.log(`
|
|
13219
|
-
${import_picocolors16.default.dim("AI suggestion:")} ${import_picocolors16.default.bold(import_picocolors16.default.cyan(suggested))}`);
|
|
13220
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors16.default.bold(suggested)} as your branch name?`);
|
|
13221
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
13222
|
-
} else {
|
|
13223
|
-
spinner.fail("AI did not return a suggestion.");
|
|
13224
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
13225
|
-
}
|
|
13226
|
-
}
|
|
13227
|
-
}
|
|
13228
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
13229
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors16.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
13230
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
13231
|
-
}
|
|
13232
|
-
if (!isValidBranchName(newBranchName)) {
|
|
13233
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
13234
|
-
process.exit(1);
|
|
13235
|
-
}
|
|
13236
|
-
if (await branchExists(newBranchName)) {
|
|
13237
|
-
error(`Branch ${import_picocolors16.default.bold(newBranchName)} already exists. Choose a different name.`);
|
|
13238
|
-
process.exit(1);
|
|
13919
|
+
const newBranchName = await promptForBranchName({
|
|
13920
|
+
branchPrefixes: config.branchPrefixes,
|
|
13921
|
+
useAI: aiEnabled,
|
|
13922
|
+
model: args.model
|
|
13923
|
+
});
|
|
13924
|
+
if (!newBranchName) {
|
|
13925
|
+
info("No changes made. You are still on your current branch.");
|
|
13926
|
+
return;
|
|
13239
13927
|
}
|
|
13240
13928
|
const branchResult = await createBranch(newBranchName);
|
|
13241
13929
|
if (branchResult.exitCode !== 0) {
|
|
13242
13930
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
13243
13931
|
process.exit(1);
|
|
13244
13932
|
}
|
|
13245
|
-
success(`Created ${
|
|
13933
|
+
success(`Created ${import_picocolors17.default.bold(newBranchName)} with your changes.`);
|
|
13246
13934
|
await updateLocalBranch(currentBranch, remoteRef);
|
|
13247
|
-
info(`Reset ${
|
|
13935
|
+
info(`Reset ${import_picocolors17.default.bold(currentBranch)} back to ${import_picocolors17.default.bold(remoteRef)} — no damage done.`, "");
|
|
13248
13936
|
console.log();
|
|
13249
|
-
success(`You're now on ${
|
|
13250
|
-
info(`Run ${
|
|
13937
|
+
success(`You're now on ${import_picocolors17.default.bold(newBranchName)} with all your work intact.`);
|
|
13938
|
+
info(`Run ${import_picocolors17.default.bold("contrib submit")} again to push and create your PR.`, "");
|
|
13251
13939
|
return;
|
|
13252
13940
|
}
|
|
13253
|
-
|
|
13941
|
+
projectHeading("submit", "\uD83D\uDE80");
|
|
13254
13942
|
const ghInstalled = await checkGhInstalled();
|
|
13255
13943
|
const ghAuthed = ghInstalled && await checkGhAuth();
|
|
13256
13944
|
if (ghInstalled && ghAuthed) {
|
|
13257
13945
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
13258
13946
|
if (mergedPR) {
|
|
13259
|
-
warn(`PR #${mergedPR.number} (${
|
|
13947
|
+
warn(`PR #${mergedPR.number} (${import_picocolors17.default.bold(mergedPR.title)}) was already merged.`);
|
|
13260
13948
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
13261
13949
|
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
13262
13950
|
if (hasWork) {
|
|
@@ -13264,7 +13952,7 @@ var submit_default = defineCommand({
|
|
|
13264
13952
|
warn("You have uncommitted changes in your working tree.");
|
|
13265
13953
|
}
|
|
13266
13954
|
if (localWork.unpushedCommits > 0) {
|
|
13267
|
-
warn(`You have ${
|
|
13955
|
+
warn(`You have ${import_picocolors17.default.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not in the merged PR.`);
|
|
13268
13956
|
}
|
|
13269
13957
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
13270
13958
|
const DISCARD = "Discard all changes and clean up";
|
|
@@ -13275,46 +13963,26 @@ var submit_default = defineCommand({
|
|
|
13275
13963
|
return;
|
|
13276
13964
|
}
|
|
13277
13965
|
if (action === SAVE_NEW_BRANCH) {
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
13286
|
-
console.log(`
|
|
13287
|
-
${import_picocolors16.default.dim("AI suggestion:")} ${import_picocolors16.default.bold(import_picocolors16.default.cyan(suggested))}`);
|
|
13288
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors16.default.bold(suggested)} as your branch name?`);
|
|
13289
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
13290
|
-
} else {
|
|
13291
|
-
spinner.fail("AI did not return a suggestion.");
|
|
13292
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
13293
|
-
}
|
|
13294
|
-
}
|
|
13295
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
13296
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors16.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
13297
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
13298
|
-
}
|
|
13299
|
-
if (!isValidBranchName(newBranchName)) {
|
|
13300
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
13301
|
-
process.exit(1);
|
|
13966
|
+
const newBranchName = await promptForBranchName({
|
|
13967
|
+
branchPrefixes: config.branchPrefixes,
|
|
13968
|
+
useAI: aiEnabled,
|
|
13969
|
+
model: args.model
|
|
13970
|
+
});
|
|
13971
|
+
if (!newBranchName) {
|
|
13972
|
+
info("No changes made. You are still on your current branch.");
|
|
13973
|
+
return;
|
|
13302
13974
|
}
|
|
13303
13975
|
const staleUpstream = await getUpstreamRef();
|
|
13304
13976
|
const staleUpstreamHash = staleUpstream ? await getCommitHash(staleUpstream) : null;
|
|
13305
|
-
if (await branchExists(newBranchName)) {
|
|
13306
|
-
error(`Branch ${import_picocolors16.default.bold(newBranchName)} already exists. Choose a different name.`);
|
|
13307
|
-
process.exit(1);
|
|
13308
|
-
}
|
|
13309
13977
|
const renameResult = await renameBranch(currentBranch, newBranchName);
|
|
13310
13978
|
if (renameResult.exitCode !== 0) {
|
|
13311
13979
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
13312
13980
|
process.exit(1);
|
|
13313
13981
|
}
|
|
13314
|
-
success(`Renamed ${
|
|
13982
|
+
success(`Renamed ${import_picocolors17.default.bold(currentBranch)} → ${import_picocolors17.default.bold(newBranchName)}`);
|
|
13315
13983
|
await unsetUpstream();
|
|
13316
13984
|
const syncSource2 = getSyncSource(config);
|
|
13317
|
-
info(`Syncing ${
|
|
13985
|
+
info(`Syncing ${import_picocolors17.default.bold(newBranchName)} with latest ${import_picocolors17.default.bold(baseBranch)}...`);
|
|
13318
13986
|
await fetchRemote(syncSource2.remote);
|
|
13319
13987
|
let rebaseResult;
|
|
13320
13988
|
if (staleUpstreamHash) {
|
|
@@ -13325,17 +13993,17 @@ var submit_default = defineCommand({
|
|
|
13325
13993
|
}
|
|
13326
13994
|
if (rebaseResult.exitCode !== 0) {
|
|
13327
13995
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
13328
|
-
info(` ${
|
|
13996
|
+
info(` ${import_picocolors17.default.bold("git rebase --continue")}`, "");
|
|
13329
13997
|
} else {
|
|
13330
|
-
success(`Rebased ${
|
|
13998
|
+
success(`Rebased ${import_picocolors17.default.bold(newBranchName)} onto ${import_picocolors17.default.bold(syncSource2.ref)}.`);
|
|
13331
13999
|
}
|
|
13332
|
-
info(`All your changes are preserved. Run ${
|
|
14000
|
+
info(`All your changes are preserved. Run ${import_picocolors17.default.bold("contrib submit")} when ready to create a new PR.`, "");
|
|
13333
14001
|
return;
|
|
13334
14002
|
}
|
|
13335
14003
|
warn("Discarding local changes...");
|
|
13336
14004
|
}
|
|
13337
14005
|
const syncSource = getSyncSource(config);
|
|
13338
|
-
info(`Switching to ${
|
|
14006
|
+
info(`Switching to ${import_picocolors17.default.bold(baseBranch)} and syncing...`);
|
|
13339
14007
|
await fetchRemote(syncSource.remote);
|
|
13340
14008
|
await resetHard("HEAD");
|
|
13341
14009
|
const coResult = await checkoutBranch(baseBranch);
|
|
@@ -13344,23 +14012,23 @@ var submit_default = defineCommand({
|
|
|
13344
14012
|
process.exit(1);
|
|
13345
14013
|
}
|
|
13346
14014
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
13347
|
-
success(`Synced ${
|
|
13348
|
-
info(`Deleting stale branch ${
|
|
14015
|
+
success(`Synced ${import_picocolors17.default.bold(baseBranch)} with ${import_picocolors17.default.bold(syncSource.ref)}.`);
|
|
14016
|
+
info(`Deleting stale branch ${import_picocolors17.default.bold(currentBranch)}...`);
|
|
13349
14017
|
const delResult = await forceDeleteBranch(currentBranch);
|
|
13350
14018
|
if (delResult.exitCode === 0) {
|
|
13351
|
-
success(`Deleted ${
|
|
14019
|
+
success(`Deleted ${import_picocolors17.default.bold(currentBranch)}.`);
|
|
13352
14020
|
} else {
|
|
13353
14021
|
warn(`Could not delete branch: ${delResult.stderr.trim()}`);
|
|
13354
14022
|
}
|
|
13355
14023
|
console.log();
|
|
13356
|
-
info(`You're now on ${
|
|
14024
|
+
info(`You're now on ${import_picocolors17.default.bold(baseBranch)}. Run ${import_picocolors17.default.bold("contrib start")} to begin a new feature.`);
|
|
13357
14025
|
return;
|
|
13358
14026
|
}
|
|
13359
14027
|
}
|
|
13360
14028
|
if (ghInstalled && ghAuthed) {
|
|
13361
14029
|
const existingPR = await getPRForBranch(currentBranch);
|
|
13362
14030
|
if (existingPR) {
|
|
13363
|
-
info(`Pushing ${
|
|
14031
|
+
info(`Pushing ${import_picocolors17.default.bold(currentBranch)} to ${origin}...`);
|
|
13364
14032
|
const pushResult2 = await pushSetUpstream(origin, currentBranch);
|
|
13365
14033
|
if (pushResult2.exitCode !== 0) {
|
|
13366
14034
|
error(`Failed to push: ${pushResult2.stderr}`);
|
|
@@ -13371,8 +14039,8 @@ var submit_default = defineCommand({
|
|
|
13371
14039
|
}
|
|
13372
14040
|
process.exit(1);
|
|
13373
14041
|
}
|
|
13374
|
-
success(`Pushed changes to existing PR #${existingPR.number}: ${
|
|
13375
|
-
console.log(` ${
|
|
14042
|
+
success(`Pushed changes to existing PR #${existingPR.number}: ${import_picocolors17.default.bold(existingPR.title)}`);
|
|
14043
|
+
console.log(` ${import_picocolors17.default.cyan(existingPR.url)}`);
|
|
13376
14044
|
return;
|
|
13377
14045
|
}
|
|
13378
14046
|
}
|
|
@@ -13380,22 +14048,24 @@ var submit_default = defineCommand({
|
|
|
13380
14048
|
let prBody = null;
|
|
13381
14049
|
async function tryGenerateAI() {
|
|
13382
14050
|
const [copilotError, commits, diff] = await Promise.all([
|
|
13383
|
-
|
|
14051
|
+
checkCopilotAvailable2(),
|
|
13384
14052
|
getLog(baseBranch, "HEAD"),
|
|
13385
14053
|
getLogDiff(baseBranch, "HEAD")
|
|
13386
14054
|
]);
|
|
13387
14055
|
if (!copilotError) {
|
|
13388
|
-
const spinner = createSpinner("Generating AI PR description..."
|
|
14056
|
+
const spinner = createSpinner("Generating AI PR description...", {
|
|
14057
|
+
tips: LOADING_TIPS
|
|
14058
|
+
});
|
|
13389
14059
|
const result = await generatePRDescription(commits, diff, args.model, config.commitConvention);
|
|
13390
14060
|
if (result) {
|
|
13391
14061
|
prTitle = result.title;
|
|
13392
14062
|
prBody = result.body;
|
|
13393
14063
|
spinner.success("PR description generated.");
|
|
13394
14064
|
console.log(`
|
|
13395
|
-
${
|
|
14065
|
+
${import_picocolors17.default.dim("AI title:")} ${import_picocolors17.default.bold(import_picocolors17.default.cyan(prTitle))}`);
|
|
13396
14066
|
console.log(`
|
|
13397
|
-
${
|
|
13398
|
-
console.log(
|
|
14067
|
+
${import_picocolors17.default.dim("AI body preview:")}`);
|
|
14068
|
+
console.log(import_picocolors17.default.dim(prBody.slice(0, 300) + (prBody.length > 300 ? "..." : "")));
|
|
13399
14069
|
} else {
|
|
13400
14070
|
spinner.fail("AI did not return a PR description.");
|
|
13401
14071
|
}
|
|
@@ -13408,8 +14078,28 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13408
14078
|
const REGENERATE = "Regenerate AI description";
|
|
13409
14079
|
let submitAction = "cancel";
|
|
13410
14080
|
const isMaintainer = config.role === "maintainer";
|
|
13411
|
-
if (
|
|
13412
|
-
|
|
14081
|
+
if (args.pullrequest && args.local) {
|
|
14082
|
+
error("Use only one submit mode flag at a time: --pullrequest/--pr/-pr or -l for local squash merge.");
|
|
14083
|
+
process.exit(1);
|
|
14084
|
+
}
|
|
14085
|
+
if (args.local && !isMaintainer) {
|
|
14086
|
+
error("The -l flag is only available for maintainers. Contributors must submit via PR.");
|
|
14087
|
+
process.exit(1);
|
|
14088
|
+
}
|
|
14089
|
+
if (args.local) {
|
|
14090
|
+
await performSquashMerge(origin, baseBranch, currentBranch, {
|
|
14091
|
+
model: args.model,
|
|
14092
|
+
convention: config.commitConvention,
|
|
14093
|
+
useAI: aiEnabled
|
|
14094
|
+
});
|
|
14095
|
+
return;
|
|
14096
|
+
}
|
|
14097
|
+
if (isMaintainer && !args.pullrequest) {
|
|
14098
|
+
const maintainerChoice = await selectPrompt("How would you like to submit your changes?", [
|
|
14099
|
+
"Create a PR",
|
|
14100
|
+
SQUASH_LOCAL,
|
|
14101
|
+
CANCEL
|
|
14102
|
+
]);
|
|
13413
14103
|
if (maintainerChoice === CANCEL) {
|
|
13414
14104
|
warn("Submit cancelled.");
|
|
13415
14105
|
return;
|
|
@@ -13417,12 +14107,13 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13417
14107
|
if (maintainerChoice === SQUASH_LOCAL) {
|
|
13418
14108
|
await performSquashMerge(origin, baseBranch, currentBranch, {
|
|
13419
14109
|
model: args.model,
|
|
13420
|
-
convention: config.commitConvention
|
|
14110
|
+
convention: config.commitConvention,
|
|
14111
|
+
useAI: aiEnabled
|
|
13421
14112
|
});
|
|
13422
14113
|
return;
|
|
13423
14114
|
}
|
|
13424
14115
|
}
|
|
13425
|
-
if (
|
|
14116
|
+
if (aiEnabled) {
|
|
13426
14117
|
await tryGenerateAI();
|
|
13427
14118
|
}
|
|
13428
14119
|
let actionResolved = false;
|
|
@@ -13461,7 +14152,7 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13461
14152
|
}
|
|
13462
14153
|
} else {
|
|
13463
14154
|
const choices = [];
|
|
13464
|
-
if (
|
|
14155
|
+
if (aiEnabled)
|
|
13465
14156
|
choices.push(REGENERATE);
|
|
13466
14157
|
choices.push("Write title & body manually", "Use gh --fill (auto-fill from commits)", CANCEL);
|
|
13467
14158
|
const action = await selectPrompt("How would you like to create the PR?", choices);
|
|
@@ -13485,7 +14176,7 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13485
14176
|
warn("Submit cancelled.");
|
|
13486
14177
|
return;
|
|
13487
14178
|
}
|
|
13488
|
-
info(`Pushing ${
|
|
14179
|
+
info(`Pushing ${import_picocolors17.default.bold(currentBranch)} to ${origin}...`);
|
|
13489
14180
|
const pushResult = await pushSetUpstream(origin, currentBranch);
|
|
13490
14181
|
if (pushResult.exitCode !== 0) {
|
|
13491
14182
|
error(`Failed to push: ${pushResult.stderr}`);
|
|
@@ -13504,7 +14195,7 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13504
14195
|
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/compare/${baseBranch}...${currentBranch}?expand=1`;
|
|
13505
14196
|
console.log();
|
|
13506
14197
|
info("Create your PR manually:", "");
|
|
13507
|
-
console.log(` ${
|
|
14198
|
+
console.log(` ${import_picocolors17.default.cyan(prUrl)}`);
|
|
13508
14199
|
} else {
|
|
13509
14200
|
info("gh CLI not available. Create your PR manually on GitHub.", "");
|
|
13510
14201
|
}
|
|
@@ -13538,7 +14229,7 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13538
14229
|
});
|
|
13539
14230
|
|
|
13540
14231
|
// src/commands/switch.ts
|
|
13541
|
-
var
|
|
14232
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
13542
14233
|
var switch_default = defineCommand({
|
|
13543
14234
|
meta: {
|
|
13544
14235
|
name: "switch",
|
|
@@ -13559,7 +14250,7 @@ var switch_default = defineCommand({
|
|
|
13559
14250
|
const config = readConfig();
|
|
13560
14251
|
const protectedBranches = config ? getProtectedBranches(config) : ["main", "master"];
|
|
13561
14252
|
const currentBranch = await getCurrentBranch();
|
|
13562
|
-
|
|
14253
|
+
projectHeading("switch", "\uD83D\uDD00");
|
|
13563
14254
|
let targetBranch = args.name;
|
|
13564
14255
|
if (!targetBranch) {
|
|
13565
14256
|
const localBranches = await getLocalBranches();
|
|
@@ -13570,11 +14261,11 @@ var switch_default = defineCommand({
|
|
|
13570
14261
|
const choices = localBranches.filter((b2) => b2.name !== currentBranch).map((b2) => {
|
|
13571
14262
|
const labels = [];
|
|
13572
14263
|
if (protectedBranches.includes(b2.name))
|
|
13573
|
-
labels.push(
|
|
14264
|
+
labels.push(import_picocolors18.default.red("protected"));
|
|
13574
14265
|
if (b2.upstream)
|
|
13575
|
-
labels.push(
|
|
14266
|
+
labels.push(import_picocolors18.default.dim(`→ ${b2.upstream}`));
|
|
13576
14267
|
if (b2.gone)
|
|
13577
|
-
labels.push(
|
|
14268
|
+
labels.push(import_picocolors18.default.red("remote gone"));
|
|
13578
14269
|
const suffix = labels.length > 0 ? ` ${labels.join(" · ")}` : "";
|
|
13579
14270
|
return `${b2.name}${suffix}`;
|
|
13580
14271
|
});
|
|
@@ -13586,7 +14277,7 @@ var switch_default = defineCommand({
|
|
|
13586
14277
|
targetBranch = selected.split(/\s{2,}/)[0].trim();
|
|
13587
14278
|
}
|
|
13588
14279
|
if (targetBranch === currentBranch) {
|
|
13589
|
-
info(`Already on ${
|
|
14280
|
+
info(`Already on ${import_picocolors18.default.bold(targetBranch)}.`);
|
|
13590
14281
|
return;
|
|
13591
14282
|
}
|
|
13592
14283
|
if (await hasUncommittedChanges()) {
|
|
@@ -13605,7 +14296,7 @@ var switch_default = defineCommand({
|
|
|
13605
14296
|
const stashMsg = `contrib-save: auto-save from ${currentBranch}`;
|
|
13606
14297
|
try {
|
|
13607
14298
|
await exec("git", ["stash", "push", "-m", stashMsg]);
|
|
13608
|
-
info(`Saved changes: ${
|
|
14299
|
+
info(`Saved changes: ${import_picocolors18.default.dim(stashMsg)}`);
|
|
13609
14300
|
} catch {
|
|
13610
14301
|
error("Failed to save changes. Please commit or save manually.");
|
|
13611
14302
|
process.exit(1);
|
|
@@ -13621,9 +14312,9 @@ var switch_default = defineCommand({
|
|
|
13621
14312
|
}
|
|
13622
14313
|
process.exit(1);
|
|
13623
14314
|
}
|
|
13624
|
-
success(`Switched to ${
|
|
13625
|
-
info(`Your changes from ${
|
|
13626
|
-
info(`Use ${
|
|
14315
|
+
success(`Switched to ${import_picocolors18.default.bold(targetBranch)}`);
|
|
14316
|
+
info(`Your changes from ${import_picocolors18.default.bold(currentBranch ?? "previous branch")} are saved.`, "");
|
|
14317
|
+
info(`Use ${import_picocolors18.default.bold("contrib save --restore")} to bring them back.`, "");
|
|
13627
14318
|
return;
|
|
13628
14319
|
}
|
|
13629
14320
|
const result = await checkoutBranch(targetBranch);
|
|
@@ -13631,12 +14322,12 @@ var switch_default = defineCommand({
|
|
|
13631
14322
|
error(`Failed to switch to ${targetBranch}: ${result.stderr}`);
|
|
13632
14323
|
process.exit(1);
|
|
13633
14324
|
}
|
|
13634
|
-
success(`Switched to ${
|
|
14325
|
+
success(`Switched to ${import_picocolors18.default.bold(targetBranch)}`);
|
|
13635
14326
|
}
|
|
13636
14327
|
});
|
|
13637
14328
|
|
|
13638
14329
|
// src/commands/sync.ts
|
|
13639
|
-
var
|
|
14330
|
+
var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
13640
14331
|
var sync_default = defineCommand({
|
|
13641
14332
|
meta: {
|
|
13642
14333
|
name: "sync",
|
|
@@ -13667,7 +14358,7 @@ var sync_default = defineCommand({
|
|
|
13667
14358
|
await assertCleanGitState("syncing");
|
|
13668
14359
|
const config = readConfig();
|
|
13669
14360
|
if (!config) {
|
|
13670
|
-
error("No
|
|
14361
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
13671
14362
|
process.exit(1);
|
|
13672
14363
|
}
|
|
13673
14364
|
const { workflow, role, origin } = config;
|
|
@@ -13675,7 +14366,7 @@ var sync_default = defineCommand({
|
|
|
13675
14366
|
error("You have uncommitted changes. Please commit or stash them before syncing.");
|
|
13676
14367
|
process.exit(1);
|
|
13677
14368
|
}
|
|
13678
|
-
|
|
14369
|
+
projectHeading(`sync (${workflow}, ${role})`, "\uD83D\uDD04");
|
|
13679
14370
|
const baseBranch = getBaseBranch(config);
|
|
13680
14371
|
const syncSource = getSyncSource(config);
|
|
13681
14372
|
info(`Fetching ${syncSource.remote}...`);
|
|
@@ -13688,24 +14379,24 @@ var sync_default = defineCommand({
|
|
|
13688
14379
|
await fetchRemote(origin);
|
|
13689
14380
|
}
|
|
13690
14381
|
if (!await refExists(syncSource.ref)) {
|
|
13691
|
-
error(`Remote ref ${
|
|
14382
|
+
error(`Remote ref ${import_picocolors19.default.bold(syncSource.ref)} does not exist.`);
|
|
13692
14383
|
info("This can happen if the branch was renamed or deleted on the remote.", "");
|
|
13693
|
-
info(`Check your config: the base branch may need updating via ${
|
|
14384
|
+
info(`Check your config: the base branch may need updating via ${import_picocolors19.default.bold("contrib setup")}.`, "");
|
|
13694
14385
|
process.exit(1);
|
|
13695
14386
|
}
|
|
13696
14387
|
let allowMergeCommit = false;
|
|
13697
14388
|
const div = await getDivergence(baseBranch, syncSource.ref);
|
|
13698
14389
|
if (div.ahead > 0 || div.behind > 0) {
|
|
13699
|
-
info(`${
|
|
14390
|
+
info(`${import_picocolors19.default.bold(baseBranch)} is ${import_picocolors19.default.yellow(`${div.ahead} ahead`)} and ${import_picocolors19.default.red(`${div.behind} behind`)} ${syncSource.ref}`);
|
|
13700
14391
|
} else {
|
|
13701
|
-
info(`${
|
|
14392
|
+
info(`${import_picocolors19.default.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
|
|
13702
14393
|
}
|
|
13703
14394
|
if (div.ahead > 0) {
|
|
13704
14395
|
const currentBranch = await getCurrentBranch();
|
|
13705
14396
|
const protectedBranches = getProtectedBranches(config);
|
|
13706
14397
|
const isOnProtected = currentBranch && protectedBranches.includes(currentBranch);
|
|
13707
14398
|
if (isOnProtected) {
|
|
13708
|
-
warn(`You have ${
|
|
14399
|
+
warn(`You have ${import_picocolors19.default.bold(String(div.ahead))} local commit${div.ahead !== 1 ? "s" : ""} on ${import_picocolors19.default.bold(baseBranch)} that aren't on the remote.`);
|
|
13709
14400
|
info("Pulling now could create a merge commit, which breaks clean history.");
|
|
13710
14401
|
console.log();
|
|
13711
14402
|
const MOVE_BRANCH = "Move my commits to a new feature branch, then sync";
|
|
@@ -13721,44 +14412,21 @@ var sync_default = defineCommand({
|
|
|
13721
14412
|
return;
|
|
13722
14413
|
}
|
|
13723
14414
|
if (action === MOVE_BRANCH) {
|
|
13724
|
-
|
|
13725
|
-
|
|
13726
|
-
|
|
13727
|
-
|
|
13728
|
-
|
|
13729
|
-
|
|
13730
|
-
|
|
13731
|
-
|
|
13732
|
-
if (suggested) {
|
|
13733
|
-
spinner.success("Branch name suggestion ready.");
|
|
13734
|
-
console.log(`
|
|
13735
|
-
${import_picocolors18.default.dim("AI suggestion:")} ${import_picocolors18.default.bold(import_picocolors18.default.cyan(suggested))}`);
|
|
13736
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors18.default.bold(suggested)} as your branch name?`);
|
|
13737
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
13738
|
-
} else {
|
|
13739
|
-
spinner.fail("AI did not return a suggestion.");
|
|
13740
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
13741
|
-
}
|
|
13742
|
-
}
|
|
13743
|
-
}
|
|
13744
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
13745
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors18.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
13746
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
13747
|
-
}
|
|
13748
|
-
if (!isValidBranchName(newBranchName)) {
|
|
13749
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
13750
|
-
process.exit(1);
|
|
13751
|
-
}
|
|
13752
|
-
if (await branchExists(newBranchName)) {
|
|
13753
|
-
error(`Branch ${import_picocolors18.default.bold(newBranchName)} already exists. Choose a different name.`);
|
|
13754
|
-
process.exit(1);
|
|
14415
|
+
const newBranchName = await promptForBranchName({
|
|
14416
|
+
branchPrefixes: config.branchPrefixes,
|
|
14417
|
+
useAI: isAIEnabled(config, args["no-ai"]),
|
|
14418
|
+
model: args.model
|
|
14419
|
+
});
|
|
14420
|
+
if (!newBranchName) {
|
|
14421
|
+
info("No changes made.");
|
|
14422
|
+
return;
|
|
13755
14423
|
}
|
|
13756
14424
|
const branchResult = await createBranch(newBranchName);
|
|
13757
14425
|
if (branchResult.exitCode !== 0) {
|
|
13758
14426
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
13759
14427
|
process.exit(1);
|
|
13760
14428
|
}
|
|
13761
|
-
success(`Created ${
|
|
14429
|
+
success(`Created ${import_picocolors19.default.bold(newBranchName)} with your commits.`);
|
|
13762
14430
|
const coResult2 = await checkoutBranch(baseBranch);
|
|
13763
14431
|
if (coResult2.exitCode !== 0) {
|
|
13764
14432
|
error(`Failed to checkout ${baseBranch}: ${coResult2.stderr}`);
|
|
@@ -13766,11 +14434,11 @@ var sync_default = defineCommand({
|
|
|
13766
14434
|
}
|
|
13767
14435
|
const remoteRef = syncSource.ref;
|
|
13768
14436
|
await updateLocalBranch(baseBranch, remoteRef);
|
|
13769
|
-
success(`Reset ${
|
|
13770
|
-
success(`${
|
|
14437
|
+
success(`Reset ${import_picocolors19.default.bold(baseBranch)} to ${import_picocolors19.default.bold(remoteRef)}.`);
|
|
14438
|
+
success(`${import_picocolors19.default.bold(baseBranch)} is now in sync with ${syncSource.ref}`);
|
|
13771
14439
|
console.log();
|
|
13772
|
-
info(`Your commits are safe on ${
|
|
13773
|
-
info(`Run ${
|
|
14440
|
+
info(`Your commits are safe on ${import_picocolors19.default.bold(newBranchName)}.`, "");
|
|
14441
|
+
info(`Run ${import_picocolors19.default.bold(`git checkout ${newBranchName}`)} then ${import_picocolors19.default.bold("contrib update")} to rebase onto the synced ${import_picocolors19.default.bold(baseBranch)}.`, "");
|
|
13774
14442
|
return;
|
|
13775
14443
|
}
|
|
13776
14444
|
allowMergeCommit = true;
|
|
@@ -13778,7 +14446,7 @@ var sync_default = defineCommand({
|
|
|
13778
14446
|
}
|
|
13779
14447
|
}
|
|
13780
14448
|
if (!args.yes) {
|
|
13781
|
-
const ok = await confirmPrompt(`This will pull ${
|
|
14449
|
+
const ok = await confirmPrompt(`This will pull ${import_picocolors19.default.bold(syncSource.ref)} into local ${import_picocolors19.default.bold(baseBranch)}.`);
|
|
13782
14450
|
if (!ok)
|
|
13783
14451
|
process.exit(0);
|
|
13784
14452
|
}
|
|
@@ -13792,8 +14460,8 @@ var sync_default = defineCommand({
|
|
|
13792
14460
|
if (allowMergeCommit) {
|
|
13793
14461
|
error(`Pull failed: ${pullResult.stderr.trim()}`);
|
|
13794
14462
|
} else {
|
|
13795
|
-
error(`Fast-forward pull failed. Your local ${
|
|
13796
|
-
info(`Use ${
|
|
14463
|
+
error(`Fast-forward pull failed. Your local ${import_picocolors19.default.bold(baseBranch)} may have diverged.`);
|
|
14464
|
+
info(`Use ${import_picocolors19.default.bold("contrib sync")} again and choose "Move my commits to a new feature branch" to fix this.`, "");
|
|
13797
14465
|
}
|
|
13798
14466
|
process.exit(1);
|
|
13799
14467
|
}
|
|
@@ -13801,7 +14469,7 @@ var sync_default = defineCommand({
|
|
|
13801
14469
|
if (hasDevBranch(workflow) && role === "maintainer") {
|
|
13802
14470
|
const mainDiv = await getDivergence(config.mainBranch, `${origin}/${config.mainBranch}`);
|
|
13803
14471
|
if (mainDiv.behind > 0) {
|
|
13804
|
-
info(`Also syncing ${
|
|
14472
|
+
info(`Also syncing ${import_picocolors19.default.bold(config.mainBranch)}...`);
|
|
13805
14473
|
const mainCoResult = await checkoutBranch(config.mainBranch);
|
|
13806
14474
|
if (mainCoResult.exitCode === 0) {
|
|
13807
14475
|
const mainPullResult = await pullFastForwardOnly(origin, config.mainBranch);
|
|
@@ -13836,23 +14504,26 @@ var sync_default = defineCommand({
|
|
|
13836
14504
|
for (const { name, hash } of refs) {
|
|
13837
14505
|
if (!groups.has(hash))
|
|
13838
14506
|
groups.set(hash, []);
|
|
13839
|
-
groups.get(hash)
|
|
14507
|
+
const group = groups.get(hash);
|
|
14508
|
+
if (group) {
|
|
14509
|
+
group.push(name);
|
|
14510
|
+
}
|
|
13840
14511
|
}
|
|
13841
14512
|
console.log();
|
|
13842
|
-
console.log(` ${
|
|
14513
|
+
console.log(` ${import_picocolors19.default.bold("\uD83D\uDD17 Branch Alignment")}`);
|
|
13843
14514
|
for (const [hash, names] of groups) {
|
|
13844
14515
|
const short = hash.slice(0, 7);
|
|
13845
|
-
const nameStr = names.map((n2) =>
|
|
13846
|
-
console.log(` ${
|
|
14516
|
+
const nameStr = names.map((n2) => import_picocolors19.default.bold(n2)).join(import_picocolors19.default.dim(" · "));
|
|
14517
|
+
console.log(` ${import_picocolors19.default.yellow(short)} ${import_picocolors19.default.dim("──")} ${nameStr}`);
|
|
13847
14518
|
const subject = await getCommitSubject(hash);
|
|
13848
14519
|
if (subject) {
|
|
13849
|
-
console.log(` ${
|
|
14520
|
+
console.log(` ${import_picocolors19.default.dim(subject)}`);
|
|
13850
14521
|
}
|
|
13851
14522
|
}
|
|
13852
14523
|
if (groups.size === 1) {
|
|
13853
|
-
console.log(` ${
|
|
14524
|
+
console.log(` ${import_picocolors19.default.green("✓")} ${import_picocolors19.default.green("All branches aligned")} ${import_picocolors19.default.dim("— ready to start")}`);
|
|
13854
14525
|
} else {
|
|
13855
|
-
console.log(` ${
|
|
14526
|
+
console.log(` ${import_picocolors19.default.yellow("⚠")} ${import_picocolors19.default.yellow("Branches are not fully aligned")}`);
|
|
13856
14527
|
}
|
|
13857
14528
|
}
|
|
13858
14529
|
}
|
|
@@ -13861,7 +14532,10 @@ var sync_default = defineCommand({
|
|
|
13861
14532
|
|
|
13862
14533
|
// src/commands/update.ts
|
|
13863
14534
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
13864
|
-
var
|
|
14535
|
+
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
14536
|
+
function hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, hasUncommittedChanges2) {
|
|
14537
|
+
return hasUncommittedChanges2 || uniqueCommitsAheadOfBase > 0;
|
|
14538
|
+
}
|
|
13865
14539
|
var update_default = defineCommand({
|
|
13866
14540
|
meta: {
|
|
13867
14541
|
name: "update",
|
|
@@ -13886,7 +14560,7 @@ var update_default = defineCommand({
|
|
|
13886
14560
|
await assertCleanGitState("updating");
|
|
13887
14561
|
const config = readConfig();
|
|
13888
14562
|
if (!config) {
|
|
13889
|
-
error("No
|
|
14563
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
13890
14564
|
process.exit(1);
|
|
13891
14565
|
}
|
|
13892
14566
|
const baseBranch = getBaseBranch(config);
|
|
@@ -13898,8 +14572,8 @@ var update_default = defineCommand({
|
|
|
13898
14572
|
process.exit(1);
|
|
13899
14573
|
}
|
|
13900
14574
|
if (protectedBranches.includes(currentBranch)) {
|
|
13901
|
-
|
|
13902
|
-
warn(`You're on ${
|
|
14575
|
+
projectHeading("update", "\uD83D\uDD03");
|
|
14576
|
+
warn(`You're on ${import_picocolors20.default.bold(currentBranch)}, which is a protected branch. Updates (rebase) apply to feature branches.`);
|
|
13903
14577
|
await fetchAll();
|
|
13904
14578
|
const { origin } = config;
|
|
13905
14579
|
const remoteRef = `${origin}/${currentBranch}`;
|
|
@@ -13908,12 +14582,12 @@ var update_default = defineCommand({
|
|
|
13908
14582
|
const hasCommits = localWork.unpushedCommits > 0;
|
|
13909
14583
|
const hasAnything = hasCommits || dirty;
|
|
13910
14584
|
if (!hasAnything) {
|
|
13911
|
-
info(`No local changes found on ${
|
|
13912
|
-
info(`Use ${
|
|
14585
|
+
info(`No local changes found on ${import_picocolors20.default.bold(currentBranch)}.`);
|
|
14586
|
+
info(`Use ${import_picocolors20.default.bold("contrib sync")} to sync protected branches, or ${import_picocolors20.default.bold("contrib start")} to create a feature branch.`);
|
|
13913
14587
|
process.exit(1);
|
|
13914
14588
|
}
|
|
13915
14589
|
if (hasCommits) {
|
|
13916
|
-
info(`Found ${
|
|
14590
|
+
info(`Found ${import_picocolors20.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors20.default.bold(currentBranch)}.`);
|
|
13917
14591
|
}
|
|
13918
14592
|
if (dirty) {
|
|
13919
14593
|
info("You also have uncommitted changes in the working tree.");
|
|
@@ -13929,111 +14603,73 @@ var update_default = defineCommand({
|
|
|
13929
14603
|
info("No changes made. You are still on your current branch.");
|
|
13930
14604
|
return;
|
|
13931
14605
|
}
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
if (suggested) {
|
|
13941
|
-
spinner.success("Branch name suggestion ready.");
|
|
13942
|
-
console.log(`
|
|
13943
|
-
${import_picocolors19.default.dim("AI suggestion:")} ${import_picocolors19.default.bold(import_picocolors19.default.cyan(suggested))}`);
|
|
13944
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors19.default.bold(suggested)} as your branch name?`);
|
|
13945
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
13946
|
-
} else {
|
|
13947
|
-
spinner.fail("AI did not return a suggestion.");
|
|
13948
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
13949
|
-
}
|
|
13950
|
-
}
|
|
13951
|
-
}
|
|
13952
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
13953
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors19.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
13954
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
13955
|
-
}
|
|
13956
|
-
if (!isValidBranchName(newBranchName)) {
|
|
13957
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
13958
|
-
process.exit(1);
|
|
14606
|
+
const newBranchName = await promptForBranchName({
|
|
14607
|
+
branchPrefixes: config.branchPrefixes,
|
|
14608
|
+
useAI: isAIEnabled(config, args["no-ai"]),
|
|
14609
|
+
model: args.model
|
|
14610
|
+
});
|
|
14611
|
+
if (!newBranchName) {
|
|
14612
|
+
info("No changes made. You are still on your current branch.");
|
|
14613
|
+
return;
|
|
13959
14614
|
}
|
|
13960
14615
|
const branchResult = await createBranch(newBranchName);
|
|
13961
14616
|
if (branchResult.exitCode !== 0) {
|
|
13962
14617
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
13963
14618
|
process.exit(1);
|
|
13964
14619
|
}
|
|
13965
|
-
success(`Created ${
|
|
14620
|
+
success(`Created ${import_picocolors20.default.bold(newBranchName)} with your changes.`);
|
|
13966
14621
|
await updateLocalBranch(currentBranch, remoteRef);
|
|
13967
|
-
info(`Reset ${
|
|
14622
|
+
info(`Reset ${import_picocolors20.default.bold(currentBranch)} back to ${import_picocolors20.default.bold(remoteRef)} — no damage done.`, "");
|
|
13968
14623
|
console.log();
|
|
13969
|
-
success(`You're now on ${
|
|
13970
|
-
info(`Run ${
|
|
14624
|
+
success(`You're now on ${import_picocolors20.default.bold(newBranchName)} with all your work intact.`);
|
|
14625
|
+
info(`Run ${import_picocolors20.default.bold("contrib update")} again to rebase onto latest ${import_picocolors20.default.bold(baseBranch)}.`, "");
|
|
13971
14626
|
return;
|
|
13972
14627
|
}
|
|
13973
14628
|
if (await hasUncommittedChanges()) {
|
|
13974
14629
|
error("You have uncommitted changes. Please commit or stash them first.");
|
|
13975
14630
|
process.exit(1);
|
|
13976
14631
|
}
|
|
13977
|
-
|
|
14632
|
+
projectHeading("update", "\uD83D\uDD03");
|
|
13978
14633
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
13979
14634
|
if (mergedPR) {
|
|
13980
|
-
warn(`PR #${mergedPR.number} (${
|
|
13981
|
-
info(`Link: ${
|
|
13982
|
-
const
|
|
13983
|
-
const
|
|
14635
|
+
warn(`PR #${mergedPR.number} (${import_picocolors20.default.bold(mergedPR.title)}) has already been merged.`);
|
|
14636
|
+
info(`Link: ${import_picocolors20.default.underline(mergedPR.url)}`, "");
|
|
14637
|
+
const uniqueCommitsAheadOfBase = await countCommitsAhead(currentBranch, syncSource.ref);
|
|
14638
|
+
const dirty = await hasUncommittedChanges();
|
|
14639
|
+
const hasWork = hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, dirty);
|
|
13984
14640
|
if (hasWork) {
|
|
13985
|
-
if (
|
|
14641
|
+
if (dirty) {
|
|
13986
14642
|
info("You have uncommitted local changes.");
|
|
13987
14643
|
}
|
|
13988
|
-
if (
|
|
13989
|
-
info(`You have ${
|
|
14644
|
+
if (uniqueCommitsAheadOfBase > 0) {
|
|
14645
|
+
info(`You have ${uniqueCommitsAheadOfBase} local commit(s) not in ${import_picocolors20.default.bold(syncSource.ref)}.`);
|
|
13990
14646
|
}
|
|
13991
14647
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
13992
14648
|
const DISCARD = "Discard all changes and clean up";
|
|
13993
14649
|
const CANCEL = "Cancel";
|
|
13994
|
-
const action = await selectPrompt(`${
|
|
14650
|
+
const action = await selectPrompt(`${import_picocolors20.default.bold(currentBranch)} is stale but has local work. What would you like to do?`, [SAVE_NEW_BRANCH, DISCARD, CANCEL]);
|
|
13995
14651
|
if (action === CANCEL) {
|
|
13996
14652
|
info("No changes made. You are still on your current branch.");
|
|
13997
14653
|
return;
|
|
13998
14654
|
}
|
|
13999
14655
|
if (action === SAVE_NEW_BRANCH) {
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
console.log(`
|
|
14009
|
-
${import_picocolors19.default.dim("AI suggestion:")} ${import_picocolors19.default.bold(import_picocolors19.default.cyan(suggested))}`);
|
|
14010
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors19.default.bold(suggested)} as your branch name?`);
|
|
14011
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
14012
|
-
} else {
|
|
14013
|
-
spinner.fail("AI did not return a suggestion.");
|
|
14014
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
14015
|
-
}
|
|
14016
|
-
}
|
|
14017
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
14018
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors19.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
14019
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
14020
|
-
}
|
|
14021
|
-
if (!isValidBranchName(newBranchName)) {
|
|
14022
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
14023
|
-
process.exit(1);
|
|
14656
|
+
const newBranchName = await promptForBranchName({
|
|
14657
|
+
branchPrefixes: config.branchPrefixes,
|
|
14658
|
+
useAI: isAIEnabled(config, args["no-ai"]),
|
|
14659
|
+
model: args.model
|
|
14660
|
+
});
|
|
14661
|
+
if (!newBranchName) {
|
|
14662
|
+
info("No changes made. You are still on your current branch.");
|
|
14663
|
+
return;
|
|
14024
14664
|
}
|
|
14025
14665
|
const staleUpstream = await getUpstreamRef();
|
|
14026
14666
|
const staleUpstreamHash = staleUpstream ? await getCommitHash(staleUpstream) : null;
|
|
14027
|
-
if (await branchExists(newBranchName)) {
|
|
14028
|
-
error(`Branch ${import_picocolors19.default.bold(newBranchName)} already exists. Choose a different name.`);
|
|
14029
|
-
process.exit(1);
|
|
14030
|
-
}
|
|
14031
14667
|
const renameResult = await renameBranch(currentBranch, newBranchName);
|
|
14032
14668
|
if (renameResult.exitCode !== 0) {
|
|
14033
14669
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
14034
14670
|
process.exit(1);
|
|
14035
14671
|
}
|
|
14036
|
-
success(`Renamed ${
|
|
14672
|
+
success(`Renamed ${import_picocolors20.default.bold(currentBranch)} → ${import_picocolors20.default.bold(newBranchName)}`);
|
|
14037
14673
|
await unsetUpstream();
|
|
14038
14674
|
await fetchRemote(syncSource.remote);
|
|
14039
14675
|
let rebaseResult2;
|
|
@@ -14045,14 +14681,20 @@ var update_default = defineCommand({
|
|
|
14045
14681
|
}
|
|
14046
14682
|
if (rebaseResult2.exitCode !== 0) {
|
|
14047
14683
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
14048
|
-
info(` ${
|
|
14684
|
+
info(` ${import_picocolors20.default.bold("git rebase --continue")}`, "");
|
|
14049
14685
|
} else {
|
|
14050
|
-
success(`Rebased ${
|
|
14686
|
+
success(`Rebased ${import_picocolors20.default.bold(newBranchName)} onto ${import_picocolors20.default.bold(syncSource.ref)}.`);
|
|
14051
14687
|
}
|
|
14052
|
-
info(`All your changes are preserved. Run ${
|
|
14688
|
+
info(`All your changes are preserved. Run ${import_picocolors20.default.bold("contrib submit")} when ready to create a new PR.`, "");
|
|
14053
14689
|
return;
|
|
14054
14690
|
}
|
|
14055
14691
|
warn("Discarding local changes...");
|
|
14692
|
+
} else {
|
|
14693
|
+
const proceed = await confirmPrompt(`Switch to ${baseBranch} and delete stale branch ${currentBranch}?`);
|
|
14694
|
+
if (!proceed) {
|
|
14695
|
+
info("No changes made. You are still on your current branch.");
|
|
14696
|
+
return;
|
|
14697
|
+
}
|
|
14056
14698
|
}
|
|
14057
14699
|
await fetchRemote(syncSource.remote);
|
|
14058
14700
|
await resetHard("HEAD");
|
|
@@ -14062,30 +14704,30 @@ var update_default = defineCommand({
|
|
|
14062
14704
|
process.exit(1);
|
|
14063
14705
|
}
|
|
14064
14706
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
14065
|
-
success(`Synced ${
|
|
14066
|
-
info(`Deleting stale branch ${
|
|
14707
|
+
success(`Synced ${import_picocolors20.default.bold(baseBranch)} with ${import_picocolors20.default.bold(syncSource.ref)}.`);
|
|
14708
|
+
info(`Deleting stale branch ${import_picocolors20.default.bold(currentBranch)}...`);
|
|
14067
14709
|
await forceDeleteBranch(currentBranch);
|
|
14068
|
-
success(`Deleted ${
|
|
14069
|
-
info(`Run ${
|
|
14710
|
+
success(`Deleted ${import_picocolors20.default.bold(currentBranch)}.`);
|
|
14711
|
+
info(`Run ${import_picocolors20.default.bold("contrib start")} to begin a new feature branch.`, "");
|
|
14070
14712
|
return;
|
|
14071
14713
|
}
|
|
14072
|
-
info(`Updating ${
|
|
14714
|
+
info(`Updating ${import_picocolors20.default.bold(currentBranch)} with latest ${import_picocolors20.default.bold(baseBranch)}...`);
|
|
14073
14715
|
await fetchRemote(syncSource.remote);
|
|
14074
14716
|
if (!await refExists(syncSource.ref)) {
|
|
14075
|
-
error(`Remote ref ${
|
|
14717
|
+
error(`Remote ref ${import_picocolors20.default.bold(syncSource.ref)} does not exist.`);
|
|
14076
14718
|
error("Run `git fetch --all` and verify your remote configuration.");
|
|
14077
14719
|
process.exit(1);
|
|
14078
14720
|
}
|
|
14079
14721
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
14080
14722
|
const rebaseStrategy = await determineRebaseStrategy(currentBranch, syncSource.ref);
|
|
14081
14723
|
if (rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase) {
|
|
14082
|
-
info(
|
|
14724
|
+
info(import_picocolors20.default.dim(`Using --onto rebase (branch was based on a different ref)`));
|
|
14083
14725
|
}
|
|
14084
14726
|
const rebaseResult = rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase ? await rebaseOnto(syncSource.ref, rebaseStrategy.ontoOldBase) : await rebase(syncSource.ref);
|
|
14085
14727
|
if (rebaseResult.exitCode !== 0) {
|
|
14086
14728
|
warn("Rebase hit conflicts. Resolve them manually.");
|
|
14087
14729
|
console.log();
|
|
14088
|
-
if (
|
|
14730
|
+
if (isAIEnabled(config, args["no-ai"])) {
|
|
14089
14731
|
const copilotError = await checkCopilotAvailable();
|
|
14090
14732
|
if (!copilotError) {
|
|
14091
14733
|
info("Fetching AI conflict resolution suggestions...");
|
|
@@ -14103,15 +14745,17 @@ ${content.slice(0, 2000)}
|
|
|
14103
14745
|
} catch {}
|
|
14104
14746
|
}
|
|
14105
14747
|
if (conflictDiff) {
|
|
14106
|
-
const spinner = createSpinner("Analyzing conflicts with AI..."
|
|
14748
|
+
const spinner = createSpinner("Analyzing conflicts with AI...", {
|
|
14749
|
+
tips: LOADING_TIPS
|
|
14750
|
+
});
|
|
14107
14751
|
const suggestion = await suggestConflictResolution(conflictDiff, args.model);
|
|
14108
14752
|
if (suggestion) {
|
|
14109
14753
|
spinner.success("AI conflict guidance ready.");
|
|
14110
14754
|
console.log(`
|
|
14111
|
-
${
|
|
14112
|
-
console.log(
|
|
14755
|
+
${import_picocolors20.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
14756
|
+
console.log(import_picocolors20.default.dim("─".repeat(60)));
|
|
14113
14757
|
console.log(suggestion);
|
|
14114
|
-
console.log(
|
|
14758
|
+
console.log(import_picocolors20.default.dim("─".repeat(60)));
|
|
14115
14759
|
console.log();
|
|
14116
14760
|
} else {
|
|
14117
14761
|
spinner.fail("AI could not analyze the conflicts.");
|
|
@@ -14119,21 +14763,21 @@ ${import_picocolors19.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance
|
|
|
14119
14763
|
}
|
|
14120
14764
|
}
|
|
14121
14765
|
}
|
|
14122
|
-
console.log(
|
|
14766
|
+
console.log(import_picocolors20.default.bold("To resolve:"));
|
|
14123
14767
|
console.log(` 1. Fix conflicts in the affected files`);
|
|
14124
|
-
console.log(` 2. ${
|
|
14125
|
-
console.log(` 3. ${
|
|
14768
|
+
console.log(` 2. ${import_picocolors20.default.cyan("git add <resolved-files>")}`);
|
|
14769
|
+
console.log(` 3. ${import_picocolors20.default.cyan("git rebase --continue")}`);
|
|
14126
14770
|
console.log();
|
|
14127
|
-
console.log(` Or abort: ${
|
|
14771
|
+
console.log(` Or abort: ${import_picocolors20.default.cyan("git rebase --abort")}`);
|
|
14128
14772
|
process.exit(1);
|
|
14129
14773
|
}
|
|
14130
|
-
success(`${
|
|
14774
|
+
success(`${import_picocolors20.default.bold(currentBranch)} has been rebased onto latest ${import_picocolors20.default.bold(baseBranch)}`);
|
|
14131
14775
|
}
|
|
14132
14776
|
});
|
|
14133
14777
|
|
|
14134
14778
|
// src/commands/validate.ts
|
|
14135
|
-
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
14136
14779
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
14780
|
+
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
14137
14781
|
var validate_default = defineCommand({
|
|
14138
14782
|
meta: {
|
|
14139
14783
|
name: "validate",
|
|
@@ -14153,9 +14797,10 @@ var validate_default = defineCommand({
|
|
|
14153
14797
|
async run({ args }) {
|
|
14154
14798
|
const config = readConfig();
|
|
14155
14799
|
if (!config) {
|
|
14156
|
-
error("No
|
|
14800
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
14157
14801
|
process.exit(1);
|
|
14158
14802
|
}
|
|
14803
|
+
projectHeading("validate", "✅");
|
|
14159
14804
|
const convention = config.commitConvention;
|
|
14160
14805
|
if (convention === "none") {
|
|
14161
14806
|
info('Commit convention is set to "none". All messages are accepted.');
|
|
@@ -14172,7 +14817,7 @@ var validate_default = defineCommand({
|
|
|
14172
14817
|
}
|
|
14173
14818
|
const errors = getValidationError(convention);
|
|
14174
14819
|
for (const line of errors) {
|
|
14175
|
-
console.error(
|
|
14820
|
+
console.error(import_picocolors21.default.red(` ✗ ${line}`));
|
|
14176
14821
|
}
|
|
14177
14822
|
process.exit(1);
|
|
14178
14823
|
}
|
|
@@ -15471,8 +16116,8 @@ var figlet = (() => {
|
|
|
15471
16116
|
}
|
|
15472
16117
|
};
|
|
15473
16118
|
me2.fonts = function(callback) {
|
|
15474
|
-
return new Promise(function(
|
|
15475
|
-
|
|
16119
|
+
return new Promise(function(resolve3, reject) {
|
|
16120
|
+
resolve3(fontList);
|
|
15476
16121
|
if (callback) {
|
|
15477
16122
|
callback(null, fontList);
|
|
15478
16123
|
}
|
|
@@ -15494,12 +16139,12 @@ var nodeFiglet = figlet;
|
|
|
15494
16139
|
nodeFiglet.defaults({ fontPath });
|
|
15495
16140
|
nodeFiglet.loadFont = function(name, callback) {
|
|
15496
16141
|
const actualFontName = getFontName(name);
|
|
15497
|
-
return new Promise((
|
|
16142
|
+
return new Promise((resolve3, reject) => {
|
|
15498
16143
|
if (nodeFiglet.figFonts[actualFontName]) {
|
|
15499
16144
|
if (callback) {
|
|
15500
16145
|
callback(null, nodeFiglet.figFonts[actualFontName].options);
|
|
15501
16146
|
}
|
|
15502
|
-
|
|
16147
|
+
resolve3(nodeFiglet.figFonts[actualFontName].options);
|
|
15503
16148
|
return;
|
|
15504
16149
|
}
|
|
15505
16150
|
fs2.readFile(path2.join(nodeFiglet.defaults().fontPath, actualFontName + ".flf"), { encoding: "utf-8" }, (err, fontData) => {
|
|
@@ -15516,7 +16161,7 @@ nodeFiglet.loadFont = function(name, callback) {
|
|
|
15516
16161
|
if (callback) {
|
|
15517
16162
|
callback(null, font);
|
|
15518
16163
|
}
|
|
15519
|
-
|
|
16164
|
+
resolve3(font);
|
|
15520
16165
|
} catch (error2) {
|
|
15521
16166
|
const typedError = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
15522
16167
|
if (callback) {
|
|
@@ -15538,7 +16183,7 @@ nodeFiglet.loadFontSync = function(font) {
|
|
|
15538
16183
|
return nodeFiglet.parseFont(actualFontName, fontData);
|
|
15539
16184
|
};
|
|
15540
16185
|
nodeFiglet.fonts = function(next) {
|
|
15541
|
-
return new Promise((
|
|
16186
|
+
return new Promise((resolve3, reject) => {
|
|
15542
16187
|
const fontList2 = [];
|
|
15543
16188
|
fs2.readdir(nodeFiglet.defaults().fontPath, (err, files) => {
|
|
15544
16189
|
if (err) {
|
|
@@ -15552,7 +16197,7 @@ nodeFiglet.fonts = function(next) {
|
|
|
15552
16197
|
}
|
|
15553
16198
|
});
|
|
15554
16199
|
next && next(null, fontList2);
|
|
15555
|
-
|
|
16200
|
+
resolve3(fontList2);
|
|
15556
16201
|
});
|
|
15557
16202
|
});
|
|
15558
16203
|
};
|
|
@@ -15567,7 +16212,37 @@ nodeFiglet.fontsSync = function() {
|
|
|
15567
16212
|
};
|
|
15568
16213
|
|
|
15569
16214
|
// src/ui/banner.ts
|
|
15570
|
-
var
|
|
16215
|
+
var import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
16216
|
+
|
|
16217
|
+
// src/data/announcements.json
|
|
16218
|
+
var announcements_default = [
|
|
16219
|
+
{
|
|
16220
|
+
id: "legacy-config-migration",
|
|
16221
|
+
kind: "notice",
|
|
16222
|
+
title: "Legacy Config Detected",
|
|
16223
|
+
message: "Run cn setup to migrate this clone to local Git config, then delete .contributerc.json.",
|
|
16224
|
+
when: "legacy-config-present"
|
|
16225
|
+
}
|
|
16226
|
+
];
|
|
16227
|
+
|
|
16228
|
+
// src/utils/announcements.ts
|
|
16229
|
+
var DEFINITIONS = announcements_default;
|
|
16230
|
+
function getActiveAnnouncements(cwd = process.cwd()) {
|
|
16231
|
+
return DEFINITIONS.filter((announcement) => shouldShowAnnouncement(announcement, cwd)).map(({ id, kind, title, message }) => ({ id, kind, title, message }));
|
|
16232
|
+
}
|
|
16233
|
+
function shouldShowAnnouncement(announcement, cwd) {
|
|
16234
|
+
if (!announcement.when) {
|
|
16235
|
+
return true;
|
|
16236
|
+
}
|
|
16237
|
+
switch (announcement.when) {
|
|
16238
|
+
case "legacy-config-present":
|
|
16239
|
+
return hasLegacyConfig(cwd);
|
|
16240
|
+
default:
|
|
16241
|
+
return false;
|
|
16242
|
+
}
|
|
16243
|
+
}
|
|
16244
|
+
|
|
16245
|
+
// src/ui/banner.ts
|
|
15571
16246
|
var LOGO_BIG;
|
|
15572
16247
|
try {
|
|
15573
16248
|
LOGO_BIG = nodeFiglet.textSync(`Contribute
|
|
@@ -15589,19 +16264,194 @@ function getAuthor() {
|
|
|
15589
16264
|
}
|
|
15590
16265
|
function showBanner(variant = "small") {
|
|
15591
16266
|
const logo = variant === "big" ? LOGO_BIG : LOGO_SMALL;
|
|
15592
|
-
console.log(
|
|
16267
|
+
console.log(import_picocolors22.default.cyan(`
|
|
15593
16268
|
${logo}`));
|
|
15594
|
-
console.log(` ${
|
|
16269
|
+
console.log(` ${import_picocolors22.default.dim(`v${getVersion()}`)} ${import_picocolors22.default.dim("—")} ${import_picocolors22.default.dim(`Built by ${getAuthor()}`)}`);
|
|
16270
|
+
const announcements = getActiveAnnouncements();
|
|
16271
|
+
if (announcements.length > 0) {
|
|
16272
|
+
console.log();
|
|
16273
|
+
renderAnnouncements(announcements);
|
|
16274
|
+
}
|
|
15595
16275
|
if (variant === "big") {
|
|
16276
|
+
const panelLines = [
|
|
16277
|
+
{
|
|
16278
|
+
label: import_picocolors22.default.bold(import_picocolors22.default.cyan("Getting Started")),
|
|
16279
|
+
rawLabel: "Getting Started",
|
|
16280
|
+
value: "",
|
|
16281
|
+
rawValue: ""
|
|
16282
|
+
},
|
|
16283
|
+
{
|
|
16284
|
+
label: import_picocolors22.default.cyan("cn setup"),
|
|
16285
|
+
rawLabel: "cn setup",
|
|
16286
|
+
value: import_picocolors22.default.dim("configure workflow, remotes, and defaults"),
|
|
16287
|
+
rawValue: "configure workflow, remotes, and defaults"
|
|
16288
|
+
},
|
|
16289
|
+
{
|
|
16290
|
+
label: import_picocolors22.default.cyan("cn doctor"),
|
|
16291
|
+
rawLabel: "cn doctor",
|
|
16292
|
+
value: import_picocolors22.default.dim("verify your environment before doing any work"),
|
|
16293
|
+
rawValue: "verify your environment before doing any work"
|
|
16294
|
+
},
|
|
16295
|
+
{
|
|
16296
|
+
label: import_picocolors22.default.cyan("cn start"),
|
|
16297
|
+
rawLabel: "cn start",
|
|
16298
|
+
value: import_picocolors22.default.dim("create a branch and begin the next task"),
|
|
16299
|
+
rawValue: "create a branch and begin the next task"
|
|
16300
|
+
},
|
|
16301
|
+
{
|
|
16302
|
+
label: "",
|
|
16303
|
+
rawLabel: "",
|
|
16304
|
+
value: "",
|
|
16305
|
+
rawValue: ""
|
|
16306
|
+
},
|
|
16307
|
+
{
|
|
16308
|
+
label: import_picocolors22.default.bold(import_picocolors22.default.cyan("Workflow")),
|
|
16309
|
+
rawLabel: "Workflow",
|
|
16310
|
+
value: "",
|
|
16311
|
+
rawValue: ""
|
|
16312
|
+
},
|
|
16313
|
+
{
|
|
16314
|
+
label: import_picocolors22.default.dim("cn setup → cn commit → cn update → cn submit"),
|
|
16315
|
+
rawLabel: "cn setup → cn commit → cn update → cn submit",
|
|
16316
|
+
value: "",
|
|
16317
|
+
rawValue: ""
|
|
16318
|
+
}
|
|
16319
|
+
];
|
|
16320
|
+
const terminalWidth = process.stdout.columns ?? 80;
|
|
16321
|
+
const maxContentWidth = Math.max(36, terminalWidth - 8);
|
|
16322
|
+
const unclampedLabelWidth = panelLines.reduce((max, line) => Math.max(max, line.rawLabel.length), 0);
|
|
16323
|
+
const labelWidth = Math.min(unclampedLabelWidth, 18);
|
|
16324
|
+
const valueWidth = Math.max(14, maxContentWidth - labelWidth - 2);
|
|
16325
|
+
const rows = panelLines.map((line) => {
|
|
16326
|
+
const rawLabel = line.rawValue ? truncateText3(line.rawLabel, labelWidth) : line.rawLabel;
|
|
16327
|
+
const rawValue = line.rawValue ? truncateText3(line.rawValue, valueWidth) : "";
|
|
16328
|
+
return {
|
|
16329
|
+
...line,
|
|
16330
|
+
rawLabel,
|
|
16331
|
+
rawValue
|
|
16332
|
+
};
|
|
16333
|
+
});
|
|
16334
|
+
const contentWidth = Math.min(maxContentWidth, rows.reduce((max, line) => {
|
|
16335
|
+
const lineLength = line.rawValue ? labelWidth + 2 + line.rawValue.length : line.rawLabel.length;
|
|
16336
|
+
return Math.max(max, lineLength);
|
|
16337
|
+
}, 0));
|
|
16338
|
+
console.log();
|
|
16339
|
+
console.log(` ${import_picocolors22.default.dim(`┌${"─".repeat(contentWidth + 2)}┐`)}`);
|
|
16340
|
+
for (const line of rows) {
|
|
16341
|
+
if (!line.rawLabel && !line.rawValue) {
|
|
16342
|
+
console.log(` ${import_picocolors22.default.dim("│")} ${" ".repeat(contentWidth)} ${import_picocolors22.default.dim("│")}`);
|
|
16343
|
+
continue;
|
|
16344
|
+
}
|
|
16345
|
+
const left = line.rawValue ? `${line.label}${" ".repeat(Math.max(0, labelWidth - line.rawLabel.length + 2))}` : line.label;
|
|
16346
|
+
const value = line.rawValue ? import_picocolors22.default.dim(line.rawValue) : "";
|
|
16347
|
+
const rawLength = line.rawValue ? labelWidth + 2 + line.rawValue.length : line.rawLabel.length;
|
|
16348
|
+
const trailing = " ".repeat(Math.max(0, contentWidth - rawLength));
|
|
16349
|
+
console.log(` ${import_picocolors22.default.dim("│")} ${left}${value}${trailing} ${import_picocolors22.default.dim("│")}`);
|
|
16350
|
+
}
|
|
16351
|
+
console.log(` ${import_picocolors22.default.dim(`└${"─".repeat(contentWidth + 2)}┘`)}`);
|
|
15596
16352
|
console.log();
|
|
15597
|
-
console.log(` ${
|
|
15598
|
-
console.log(` ${
|
|
15599
|
-
console.log(` ${import_picocolors21.default.magenta("Sponsor")} ${import_picocolors21.default.cyan("https://warengonzaga.com/sponsor")}`);
|
|
16353
|
+
console.log(` ${import_picocolors22.default.dim("Star or contribute:")} ${import_picocolors22.default.dim(linkify("gh.waren.build/contribute-now", "https://gh.waren.build/contribute-now"))}`);
|
|
16354
|
+
console.log(` ${import_picocolors22.default.dim("Sponsor:")} ${import_picocolors22.default.dim(linkify("warengonzaga.com/sponsor", "https://warengonzaga.com/sponsor"))}`);
|
|
15600
16355
|
}
|
|
15601
16356
|
console.log();
|
|
15602
16357
|
}
|
|
16358
|
+
function truncateText3(text, maxWidth) {
|
|
16359
|
+
if (text.length <= maxWidth) {
|
|
16360
|
+
return text;
|
|
16361
|
+
}
|
|
16362
|
+
if (maxWidth <= 1) {
|
|
16363
|
+
return text.slice(0, maxWidth);
|
|
16364
|
+
}
|
|
16365
|
+
return `${text.slice(0, maxWidth - 1)}…`;
|
|
16366
|
+
}
|
|
16367
|
+
function linkify(label, url) {
|
|
16368
|
+
return `\x1B]8;;${url}\x07${label}\x1B]8;;\x07`;
|
|
16369
|
+
}
|
|
16370
|
+
function renderAnnouncements(announcements) {
|
|
16371
|
+
for (const announcement of announcements) {
|
|
16372
|
+
renderAnnouncementBanner(announcement);
|
|
16373
|
+
}
|
|
16374
|
+
}
|
|
16375
|
+
function renderAnnouncementBanner(announcement) {
|
|
16376
|
+
const terminalWidth = process.stdout.columns ?? 80;
|
|
16377
|
+
const contentWidth = Math.max(36, Math.min(terminalWidth - 8, 92));
|
|
16378
|
+
const tone = getAnnouncementTone(announcement.kind);
|
|
16379
|
+
const title = `${tone.emoji} ${announcement.title}`;
|
|
16380
|
+
const messageLines = wrapText(announcement.message, contentWidth);
|
|
16381
|
+
const lines = [title, ...messageLines];
|
|
16382
|
+
const rawWidth = Math.max(...lines.map((line) => line.length));
|
|
16383
|
+
console.log(` ${tone.border(`┌${"─".repeat(rawWidth + 2)}┐`)}`);
|
|
16384
|
+
for (const line of lines) {
|
|
16385
|
+
const trailing = " ".repeat(Math.max(0, rawWidth - line.length));
|
|
16386
|
+
const content = line === title ? tone.title(line) : import_picocolors22.default.dim(line);
|
|
16387
|
+
console.log(` ${tone.border("│")} ${content}${trailing} ${tone.border("│")}`);
|
|
16388
|
+
}
|
|
16389
|
+
console.log(` ${tone.border(`└${"─".repeat(rawWidth + 2)}┘`)}`);
|
|
16390
|
+
}
|
|
16391
|
+
function wrapText(text, maxWidth) {
|
|
16392
|
+
if (text.length <= maxWidth) {
|
|
16393
|
+
return [text];
|
|
16394
|
+
}
|
|
16395
|
+
const words = text.split(/\s+/);
|
|
16396
|
+
const lines = [];
|
|
16397
|
+
let current = "";
|
|
16398
|
+
for (const word of words) {
|
|
16399
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
16400
|
+
if (candidate.length <= maxWidth) {
|
|
16401
|
+
current = candidate;
|
|
16402
|
+
continue;
|
|
16403
|
+
}
|
|
16404
|
+
if (current) {
|
|
16405
|
+
lines.push(current);
|
|
16406
|
+
current = word;
|
|
16407
|
+
} else {
|
|
16408
|
+
lines.push(word.slice(0, maxWidth));
|
|
16409
|
+
current = word.slice(maxWidth);
|
|
16410
|
+
}
|
|
16411
|
+
}
|
|
16412
|
+
if (current) {
|
|
16413
|
+
lines.push(current);
|
|
16414
|
+
}
|
|
16415
|
+
return lines;
|
|
16416
|
+
}
|
|
16417
|
+
function getAnnouncementTone(kind) {
|
|
16418
|
+
switch (kind) {
|
|
16419
|
+
case "info":
|
|
16420
|
+
return {
|
|
16421
|
+
emoji: "ℹ",
|
|
16422
|
+
border: import_picocolors22.default.blue,
|
|
16423
|
+
title: (value) => import_picocolors22.default.bold(import_picocolors22.default.blue(value))
|
|
16424
|
+
};
|
|
16425
|
+
case "warning":
|
|
16426
|
+
return {
|
|
16427
|
+
emoji: "\uD83D\uDEA8",
|
|
16428
|
+
border: import_picocolors22.default.red,
|
|
16429
|
+
title: (value) => import_picocolors22.default.bold(import_picocolors22.default.red(value))
|
|
16430
|
+
};
|
|
16431
|
+
default:
|
|
16432
|
+
return {
|
|
16433
|
+
emoji: "⚠",
|
|
16434
|
+
border: import_picocolors22.default.yellow,
|
|
16435
|
+
title: (value) => import_picocolors22.default.bold(import_picocolors22.default.yellow(value))
|
|
16436
|
+
};
|
|
16437
|
+
}
|
|
16438
|
+
}
|
|
15603
16439
|
|
|
15604
16440
|
// src/index.ts
|
|
16441
|
+
function normalizeCliArgs(argv2) {
|
|
16442
|
+
return argv2.map((arg, index) => {
|
|
16443
|
+
const previous = argv2[index - 1];
|
|
16444
|
+
const isSubmitCommand = previous === "submit" || argv2.includes("submit");
|
|
16445
|
+
if (!isSubmitCommand) {
|
|
16446
|
+
return arg;
|
|
16447
|
+
}
|
|
16448
|
+
if (arg === "-pr" || arg === "--pr") {
|
|
16449
|
+
return "--pullrequest";
|
|
16450
|
+
}
|
|
16451
|
+
return arg;
|
|
16452
|
+
});
|
|
16453
|
+
}
|
|
16454
|
+
process.argv = normalizeCliArgs(process.argv);
|
|
15605
16455
|
var isVersion = process.argv.includes("--version") || process.argv.includes("-v");
|
|
15606
16456
|
if (!isVersion) {
|
|
15607
16457
|
const subCommands = [
|