contribute-now 0.6.2-dev.b908626 → 0.6.2-dev.d6e92ac
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 +1876 -924
- 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
|
|
@@ -10103,12 +10426,12 @@ async function multiSelectPrompt(message, choices) {
|
|
|
10103
10426
|
init_dist();
|
|
10104
10427
|
var CONVENTIONAL_COMMIT_SYSTEM_PROMPT = `Git commit message generator. Format: <type>[!][(<scope>)]: <description>
|
|
10105
10428
|
Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
10106
|
-
Rules: breaking (!) only for feat/fix/refactor/perf; imperative mood; max 72 chars; lowercase start; scope optional camelCase/kebab-case. Return ONLY the message line.
|
|
10429
|
+
Rules: breaking (!) only for feat/fix/refactor/perf; imperative mood; max 72 chars; lowercase start; scope optional camelCase/kebab-case. Do NOT use backticks, quotes, or markdown formatting around filenames, functions, or identifiers. Return ONLY the message line.
|
|
10107
10430
|
Examples: feat: add user auth | fix(auth): resolve token expiry | feat!: redesign auth API`;
|
|
10108
10431
|
var CLEAN_COMMIT_SYSTEM_PROMPT = `Git commit message generator. EXACT format: <emoji> <type>[!][ (<scope>)]: <description>
|
|
10109
10432
|
Spacing: EMOJI SPACE TYPE [SPACE OPENPAREN SCOPE CLOSEPAREN] COLON SPACE DESCRIPTION
|
|
10110
10433
|
Types: \uD83D\uDCE6 new, \uD83D\uDD27 update, \uD83D\uDDD1️ remove, \uD83D\uDD12 security, ⚙️ setup, ☕ chore, \uD83E\uDDEA test, \uD83D\uDCD6 docs, \uD83D\uDE80 release
|
|
10111
|
-
Rules: breaking (!) only for new/update/remove/security; imperative mood; max 72 chars; lowercase start; scope optional. Return ONLY the message line.
|
|
10434
|
+
Rules: breaking (!) only for new/update/remove/security; imperative mood; max 72 chars; lowercase start; scope optional. Do NOT use backticks, quotes, or markdown formatting around filenames, functions, or identifiers. Return ONLY the message line.
|
|
10112
10435
|
Correct: \uD83D\uDCE6 new: add user auth | \uD83D\uDD27 update (api): improve error handling | ⚙️ setup (ci): configure github actions
|
|
10113
10436
|
WRONG: ⚙️setup(ci): ... | \uD83D\uDD27 update(api): ... ← always space before scope parenthesis`;
|
|
10114
10437
|
function getGroupingSystemPrompt(convention) {
|
|
@@ -10133,6 +10456,7 @@ Rules:
|
|
|
10133
10456
|
- Each group should represent ONE logical change
|
|
10134
10457
|
- Every file must appear in exactly one group
|
|
10135
10458
|
- Commit messages must follow the convention, be concise, imperative, max 72 chars
|
|
10459
|
+
- Do not use backticks, quotes, or markdown formatting in commit messages
|
|
10136
10460
|
- Order groups so foundational changes come first (types, utils) and consumers come after
|
|
10137
10461
|
- Return ONLY the JSON array, nothing else`;
|
|
10138
10462
|
}
|
|
@@ -10167,11 +10491,11 @@ function suppressSubprocessWarnings() {
|
|
|
10167
10491
|
process.env.NODE_NO_WARNINGS = "1";
|
|
10168
10492
|
}
|
|
10169
10493
|
function withTimeout(promise, ms) {
|
|
10170
|
-
return new Promise((
|
|
10494
|
+
return new Promise((resolve3, reject) => {
|
|
10171
10495
|
const timer = setTimeout(() => reject(new Error(`Copilot request timed out after ${ms / 1000}s`)), ms);
|
|
10172
10496
|
promise.then((val) => {
|
|
10173
10497
|
clearTimeout(timer);
|
|
10174
|
-
|
|
10498
|
+
resolve3(val);
|
|
10175
10499
|
}, (err) => {
|
|
10176
10500
|
clearTimeout(timer);
|
|
10177
10501
|
reject(err);
|
|
@@ -10182,10 +10506,84 @@ var COPILOT_TIMEOUT_MS = 30000;
|
|
|
10182
10506
|
var COPILOT_LONG_TIMEOUT_MS = 90000;
|
|
10183
10507
|
var BATCH_CONFIG = {
|
|
10184
10508
|
LARGE_CHANGESET_THRESHOLD: 15,
|
|
10509
|
+
DIRECT_BATCH_THRESHOLD: 40,
|
|
10185
10510
|
COMPACT_PER_FILE_CHARS: 300,
|
|
10186
10511
|
MAX_COMPACT_PAYLOAD: 1e4,
|
|
10187
|
-
FALLBACK_BATCH_SIZE:
|
|
10512
|
+
FALLBACK_BATCH_SIZE: 8
|
|
10188
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
|
+
}
|
|
10189
10587
|
function parseDiffByFile(rawDiff) {
|
|
10190
10588
|
const sections = new Map;
|
|
10191
10589
|
const headerPattern = /^diff --git a\/(.+?) b\/(.+?)$/gm;
|
|
@@ -10253,7 +10651,7 @@ ${truncated}
|
|
|
10253
10651
|
return result.length > maxTotalChars ? `${result.slice(0, maxTotalChars - 15)}
|
|
10254
10652
|
...(truncated)` : result;
|
|
10255
10653
|
}
|
|
10256
|
-
async function
|
|
10654
|
+
async function checkCopilotAvailable2() {
|
|
10257
10655
|
try {
|
|
10258
10656
|
const client = await getManagedClient();
|
|
10259
10657
|
try {
|
|
@@ -10351,16 +10749,20 @@ function extractJson(raw) {
|
|
|
10351
10749
|
}
|
|
10352
10750
|
return text;
|
|
10353
10751
|
}
|
|
10752
|
+
function sanitizeGeneratedCommitMessage(message) {
|
|
10753
|
+
return message.replace(/`+/g, "").replace(/\s+/g, " ").trim();
|
|
10754
|
+
}
|
|
10354
10755
|
async function generateCommitMessage(diff, stagedFiles, model, convention = "clean-commit", context) {
|
|
10355
10756
|
try {
|
|
10356
10757
|
const isLarge = stagedFiles.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD;
|
|
10758
|
+
const hasMissingDiffCoverage = hasIncompleteDiffCoverage(stagedFiles, diff);
|
|
10357
10759
|
const multiFileHint = stagedFiles.length > 1 ? `
|
|
10358
10760
|
|
|
10359
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.` : "";
|
|
10360
10762
|
const squashHint = context === "squash-merge" ? `
|
|
10361
10763
|
|
|
10362
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.` : "";
|
|
10363
|
-
const diffContent = isLarge ? createCompactDiff(stagedFiles, diff) : diff.slice(0, 4000);
|
|
10765
|
+
const diffContent = isLarge || hasMissingDiffCoverage ? createCompactDiff(stagedFiles, diff) : diff.slice(0, 4000);
|
|
10364
10766
|
const userMessage = `Generate a commit message for these staged changes:
|
|
10365
10767
|
|
|
10366
10768
|
Files (${stagedFiles.length}): ${stagedFiles.join(", ")}
|
|
@@ -10368,7 +10770,7 @@ Files (${stagedFiles.length}): ${stagedFiles.join(", ")}
|
|
|
10368
10770
|
Diff:
|
|
10369
10771
|
${diffContent}${multiFileHint}${squashHint}`;
|
|
10370
10772
|
const result = await callCopilot(getCommitSystemPrompt(convention), userMessage, model, isLarge ? COPILOT_LONG_TIMEOUT_MS : COPILOT_TIMEOUT_MS);
|
|
10371
|
-
return result
|
|
10773
|
+
return result ? sanitizeGeneratedCommitMessage(result) : null;
|
|
10372
10774
|
} catch {
|
|
10373
10775
|
return null;
|
|
10374
10776
|
}
|
|
@@ -10415,9 +10817,49 @@ ${conflictDiff.slice(0, 4000)}`;
|
|
|
10415
10817
|
return null;
|
|
10416
10818
|
}
|
|
10417
10819
|
}
|
|
10418
|
-
|
|
10820
|
+
function normalizeCommitGroups(changedFiles, groups) {
|
|
10821
|
+
const changedSet = new Set(changedFiles);
|
|
10822
|
+
const assignedFiles = new Set;
|
|
10823
|
+
const unknownFiles = new Set;
|
|
10824
|
+
const duplicateFiles = new Set;
|
|
10825
|
+
const normalizedGroups = groups.map((group) => {
|
|
10826
|
+
const uniqueFiles = new Set;
|
|
10827
|
+
const files = [];
|
|
10828
|
+
for (const file of group.files) {
|
|
10829
|
+
if (!changedSet.has(file)) {
|
|
10830
|
+
unknownFiles.add(file);
|
|
10831
|
+
continue;
|
|
10832
|
+
}
|
|
10833
|
+
if (uniqueFiles.has(file) || assignedFiles.has(file)) {
|
|
10834
|
+
duplicateFiles.add(file);
|
|
10835
|
+
continue;
|
|
10836
|
+
}
|
|
10837
|
+
uniqueFiles.add(file);
|
|
10838
|
+
assignedFiles.add(file);
|
|
10839
|
+
files.push(file);
|
|
10840
|
+
}
|
|
10841
|
+
return {
|
|
10842
|
+
...group,
|
|
10843
|
+
files
|
|
10844
|
+
};
|
|
10845
|
+
}).filter((group) => group.files.length > 0);
|
|
10846
|
+
const unassignedFiles = changedFiles.filter((file) => !assignedFiles.has(file));
|
|
10847
|
+
return {
|
|
10848
|
+
groups: normalizedGroups,
|
|
10849
|
+
unknownFiles: [...unknownFiles],
|
|
10850
|
+
duplicateFiles: [...duplicateFiles],
|
|
10851
|
+
unassignedFiles
|
|
10852
|
+
};
|
|
10853
|
+
}
|
|
10854
|
+
async function generateCommitGroups(files, diffs, model, convention = "clean-commit", onProgress) {
|
|
10419
10855
|
const isLarge = files.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD;
|
|
10420
|
-
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);
|
|
10421
10863
|
const largeHint = isLarge ? `
|
|
10422
10864
|
|
|
10423
10865
|
NOTE: This is a large changeset (${files.length} files). Compact diffs are provided for every file. Focus on creating well-organized logical groups.` : "";
|
|
@@ -10429,10 +10871,22 @@ ${files.join(`
|
|
|
10429
10871
|
|
|
10430
10872
|
Diffs:
|
|
10431
10873
|
${diffContent}${largeHint}`;
|
|
10432
|
-
|
|
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
|
+
}
|
|
10433
10885
|
if (!result) {
|
|
10434
|
-
if (isLarge)
|
|
10435
|
-
|
|
10886
|
+
if (isLarge) {
|
|
10887
|
+
onProgress?.(`AI returned an empty response. Switching to focused batches...`);
|
|
10888
|
+
return generateCommitGroupsInBatches(files, diffs, model, convention, onProgress);
|
|
10889
|
+
}
|
|
10436
10890
|
throw new Error("AI returned an empty response");
|
|
10437
10891
|
}
|
|
10438
10892
|
const cleaned = extractJson(result);
|
|
@@ -10440,14 +10894,18 @@ ${diffContent}${largeHint}`;
|
|
|
10440
10894
|
try {
|
|
10441
10895
|
parsed = JSON.parse(cleaned);
|
|
10442
10896
|
} catch {
|
|
10443
|
-
if (isLarge)
|
|
10444
|
-
|
|
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
|
+
}
|
|
10445
10901
|
throw new Error(`AI response is not valid JSON. Raw start: "${result.slice(0, 120)}..."`);
|
|
10446
10902
|
}
|
|
10447
10903
|
const groups = parsed;
|
|
10448
10904
|
if (!Array.isArray(groups) || groups.length === 0) {
|
|
10449
|
-
if (isLarge)
|
|
10450
|
-
|
|
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
|
+
}
|
|
10451
10909
|
throw new Error("AI response was not a valid JSON array of commit groups");
|
|
10452
10910
|
}
|
|
10453
10911
|
for (const group of groups) {
|
|
@@ -10455,19 +10913,23 @@ ${diffContent}${largeHint}`;
|
|
|
10455
10913
|
throw new Error("AI returned groups with invalid structure (missing files or message)");
|
|
10456
10914
|
}
|
|
10457
10915
|
}
|
|
10458
|
-
return groups
|
|
10916
|
+
return groups.map((group) => ({
|
|
10917
|
+
...group,
|
|
10918
|
+
message: sanitizeGeneratedCommitMessage(group.message)
|
|
10919
|
+
}));
|
|
10459
10920
|
}
|
|
10460
|
-
async function generateCommitGroupsInBatches(files, diffs, model, convention = "clean-commit") {
|
|
10921
|
+
async function generateCommitGroupsInBatches(files, diffs, model, convention = "clean-commit", onProgress) {
|
|
10461
10922
|
const batchSize = BATCH_CONFIG.FALLBACK_BATCH_SIZE;
|
|
10462
10923
|
const allGroups = [];
|
|
10463
10924
|
const diffSections = parseDiffByFile(diffs);
|
|
10925
|
+
const totalBatches = Math.ceil(files.length / batchSize);
|
|
10464
10926
|
for (let i2 = 0;i2 < files.length; i2 += batchSize) {
|
|
10465
10927
|
const batchFiles = files.slice(i2, i2 + batchSize);
|
|
10466
10928
|
const batchDiff = batchFiles.map((f3) => diffSections.get(f3) ?? "").filter(Boolean).join(`
|
|
10467
10929
|
`);
|
|
10468
|
-
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);
|
|
10469
10931
|
const batchNum = Math.floor(i2 / batchSize) + 1;
|
|
10470
|
-
|
|
10932
|
+
onProgress?.(`Grouping batch ${batchNum}/${totalBatches} (${batchFiles.length} files)...`);
|
|
10471
10933
|
const userMessage = `Group these changed files into logical atomic commits:
|
|
10472
10934
|
|
|
10473
10935
|
Files:
|
|
@@ -10490,7 +10952,11 @@ NOTE: Processing batch ${batchNum}/${totalBatches} of a large changeset. Group o
|
|
|
10490
10952
|
const batchFileSet = new Set(batchFiles);
|
|
10491
10953
|
const filteredFiles = group.files.filter((f3) => batchFileSet.has(f3));
|
|
10492
10954
|
if (filteredFiles.length > 0) {
|
|
10493
|
-
allGroups.push({
|
|
10955
|
+
allGroups.push({
|
|
10956
|
+
...group,
|
|
10957
|
+
files: filteredFiles,
|
|
10958
|
+
message: sanitizeGeneratedCommitMessage(group.message)
|
|
10959
|
+
});
|
|
10494
10960
|
}
|
|
10495
10961
|
}
|
|
10496
10962
|
}
|
|
@@ -10500,10 +10966,7 @@ NOTE: Processing batch ${batchNum}/${totalBatches} of a large changeset. Group o
|
|
|
10500
10966
|
const groupedFiles = new Set(allGroups.flatMap((g3) => g3.files));
|
|
10501
10967
|
const ungrouped = files.filter((f3) => !groupedFiles.has(f3));
|
|
10502
10968
|
if (ungrouped.length > 0) {
|
|
10503
|
-
allGroups.push(
|
|
10504
|
-
files: ungrouped,
|
|
10505
|
-
message: `chore: update ${ungrouped.length} remaining file${ungrouped.length !== 1 ? "s" : ""}`
|
|
10506
|
-
});
|
|
10969
|
+
allGroups.push(...createRecoveryCommitGroups(ungrouped, convention));
|
|
10507
10970
|
}
|
|
10508
10971
|
if (allGroups.length === 0) {
|
|
10509
10972
|
throw new Error("AI could not group any files even with batch processing");
|
|
@@ -10533,7 +10996,7 @@ ${diffContent}`;
|
|
|
10533
10996
|
return groups;
|
|
10534
10997
|
return groups.map((g3, i2) => ({
|
|
10535
10998
|
files: g3.files,
|
|
10536
|
-
message: typeof parsed[i2]?.message === "string" ? parsed[i2].message : g3.message
|
|
10999
|
+
message: typeof parsed[i2]?.message === "string" ? sanitizeGeneratedCommitMessage(parsed[i2].message) : g3.message
|
|
10537
11000
|
}));
|
|
10538
11001
|
} catch {
|
|
10539
11002
|
return groups;
|
|
@@ -10550,60 +11013,303 @@ Files: ${files.join(", ")}
|
|
|
10550
11013
|
Diff:
|
|
10551
11014
|
${diffContent}`;
|
|
10552
11015
|
const result = await callCopilot(getCommitSystemPrompt(convention), userMessage, model);
|
|
10553
|
-
return result
|
|
11016
|
+
return result ? sanitizeGeneratedCommitMessage(result) : null;
|
|
10554
11017
|
} catch {
|
|
10555
11018
|
return null;
|
|
10556
11019
|
}
|
|
10557
11020
|
}
|
|
10558
11021
|
|
|
10559
|
-
// src/utils/
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
stderr: stderr ?? ""
|
|
10568
|
-
});
|
|
10569
|
-
});
|
|
10570
|
-
});
|
|
10571
|
-
}
|
|
10572
|
-
async function checkGhInstalled() {
|
|
10573
|
-
try {
|
|
10574
|
-
const { exitCode } = await run2(["--version"]);
|
|
10575
|
-
return exitCode === 0;
|
|
10576
|
-
} catch {
|
|
10577
|
-
return false;
|
|
10578
|
-
}
|
|
10579
|
-
}
|
|
10580
|
-
async function checkGhAuth() {
|
|
10581
|
-
try {
|
|
10582
|
-
const { exitCode } = await run2(["auth", "status"]);
|
|
10583
|
-
return exitCode === 0;
|
|
10584
|
-
} catch {
|
|
10585
|
-
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 [];
|
|
10586
11030
|
}
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
if (!
|
|
10591
|
-
return
|
|
10592
|
-
const { exitCode, stdout: stdout2 } = await run2(["api", `repos/${owner}/${repo}`, "--jq", ".permissions"]);
|
|
10593
|
-
if (exitCode !== 0)
|
|
10594
|
-
return null;
|
|
10595
|
-
try {
|
|
10596
|
-
return JSON.parse(stdout2.trim());
|
|
10597
|
-
} catch {
|
|
10598
|
-
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];
|
|
10599
11036
|
}
|
|
11037
|
+
const secondary = truncateText2(normalizedTip, Math.max(MIN_LINE_WIDTH, maxWidth - 2));
|
|
11038
|
+
return [primary, secondary];
|
|
10600
11039
|
}
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
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")
|
|
10607
11313
|
return true;
|
|
10608
11314
|
if (val === "false")
|
|
10609
11315
|
return false;
|
|
@@ -10694,53 +11400,6 @@ async function getMergedPRForBranch(headBranch) {
|
|
|
10694
11400
|
}
|
|
10695
11401
|
}
|
|
10696
11402
|
|
|
10697
|
-
// src/utils/spinner.ts
|
|
10698
|
-
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
10699
|
-
var FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
10700
|
-
function createSpinner(text) {
|
|
10701
|
-
let frameIdx = 0;
|
|
10702
|
-
let currentText = text;
|
|
10703
|
-
let stopped = false;
|
|
10704
|
-
const clearLine = () => {
|
|
10705
|
-
process.stderr.write("\r\x1B[K");
|
|
10706
|
-
};
|
|
10707
|
-
const render = () => {
|
|
10708
|
-
if (stopped)
|
|
10709
|
-
return;
|
|
10710
|
-
const frame = import_picocolors6.default.cyan(FRAMES[frameIdx % FRAMES.length]);
|
|
10711
|
-
clearLine();
|
|
10712
|
-
process.stderr.write(`${frame} ${currentText}`);
|
|
10713
|
-
frameIdx++;
|
|
10714
|
-
};
|
|
10715
|
-
const timer = setInterval(render, 80);
|
|
10716
|
-
render();
|
|
10717
|
-
const stop = () => {
|
|
10718
|
-
if (stopped)
|
|
10719
|
-
return;
|
|
10720
|
-
stopped = true;
|
|
10721
|
-
clearInterval(timer);
|
|
10722
|
-
clearLine();
|
|
10723
|
-
};
|
|
10724
|
-
return {
|
|
10725
|
-
update(newText) {
|
|
10726
|
-
currentText = newText;
|
|
10727
|
-
},
|
|
10728
|
-
success(msg) {
|
|
10729
|
-
stop();
|
|
10730
|
-
process.stderr.write(`${import_picocolors6.default.green("✔")} ${msg}
|
|
10731
|
-
`);
|
|
10732
|
-
},
|
|
10733
|
-
fail(msg) {
|
|
10734
|
-
stop();
|
|
10735
|
-
process.stderr.write(`${import_picocolors6.default.red("✖")} ${msg}
|
|
10736
|
-
`);
|
|
10737
|
-
},
|
|
10738
|
-
stop() {
|
|
10739
|
-
stop();
|
|
10740
|
-
}
|
|
10741
|
-
};
|
|
10742
|
-
}
|
|
10743
|
-
|
|
10744
11403
|
// src/commands/clean.ts
|
|
10745
11404
|
async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
10746
11405
|
if (!config)
|
|
@@ -10753,44 +11412,22 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
10753
11412
|
warn("You have uncommitted changes in your working tree.");
|
|
10754
11413
|
}
|
|
10755
11414
|
if (localWork.unpushedCommits > 0) {
|
|
10756
|
-
warn(`You have ${
|
|
11415
|
+
warn(`You have ${import_picocolors8.default.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not pushed.`);
|
|
10757
11416
|
}
|
|
10758
11417
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
10759
11418
|
const DISCARD = "Discard all changes and clean up";
|
|
10760
11419
|
const CANCEL = "Skip this branch";
|
|
10761
|
-
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]);
|
|
10762
11421
|
if (action === CANCEL)
|
|
10763
11422
|
return "skipped";
|
|
10764
11423
|
if (action === SAVE_NEW_BRANCH) {
|
|
10765
11424
|
if (!config)
|
|
10766
11425
|
return "skipped";
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
const suggested = await suggestBranchName(description);
|
|
10773
|
-
if (suggested) {
|
|
10774
|
-
spinner.success("Branch name suggestion ready.");
|
|
10775
|
-
console.log(`
|
|
10776
|
-
${import_picocolors7.default.dim("AI suggestion:")} ${import_picocolors7.default.bold(import_picocolors7.default.cyan(suggested))}`);
|
|
10777
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors7.default.bold(suggested)} as your branch name?`);
|
|
10778
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
10779
|
-
} else {
|
|
10780
|
-
spinner.fail("AI did not return a suggestion.");
|
|
10781
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
10782
|
-
}
|
|
10783
|
-
}
|
|
10784
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
10785
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors7.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
10786
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
10787
|
-
}
|
|
10788
|
-
if (!isValidBranchName(newBranchName)) {
|
|
10789
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
10790
|
-
return "skipped";
|
|
10791
|
-
}
|
|
10792
|
-
if (await branchExists(newBranchName)) {
|
|
10793
|
-
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) {
|
|
10794
11431
|
return "skipped";
|
|
10795
11432
|
}
|
|
10796
11433
|
const renameResult = await renameBranch(currentBranch, newBranchName);
|
|
@@ -10798,7 +11435,7 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
10798
11435
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
10799
11436
|
return "skipped";
|
|
10800
11437
|
}
|
|
10801
|
-
success(`Renamed ${
|
|
11438
|
+
success(`Renamed ${import_picocolors8.default.bold(currentBranch)} → ${import_picocolors8.default.bold(newBranchName)}`);
|
|
10802
11439
|
const syncSource2 = getSyncSource(config);
|
|
10803
11440
|
await fetchRemote(syncSource2.remote);
|
|
10804
11441
|
const savedUpstreamRef = await getUpstreamRef();
|
|
@@ -10806,10 +11443,10 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
10806
11443
|
if (rebaseResult.exitCode !== 0) {
|
|
10807
11444
|
await rebaseAbort();
|
|
10808
11445
|
warn("Rebase had conflicts — aborted to keep the repo in a clean state.");
|
|
10809
|
-
info(`Your work is saved on ${
|
|
10810
|
-
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}`)}`, "");
|
|
10811
11448
|
} else {
|
|
10812
|
-
success(`Rebased ${
|
|
11449
|
+
success(`Rebased ${import_picocolors8.default.bold(newBranchName)} onto ${import_picocolors8.default.bold(syncSource2.ref)}.`);
|
|
10813
11450
|
}
|
|
10814
11451
|
const coResult2 = await checkoutBranch(baseBranch);
|
|
10815
11452
|
if (coResult2.exitCode !== 0) {
|
|
@@ -10817,12 +11454,12 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
10817
11454
|
return "saved";
|
|
10818
11455
|
}
|
|
10819
11456
|
await updateLocalBranch(baseBranch, syncSource2.ref);
|
|
10820
|
-
success(`Synced ${
|
|
11457
|
+
success(`Synced ${import_picocolors8.default.bold(baseBranch)} with ${import_picocolors8.default.bold(syncSource2.ref)}.`);
|
|
10821
11458
|
return "saved";
|
|
10822
11459
|
}
|
|
10823
11460
|
}
|
|
10824
11461
|
const syncSource = getSyncSource(config);
|
|
10825
|
-
info(`Switching to ${
|
|
11462
|
+
info(`Switching to ${import_picocolors8.default.bold(baseBranch)} and syncing...`);
|
|
10826
11463
|
await fetchRemote(syncSource.remote);
|
|
10827
11464
|
await resetHard("HEAD");
|
|
10828
11465
|
const coResult = await checkoutBranch(baseBranch);
|
|
@@ -10831,7 +11468,7 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
10831
11468
|
return "skipped";
|
|
10832
11469
|
}
|
|
10833
11470
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
10834
|
-
success(`Synced ${
|
|
11471
|
+
success(`Synced ${import_picocolors8.default.bold(baseBranch)} with ${import_picocolors8.default.bold(syncSource.ref)}.`);
|
|
10835
11472
|
return "switched";
|
|
10836
11473
|
}
|
|
10837
11474
|
var clean_default = defineCommand({
|
|
@@ -10855,13 +11492,13 @@ var clean_default = defineCommand({
|
|
|
10855
11492
|
await assertCleanGitState("cleaning");
|
|
10856
11493
|
const config = readConfig();
|
|
10857
11494
|
if (!config) {
|
|
10858
|
-
error("No
|
|
11495
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
10859
11496
|
process.exit(1);
|
|
10860
11497
|
}
|
|
10861
11498
|
const { origin } = config;
|
|
10862
11499
|
const baseBranch = getBaseBranch(config);
|
|
10863
11500
|
let currentBranch = await getCurrentBranch();
|
|
10864
|
-
|
|
11501
|
+
projectHeading("clean", "\uD83E\uDDF9");
|
|
10865
11502
|
info(`Pruning ${origin} remote refs...`);
|
|
10866
11503
|
const pruneResult = await pruneRemote(origin);
|
|
10867
11504
|
if (pruneResult.exitCode === 0) {
|
|
@@ -10881,21 +11518,21 @@ var clean_default = defineCommand({
|
|
|
10881
11518
|
if (ghInstalled && ghAuthed) {
|
|
10882
11519
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
10883
11520
|
if (mergedPR) {
|
|
10884
|
-
warn(`PR #${mergedPR.number} (${
|
|
10885
|
-
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)}`, "");
|
|
10886
11523
|
goneCandidates.push(currentBranch);
|
|
10887
11524
|
}
|
|
10888
11525
|
}
|
|
10889
11526
|
}
|
|
10890
11527
|
if (mergedCandidates.length > 0) {
|
|
10891
11528
|
console.log(`
|
|
10892
|
-
${
|
|
11529
|
+
${import_picocolors8.default.bold("Merged branches to delete:")}`);
|
|
10893
11530
|
for (const b2 of mergedCandidates) {
|
|
10894
|
-
const marker = b2 === currentBranch ?
|
|
10895
|
-
console.log(` ${
|
|
11531
|
+
const marker = b2 === currentBranch ? import_picocolors8.default.yellow(" (current)") : "";
|
|
11532
|
+
console.log(` ${import_picocolors8.default.dim("•")} ${b2}${marker}`);
|
|
10896
11533
|
}
|
|
10897
11534
|
console.log();
|
|
10898
|
-
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" : ""}?`);
|
|
10899
11536
|
if (ok) {
|
|
10900
11537
|
for (const branch of mergedCandidates) {
|
|
10901
11538
|
if (branch === currentBranch) {
|
|
@@ -10912,7 +11549,7 @@ ${import_picocolors7.default.bold("Merged branches to delete:")}`);
|
|
|
10912
11549
|
}
|
|
10913
11550
|
const result = await deleteBranch(branch);
|
|
10914
11551
|
if (result.exitCode === 0) {
|
|
10915
|
-
success(` Deleted ${
|
|
11552
|
+
success(` Deleted ${import_picocolors8.default.bold(branch)}`);
|
|
10916
11553
|
} else {
|
|
10917
11554
|
warn(` Failed to delete ${branch}: ${result.stderr.trim()}`);
|
|
10918
11555
|
}
|
|
@@ -10923,13 +11560,13 @@ ${import_picocolors7.default.bold("Merged branches to delete:")}`);
|
|
|
10923
11560
|
}
|
|
10924
11561
|
if (goneCandidates.length > 0) {
|
|
10925
11562
|
console.log(`
|
|
10926
|
-
${
|
|
11563
|
+
${import_picocolors8.default.bold("Stale branches (remote deleted, likely squash-merged):")}`);
|
|
10927
11564
|
for (const b2 of goneCandidates) {
|
|
10928
|
-
const marker = b2 === currentBranch ?
|
|
10929
|
-
console.log(` ${
|
|
11565
|
+
const marker = b2 === currentBranch ? import_picocolors8.default.yellow(" (current)") : "";
|
|
11566
|
+
console.log(` ${import_picocolors8.default.dim("•")} ${b2}${marker}`);
|
|
10930
11567
|
}
|
|
10931
11568
|
console.log();
|
|
10932
|
-
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" : ""}?`);
|
|
10933
11570
|
if (ok) {
|
|
10934
11571
|
for (const branch of goneCandidates) {
|
|
10935
11572
|
if (branch === currentBranch) {
|
|
@@ -10946,7 +11583,7 @@ ${import_picocolors7.default.bold("Stale branches (remote deleted, likely squash
|
|
|
10946
11583
|
}
|
|
10947
11584
|
const result = await forceDeleteBranch(branch);
|
|
10948
11585
|
if (result.exitCode === 0) {
|
|
10949
|
-
success(` Deleted ${
|
|
11586
|
+
success(` Deleted ${import_picocolors8.default.bold(branch)}`);
|
|
10950
11587
|
} else {
|
|
10951
11588
|
warn(` Failed to delete ${branch}: ${result.stderr.trim()}`);
|
|
10952
11589
|
}
|
|
@@ -10961,13 +11598,13 @@ ${import_picocolors7.default.bold("Stale branches (remote deleted, likely squash
|
|
|
10961
11598
|
const finalBranch = await getCurrentBranch();
|
|
10962
11599
|
if (finalBranch && protectedBranches.has(finalBranch)) {
|
|
10963
11600
|
console.log();
|
|
10964
|
-
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.`);
|
|
10965
11602
|
}
|
|
10966
11603
|
}
|
|
10967
11604
|
});
|
|
10968
11605
|
|
|
10969
11606
|
// src/commands/commit.ts
|
|
10970
|
-
var
|
|
11607
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
10971
11608
|
|
|
10972
11609
|
// src/utils/convention.ts
|
|
10973
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;
|
|
@@ -10986,17 +11623,24 @@ var CONVENTION_FORMAT_HINTS = {
|
|
|
10986
11623
|
conventional: [
|
|
10987
11624
|
"Format: <type>[!][(<scope>)]: <description>",
|
|
10988
11625
|
"Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert",
|
|
10989
|
-
"Examples: feat: add login page | fix(auth): resolve token expiry | docs: update README"
|
|
11626
|
+
"Examples: feat: add login page | fix(auth): resolve token expiry | docs: update README",
|
|
11627
|
+
"Do not use backticks or markdown formatting in the message."
|
|
10990
11628
|
],
|
|
10991
11629
|
"clean-commit": [
|
|
10992
11630
|
"Format: <emoji> <type>[!][(<scope>)]: <description>",
|
|
10993
11631
|
"Types: \uD83D\uDCE6 new | \uD83D\uDD27 update | \uD83D\uDDD1️ remove | \uD83D\uDD12 security | ⚙️ setup | ☕ chore | \uD83E\uDDEA test | \uD83D\uDCD6 docs | \uD83D\uDE80 release",
|
|
10994
|
-
"Examples: \uD83D\uDCE6 new: user auth | \uD83D\uDD27 update (api): improve errors | ⚙️ setup (ci): add workflow"
|
|
11632
|
+
"Examples: \uD83D\uDCE6 new: user auth | \uD83D\uDD27 update (api): improve errors | ⚙️ setup (ci): add workflow",
|
|
11633
|
+
"Do not use backticks or markdown formatting in the message."
|
|
10995
11634
|
]
|
|
10996
11635
|
};
|
|
11636
|
+
function hasUnsupportedCommitMessageChars(message) {
|
|
11637
|
+
return message.includes("`");
|
|
11638
|
+
}
|
|
10997
11639
|
function validateCommitMessage(message, convention) {
|
|
10998
11640
|
if (convention === "none")
|
|
10999
11641
|
return true;
|
|
11642
|
+
if (hasUnsupportedCommitMessageChars(message))
|
|
11643
|
+
return false;
|
|
11000
11644
|
if (convention === "clean-commit")
|
|
11001
11645
|
return CLEAN_COMMIT_PATTERN.test(message);
|
|
11002
11646
|
if (convention === "conventional")
|
|
@@ -11008,11 +11652,15 @@ function getValidationError(convention) {
|
|
|
11008
11652
|
return [];
|
|
11009
11653
|
return [
|
|
11010
11654
|
`Commit message does not follow ${CONVENTION_LABELS[convention]} format.`,
|
|
11655
|
+
"Do not use backticks or markdown formatting in commit messages.",
|
|
11011
11656
|
...CONVENTION_FORMAT_HINTS[convention]
|
|
11012
11657
|
];
|
|
11013
11658
|
}
|
|
11014
11659
|
|
|
11015
11660
|
// src/commands/commit.ts
|
|
11661
|
+
function isEmptyGroupCommitResult(detail) {
|
|
11662
|
+
return /no changes added to commit|nothing to commit/i.test(detail);
|
|
11663
|
+
}
|
|
11016
11664
|
var commit_default = defineCommand({
|
|
11017
11665
|
meta: {
|
|
11018
11666
|
name: "commit",
|
|
@@ -11042,11 +11690,16 @@ var commit_default = defineCommand({
|
|
|
11042
11690
|
await assertCleanGitState("committing");
|
|
11043
11691
|
const config = readConfig();
|
|
11044
11692
|
if (!config) {
|
|
11045
|
-
error("No
|
|
11693
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
11046
11694
|
process.exit(1);
|
|
11047
11695
|
}
|
|
11048
|
-
|
|
11696
|
+
projectHeading("commit", "\uD83D\uDCBE");
|
|
11697
|
+
const aiEnabled = isAIEnabled(config, args["no-ai"]);
|
|
11049
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
|
+
}
|
|
11050
11703
|
await runGroupCommit(args.model, config);
|
|
11051
11704
|
return;
|
|
11052
11705
|
}
|
|
@@ -11058,9 +11711,9 @@ var commit_default = defineCommand({
|
|
|
11058
11711
|
process.exit(1);
|
|
11059
11712
|
}
|
|
11060
11713
|
console.log(`
|
|
11061
|
-
${
|
|
11714
|
+
${import_picocolors9.default.bold("Changed files:")}`);
|
|
11062
11715
|
for (const f3 of changedFiles) {
|
|
11063
|
-
console.log(` ${
|
|
11716
|
+
console.log(` ${import_picocolors9.default.dim("•")} ${f3}`);
|
|
11064
11717
|
}
|
|
11065
11718
|
const stageAction = await selectPrompt("No staged changes. How would you like to stage?", [
|
|
11066
11719
|
"Stage all changes",
|
|
@@ -11102,8 +11755,8 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11102
11755
|
const dirs = new Set(stagedFiles.map((f3) => f3.split("/")[0]));
|
|
11103
11756
|
if (dirs.size > 1) {
|
|
11104
11757
|
console.log();
|
|
11105
|
-
warn(`You're staging ${
|
|
11106
|
-
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."));
|
|
11107
11760
|
const choice = await selectPrompt("How would you like to proceed?", [
|
|
11108
11761
|
"Continue as single commit",
|
|
11109
11762
|
"Switch to group mode (AI splits into atomic commits)",
|
|
@@ -11119,20 +11772,22 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11119
11772
|
}
|
|
11120
11773
|
}
|
|
11121
11774
|
let commitMessage = null;
|
|
11122
|
-
const useAI =
|
|
11775
|
+
const useAI = aiEnabled;
|
|
11123
11776
|
if (useAI) {
|
|
11124
|
-
const [copilotError, diff] = await Promise.all([
|
|
11777
|
+
const [copilotError, diff] = await Promise.all([checkCopilotAvailable2(), getStagedDiff()]);
|
|
11125
11778
|
if (copilotError) {
|
|
11126
11779
|
warn(`AI unavailable: ${copilotError}`);
|
|
11127
11780
|
warn("Falling back to manual commit message entry.");
|
|
11128
11781
|
} else {
|
|
11129
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...";
|
|
11130
|
-
const spinner = createSpinner(spinnerMsg
|
|
11783
|
+
const spinner = createSpinner(spinnerMsg, {
|
|
11784
|
+
tips: LOADING_TIPS
|
|
11785
|
+
});
|
|
11131
11786
|
commitMessage = await generateCommitMessage(diff, stagedFiles, args.model, config.commitConvention);
|
|
11132
11787
|
if (commitMessage) {
|
|
11133
11788
|
spinner.success("AI commit message generated.");
|
|
11134
11789
|
console.log(`
|
|
11135
|
-
${
|
|
11790
|
+
${import_picocolors9.default.dim("AI suggestion:")} ${import_picocolors9.default.bold(import_picocolors9.default.cyan(commitMessage))}`);
|
|
11136
11791
|
} else {
|
|
11137
11792
|
spinner.fail("AI did not return a commit message.");
|
|
11138
11793
|
warn("Falling back to manual entry.");
|
|
@@ -11152,13 +11807,15 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11152
11807
|
} else if (action === "Edit this message") {
|
|
11153
11808
|
finalMessage = await inputPrompt("Edit commit message", commitMessage);
|
|
11154
11809
|
} else if (action === "Regenerate") {
|
|
11155
|
-
const spinner = createSpinner("Regenerating commit message..."
|
|
11810
|
+
const spinner = createSpinner("Regenerating commit message...", {
|
|
11811
|
+
tips: LOADING_TIPS
|
|
11812
|
+
});
|
|
11156
11813
|
const diff = await getStagedDiff();
|
|
11157
11814
|
const regen = await generateCommitMessage(diff, stagedFiles, args.model, config.commitConvention);
|
|
11158
11815
|
if (regen) {
|
|
11159
11816
|
spinner.success("Commit message regenerated.");
|
|
11160
11817
|
console.log(`
|
|
11161
|
-
${
|
|
11818
|
+
${import_picocolors9.default.dim("AI suggestion:")} ${import_picocolors9.default.bold(import_picocolors9.default.cyan(regen))}`);
|
|
11162
11819
|
const ok = await confirmPrompt("Use this message?");
|
|
11163
11820
|
finalMessage = ok ? regen : await inputPrompt("Enter commit message manually");
|
|
11164
11821
|
} else {
|
|
@@ -11173,7 +11830,7 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11173
11830
|
if (convention2 !== "none") {
|
|
11174
11831
|
console.log();
|
|
11175
11832
|
for (const hint of CONVENTION_FORMAT_HINTS[convention2]) {
|
|
11176
|
-
console.log(
|
|
11833
|
+
console.log(import_picocolors9.default.dim(hint));
|
|
11177
11834
|
}
|
|
11178
11835
|
console.log();
|
|
11179
11836
|
}
|
|
@@ -11197,12 +11854,12 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11197
11854
|
error(`Failed to commit: ${result.stderr}`);
|
|
11198
11855
|
process.exit(1);
|
|
11199
11856
|
}
|
|
11200
|
-
success(`Committed: ${
|
|
11857
|
+
success(`Committed: ${import_picocolors9.default.bold(finalMessage)}`);
|
|
11201
11858
|
}
|
|
11202
11859
|
});
|
|
11203
11860
|
async function runGroupCommit(model, config) {
|
|
11204
11861
|
const [copilotError, changedFiles] = await Promise.all([
|
|
11205
|
-
|
|
11862
|
+
checkCopilotAvailable2(),
|
|
11206
11863
|
getChangedFiles()
|
|
11207
11864
|
]);
|
|
11208
11865
|
if (copilotError) {
|
|
@@ -11214,11 +11871,13 @@ async function runGroupCommit(model, config) {
|
|
|
11214
11871
|
process.exit(1);
|
|
11215
11872
|
}
|
|
11216
11873
|
console.log(`
|
|
11217
|
-
${
|
|
11874
|
+
${import_picocolors9.default.bold("Changed files:")}`);
|
|
11218
11875
|
for (const f3 of changedFiles) {
|
|
11219
|
-
console.log(` ${
|
|
11876
|
+
console.log(` ${import_picocolors9.default.dim("•")} ${f3}`);
|
|
11220
11877
|
}
|
|
11221
|
-
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
|
+
});
|
|
11222
11881
|
const diffs = await getFullDiffForFiles(changedFiles);
|
|
11223
11882
|
if (!diffs.trim()) {
|
|
11224
11883
|
spinner.stop();
|
|
@@ -11226,7 +11885,7 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11226
11885
|
}
|
|
11227
11886
|
let groups;
|
|
11228
11887
|
try {
|
|
11229
|
-
groups = await generateCommitGroups(changedFiles, diffs, model, config.commitConvention);
|
|
11888
|
+
groups = await generateCommitGroups(changedFiles, diffs, model, config.commitConvention, (message) => spinner.update(message));
|
|
11230
11889
|
spinner.success(`AI generated ${groups.length} commit group(s).`);
|
|
11231
11890
|
} catch (err) {
|
|
11232
11891
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -11237,15 +11896,21 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11237
11896
|
error("AI could not produce commit groups. Try committing files manually.");
|
|
11238
11897
|
process.exit(1);
|
|
11239
11898
|
}
|
|
11240
|
-
const
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
}
|
|
11246
|
-
|
|
11899
|
+
const normalized = normalizeCommitGroups(changedFiles, groups);
|
|
11900
|
+
if (normalized.unknownFiles.length > 0) {
|
|
11901
|
+
warn(`AI suggested unknown file(s): ${normalized.unknownFiles.join(", ")} — removed from groups.`);
|
|
11902
|
+
}
|
|
11903
|
+
if (normalized.duplicateFiles.length > 0) {
|
|
11904
|
+
warn(`AI assigned duplicate file(s) across groups: ${normalized.duplicateFiles.join(", ")} — keeping the first assignment only.`);
|
|
11905
|
+
}
|
|
11906
|
+
let validGroups = normalized.groups;
|
|
11907
|
+
if (normalized.unassignedFiles.length > 0) {
|
|
11908
|
+
warn(`AI left ${normalized.unassignedFiles.length} file(s) ungrouped: ${normalized.unassignedFiles.join(", ")}. Auto-resolving recovery groups.`);
|
|
11909
|
+
validGroups = [
|
|
11910
|
+
...validGroups,
|
|
11911
|
+
...createRecoveryCommitGroups(normalized.unassignedFiles, config.commitConvention)
|
|
11912
|
+
];
|
|
11247
11913
|
}
|
|
11248
|
-
let validGroups = groups.filter((g3) => g3.files.length > 0);
|
|
11249
11914
|
if (validGroups.length === 0) {
|
|
11250
11915
|
error("No valid groups remain after validation. Try committing files manually.");
|
|
11251
11916
|
process.exit(1);
|
|
@@ -11254,13 +11919,13 @@ ${import_picocolors8.default.bold("Changed files:")}`);
|
|
|
11254
11919
|
let commitAll = false;
|
|
11255
11920
|
while (!proceedToCommit) {
|
|
11256
11921
|
console.log(`
|
|
11257
|
-
${
|
|
11922
|
+
${import_picocolors9.default.bold(`AI suggested ${validGroups.length} commit group(s):`)}
|
|
11258
11923
|
`);
|
|
11259
11924
|
for (let i2 = 0;i2 < validGroups.length; i2++) {
|
|
11260
11925
|
const g3 = validGroups[i2];
|
|
11261
|
-
console.log(` ${
|
|
11926
|
+
console.log(` ${import_picocolors9.default.cyan(`Group ${i2 + 1}:`)} ${import_picocolors9.default.bold(g3.message)}`);
|
|
11262
11927
|
for (const f3 of g3.files) {
|
|
11263
|
-
console.log(` ${
|
|
11928
|
+
console.log(` ${import_picocolors9.default.dim("•")} ${f3}`);
|
|
11264
11929
|
}
|
|
11265
11930
|
console.log();
|
|
11266
11931
|
}
|
|
@@ -11275,7 +11940,9 @@ ${import_picocolors8.default.bold(`AI suggested ${validGroups.length} commit gro
|
|
|
11275
11940
|
process.exit(0);
|
|
11276
11941
|
}
|
|
11277
11942
|
if (summaryAction === "Regenerate all messages") {
|
|
11278
|
-
const regenSpinner = createSpinner("Regenerating all commit messages..."
|
|
11943
|
+
const regenSpinner = createSpinner("Regenerating all commit messages...", {
|
|
11944
|
+
tips: LOADING_TIPS
|
|
11945
|
+
});
|
|
11279
11946
|
try {
|
|
11280
11947
|
validGroups = await regenerateAllGroupMessages(validGroups, diffs, model, config.commitConvention);
|
|
11281
11948
|
regenSpinner.success("All commit messages regenerated.");
|
|
@@ -11291,29 +11958,49 @@ ${import_picocolors8.default.bold(`AI suggested ${validGroups.length} commit gro
|
|
|
11291
11958
|
if (commitAll) {
|
|
11292
11959
|
for (let i2 = 0;i2 < validGroups.length; i2++) {
|
|
11293
11960
|
const group = validGroups[i2];
|
|
11294
|
-
const
|
|
11961
|
+
const remainingChangedFiles = new Set(await getChangedFiles());
|
|
11962
|
+
const stageableFiles = group.files.filter((file) => remainingChangedFiles.has(file));
|
|
11963
|
+
const skippedFiles = group.files.filter((file) => !remainingChangedFiles.has(file));
|
|
11964
|
+
if (skippedFiles.length > 0) {
|
|
11965
|
+
warn(`Group ${i2 + 1} file(s) no longer have changes: ${skippedFiles.join(", ")}`);
|
|
11966
|
+
}
|
|
11967
|
+
if (stageableFiles.length === 0) {
|
|
11968
|
+
warn(`Skipped group ${i2 + 1}: no files remain to commit.`);
|
|
11969
|
+
continue;
|
|
11970
|
+
}
|
|
11971
|
+
const stageResult = await stageFiles(stageableFiles);
|
|
11295
11972
|
if (stageResult.exitCode !== 0) {
|
|
11296
11973
|
error(`Failed to stage group ${i2 + 1}: ${stageResult.stderr}`);
|
|
11297
11974
|
continue;
|
|
11298
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
|
+
}
|
|
11299
11982
|
const commitResult = await commitWithMessage(group.message);
|
|
11300
11983
|
if (commitResult.exitCode !== 0) {
|
|
11301
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
|
+
}
|
|
11302
11989
|
error(`Failed to commit group ${i2 + 1}: ${detail}`);
|
|
11303
|
-
await unstageFiles(
|
|
11990
|
+
await unstageFiles(stageableFiles);
|
|
11304
11991
|
continue;
|
|
11305
11992
|
}
|
|
11306
11993
|
committed++;
|
|
11307
|
-
success(`Committed group ${i2 + 1}: ${
|
|
11994
|
+
success(`Committed group ${i2 + 1}: ${import_picocolors9.default.bold(group.message)}`);
|
|
11308
11995
|
}
|
|
11309
11996
|
} else {
|
|
11310
11997
|
for (let i2 = 0;i2 < validGroups.length; i2++) {
|
|
11311
11998
|
const group = validGroups[i2];
|
|
11312
|
-
console.log(
|
|
11999
|
+
console.log(import_picocolors9.default.bold(`
|
|
11313
12000
|
── Group ${i2 + 1}/${validGroups.length} ──`));
|
|
11314
|
-
console.log(` ${
|
|
12001
|
+
console.log(` ${import_picocolors9.default.cyan(group.message)}`);
|
|
11315
12002
|
for (const f3 of group.files) {
|
|
11316
|
-
console.log(` ${
|
|
12003
|
+
console.log(` ${import_picocolors9.default.dim("•")} ${f3}`);
|
|
11317
12004
|
}
|
|
11318
12005
|
let message = group.message;
|
|
11319
12006
|
let actionDone = false;
|
|
@@ -11330,12 +12017,14 @@ ${import_picocolors8.default.bold(`AI suggested ${validGroups.length} commit gro
|
|
|
11330
12017
|
continue;
|
|
11331
12018
|
}
|
|
11332
12019
|
if (action === "Regenerate message") {
|
|
11333
|
-
const regenSpinner = createSpinner("Regenerating commit message for this group..."
|
|
12020
|
+
const regenSpinner = createSpinner("Regenerating commit message for this group...", {
|
|
12021
|
+
tips: LOADING_TIPS
|
|
12022
|
+
});
|
|
11334
12023
|
const newMsg = await regenerateGroupMessage(group.files, diffs, model, config.commitConvention);
|
|
11335
12024
|
if (newMsg) {
|
|
11336
12025
|
message = newMsg;
|
|
11337
12026
|
group.message = newMsg;
|
|
11338
|
-
regenSpinner.success(`New message: ${
|
|
12027
|
+
regenSpinner.success(`New message: ${import_picocolors9.default.bold(message)}`);
|
|
11339
12028
|
} else {
|
|
11340
12029
|
regenSpinner.fail("AI could not generate a new message. Keeping current one.");
|
|
11341
12030
|
}
|
|
@@ -11360,22 +12049,45 @@ ${import_picocolors8.default.bold(`AI suggested ${validGroups.length} commit gro
|
|
|
11360
12049
|
continue;
|
|
11361
12050
|
}
|
|
11362
12051
|
}
|
|
11363
|
-
const
|
|
12052
|
+
const remainingChangedFiles = new Set(await getChangedFiles());
|
|
12053
|
+
const stageableFiles = group.files.filter((file) => remainingChangedFiles.has(file));
|
|
12054
|
+
const skippedFiles = group.files.filter((file) => !remainingChangedFiles.has(file));
|
|
12055
|
+
if (skippedFiles.length > 0) {
|
|
12056
|
+
warn(`Group ${i2 + 1} file(s) no longer have changes: ${skippedFiles.join(", ")}`);
|
|
12057
|
+
}
|
|
12058
|
+
if (stageableFiles.length === 0) {
|
|
12059
|
+
warn(`Skipped group ${i2 + 1}: no files remain to commit.`);
|
|
12060
|
+
actionDone = true;
|
|
12061
|
+
continue;
|
|
12062
|
+
}
|
|
12063
|
+
const stageResult = await stageFiles(stageableFiles);
|
|
11364
12064
|
if (stageResult.exitCode !== 0) {
|
|
11365
12065
|
error(`Failed to stage group ${i2 + 1}: ${stageResult.stderr}`);
|
|
11366
12066
|
actionDone = true;
|
|
11367
12067
|
continue;
|
|
11368
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
|
+
}
|
|
11369
12076
|
const commitResult = await commitWithMessage(message);
|
|
11370
12077
|
if (commitResult.exitCode !== 0) {
|
|
11371
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
|
+
}
|
|
11372
12084
|
error(`Failed to commit group ${i2 + 1}: ${detail}`);
|
|
11373
|
-
await unstageFiles(
|
|
12085
|
+
await unstageFiles(stageableFiles);
|
|
11374
12086
|
actionDone = true;
|
|
11375
12087
|
continue;
|
|
11376
12088
|
}
|
|
11377
12089
|
committed++;
|
|
11378
|
-
success(`Committed group ${i2 + 1}: ${
|
|
12090
|
+
success(`Committed group ${i2 + 1}: ${import_picocolors9.default.bold(message)}`);
|
|
11379
12091
|
actionDone = true;
|
|
11380
12092
|
}
|
|
11381
12093
|
}
|
|
@@ -11391,11 +12103,11 @@ ${import_picocolors8.default.bold(`AI suggested ${validGroups.length} commit gro
|
|
|
11391
12103
|
|
|
11392
12104
|
// src/commands/doctor.ts
|
|
11393
12105
|
import { execFile as execFileCb3 } from "node:child_process";
|
|
11394
|
-
var
|
|
12106
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
11395
12107
|
// package.json
|
|
11396
12108
|
var package_default = {
|
|
11397
12109
|
name: "contribute-now",
|
|
11398
|
-
version: "0.6.2-dev.
|
|
12110
|
+
version: "0.6.2-dev.d6e92ac",
|
|
11399
12111
|
description: "Developer CLI that automates git workflows — branching, syncing, committing, and PRs — with multi-workflow and commit convention support.",
|
|
11400
12112
|
type: "module",
|
|
11401
12113
|
bin: {
|
|
@@ -11486,16 +12198,16 @@ async function getRepoInfoFromRemote(remote = "origin") {
|
|
|
11486
12198
|
}
|
|
11487
12199
|
|
|
11488
12200
|
// src/commands/doctor.ts
|
|
11489
|
-
var PASS = ` ${
|
|
11490
|
-
var FAIL = ` ${
|
|
11491
|
-
var WARN = ` ${
|
|
12201
|
+
var PASS = ` ${import_picocolors10.default.green("✔")} `;
|
|
12202
|
+
var FAIL = ` ${import_picocolors10.default.red("✗")} `;
|
|
12203
|
+
var WARN = ` ${import_picocolors10.default.yellow("⚠")} `;
|
|
11492
12204
|
function printReport(report) {
|
|
11493
12205
|
for (const section of report.sections) {
|
|
11494
12206
|
console.log(`
|
|
11495
|
-
${
|
|
12207
|
+
${import_picocolors10.default.bold(import_picocolors10.default.underline(section.title))}`);
|
|
11496
12208
|
for (const check of section.checks) {
|
|
11497
12209
|
const prefix = check.ok ? check.warning ? WARN : PASS : FAIL;
|
|
11498
|
-
const text = check.detail ? `${check.label} ${
|
|
12210
|
+
const text = check.detail ? `${check.label} ${import_picocolors10.default.dim(`— ${check.detail}`)}` : check.label;
|
|
11499
12211
|
console.log(`${prefix}${text}`);
|
|
11500
12212
|
}
|
|
11501
12213
|
}
|
|
@@ -11513,9 +12225,9 @@ function toJson(report) {
|
|
|
11513
12225
|
})), null, 2);
|
|
11514
12226
|
}
|
|
11515
12227
|
function runCmd(cmd, args) {
|
|
11516
|
-
return new Promise((
|
|
12228
|
+
return new Promise((resolve3) => {
|
|
11517
12229
|
execFileCb3(cmd, args, (error2, stdout2) => {
|
|
11518
|
-
|
|
12230
|
+
resolve3({
|
|
11519
12231
|
ok: !error2,
|
|
11520
12232
|
stdout: (stdout2 ?? "").trim()
|
|
11521
12233
|
});
|
|
@@ -11577,18 +12289,32 @@ async function configSection() {
|
|
|
11577
12289
|
const exists = configExists();
|
|
11578
12290
|
if (!exists) {
|
|
11579
12291
|
checks.push({
|
|
11580
|
-
label: "
|
|
12292
|
+
label: "Repo config not found",
|
|
11581
12293
|
ok: false,
|
|
11582
|
-
detail: "run `contrib setup` to create
|
|
12294
|
+
detail: "run `contrib setup` to create local config for this clone"
|
|
11583
12295
|
});
|
|
11584
12296
|
return { title: "Config", checks };
|
|
11585
12297
|
}
|
|
11586
12298
|
const config = readConfig();
|
|
11587
12299
|
if (!config) {
|
|
11588
|
-
checks.push({ label: "
|
|
12300
|
+
checks.push({ label: "Repo config found but invalid", ok: false });
|
|
11589
12301
|
return { title: "Config", checks };
|
|
11590
12302
|
}
|
|
11591
|
-
|
|
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
|
+
}
|
|
11592
12318
|
const desc = WORKFLOW_DESCRIPTIONS[config.workflow] ?? config.workflow;
|
|
11593
12319
|
checks.push({
|
|
11594
12320
|
label: `Workflow: ${config.workflow}`,
|
|
@@ -11603,13 +12329,21 @@ async function configSection() {
|
|
|
11603
12329
|
ok: !!config.devBranch
|
|
11604
12330
|
});
|
|
11605
12331
|
}
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
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
|
+
}
|
|
11613
12347
|
return { title: "Config", checks };
|
|
11614
12348
|
}
|
|
11615
12349
|
async function gitSection() {
|
|
@@ -11758,20 +12492,20 @@ var doctor_default = defineCommand({
|
|
|
11758
12492
|
console.log(toJson(report));
|
|
11759
12493
|
return;
|
|
11760
12494
|
}
|
|
11761
|
-
|
|
12495
|
+
projectHeading("doctor", "\uD83E\uDE7A");
|
|
11762
12496
|
printReport(report);
|
|
11763
12497
|
const total = report.sections.flatMap((s2) => s2.checks);
|
|
11764
12498
|
const failures = total.filter((c3) => !c3.ok);
|
|
11765
12499
|
const warnings = total.filter((c3) => c3.ok && c3.warning);
|
|
11766
12500
|
if (failures.length === 0 && warnings.length === 0) {
|
|
11767
|
-
console.log(` ${
|
|
12501
|
+
console.log(` ${import_picocolors10.default.green("All checks passed!")} No issues detected.
|
|
11768
12502
|
`);
|
|
11769
12503
|
} else {
|
|
11770
12504
|
if (failures.length > 0) {
|
|
11771
|
-
console.log(` ${
|
|
12505
|
+
console.log(` ${import_picocolors10.default.red(`${failures.length} issue${failures.length !== 1 ? "s" : ""} found.`)}`);
|
|
11772
12506
|
}
|
|
11773
12507
|
if (warnings.length > 0) {
|
|
11774
|
-
console.log(` ${
|
|
12508
|
+
console.log(` ${import_picocolors10.default.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}.`)}`);
|
|
11775
12509
|
}
|
|
11776
12510
|
console.log();
|
|
11777
12511
|
}
|
|
@@ -11779,9 +12513,9 @@ var doctor_default = defineCommand({
|
|
|
11779
12513
|
});
|
|
11780
12514
|
|
|
11781
12515
|
// src/commands/hook.ts
|
|
11782
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
12516
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
11783
12517
|
import { join as join4 } from "node:path";
|
|
11784
|
-
var
|
|
12518
|
+
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
11785
12519
|
var HOOK_MARKER = "# managed by contribute-now";
|
|
11786
12520
|
function getHooksDir(cwd = process.cwd()) {
|
|
11787
12521
|
return join4(cwd, ".git", "hooks");
|
|
@@ -11806,13 +12540,13 @@ esac
|
|
|
11806
12540
|
|
|
11807
12541
|
# Detect available package runner
|
|
11808
12542
|
if command -v contrib >/dev/null 2>&1; then
|
|
11809
|
-
contrib validate "$
|
|
12543
|
+
contrib validate --file "$commit_msg_file"
|
|
11810
12544
|
elif command -v bunx >/dev/null 2>&1; then
|
|
11811
|
-
bunx contrib validate "$
|
|
12545
|
+
bunx contrib validate --file "$commit_msg_file"
|
|
11812
12546
|
elif command -v pnpx >/dev/null 2>&1; then
|
|
11813
|
-
pnpx contrib validate "$
|
|
12547
|
+
pnpx contrib validate --file "$commit_msg_file"
|
|
11814
12548
|
elif command -v npx >/dev/null 2>&1; then
|
|
11815
|
-
npx contrib validate "$
|
|
12549
|
+
npx contrib validate --file "$commit_msg_file"
|
|
11816
12550
|
else
|
|
11817
12551
|
echo "Warning: No package runner found. Skipping commit message validation."
|
|
11818
12552
|
exit 0
|
|
@@ -11849,10 +12583,10 @@ var hook_default = defineCommand({
|
|
|
11849
12583
|
}
|
|
11850
12584
|
});
|
|
11851
12585
|
async function installHook() {
|
|
11852
|
-
|
|
12586
|
+
projectHeading("hook install", "\uD83E\uDE9D");
|
|
11853
12587
|
const config = readConfig();
|
|
11854
12588
|
if (!config) {
|
|
11855
|
-
error("No
|
|
12589
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
11856
12590
|
process.exit(1);
|
|
11857
12591
|
}
|
|
11858
12592
|
if (config.commitConvention === "none") {
|
|
@@ -11873,16 +12607,16 @@ async function installHook() {
|
|
|
11873
12607
|
info("Updating existing contribute-now hook...");
|
|
11874
12608
|
}
|
|
11875
12609
|
if (!existsSync4(hooksDir)) {
|
|
11876
|
-
|
|
12610
|
+
mkdirSync3(hooksDir, { recursive: true });
|
|
11877
12611
|
}
|
|
11878
12612
|
writeFileSync3(hookPath, generateHookScript(), { mode: 493 });
|
|
11879
12613
|
success(`commit-msg hook installed.`);
|
|
11880
|
-
info(`Convention: ${
|
|
11881
|
-
info(`Path: ${
|
|
12614
|
+
info(`Convention: ${import_picocolors11.default.bold(CONVENTION_LABELS[config.commitConvention])}`, "");
|
|
12615
|
+
info(`Path: ${import_picocolors11.default.dim(hookPath)}`, "");
|
|
11882
12616
|
warn("Note: hooks can be bypassed with `git commit --no-verify`.");
|
|
11883
12617
|
}
|
|
11884
12618
|
async function uninstallHook() {
|
|
11885
|
-
|
|
12619
|
+
projectHeading("hook uninstall", "\uD83E\uDE9D");
|
|
11886
12620
|
const hookPath = getHookPath();
|
|
11887
12621
|
if (!existsSync4(hookPath)) {
|
|
11888
12622
|
info("No commit-msg hook found. Nothing to uninstall.");
|
|
@@ -11898,7 +12632,10 @@ async function uninstallHook() {
|
|
|
11898
12632
|
}
|
|
11899
12633
|
|
|
11900
12634
|
// src/commands/log.ts
|
|
11901
|
-
var
|
|
12635
|
+
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
12636
|
+
function getDefaultOverviewRemoteCommitCount(hasLocalUnpushedCommits) {
|
|
12637
|
+
return hasLocalUnpushedCommits ? 10 : 20;
|
|
12638
|
+
}
|
|
11902
12639
|
var log_default = defineCommand({
|
|
11903
12640
|
meta: {
|
|
11904
12641
|
name: "log",
|
|
@@ -11919,7 +12656,13 @@ var log_default = defineCommand({
|
|
|
11919
12656
|
remote: {
|
|
11920
12657
|
type: "boolean",
|
|
11921
12658
|
alias: "r",
|
|
11922
|
-
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",
|
|
11923
12666
|
default: false
|
|
11924
12667
|
},
|
|
11925
12668
|
full: {
|
|
@@ -11946,12 +12689,15 @@ var log_default = defineCommand({
|
|
|
11946
12689
|
process.exit(1);
|
|
11947
12690
|
}
|
|
11948
12691
|
const config = readConfig();
|
|
11949
|
-
const
|
|
12692
|
+
const requestedCount = args.count ? Number.parseInt(args.count, 10) : null;
|
|
12693
|
+
const localCount = requestedCount ?? 20;
|
|
11950
12694
|
const showGraph = args.graph;
|
|
11951
12695
|
const targetBranch = args.branch;
|
|
11952
|
-
let mode = "
|
|
12696
|
+
let mode = "overview";
|
|
11953
12697
|
if (args.all)
|
|
11954
12698
|
mode = "all";
|
|
12699
|
+
else if (args.local)
|
|
12700
|
+
mode = "local";
|
|
11955
12701
|
else if (args.remote)
|
|
11956
12702
|
mode = "remote";
|
|
11957
12703
|
else if (args.full || targetBranch)
|
|
@@ -11967,33 +12713,74 @@ var log_default = defineCommand({
|
|
|
11967
12713
|
compareRef = fallback;
|
|
11968
12714
|
usingFallback = true;
|
|
11969
12715
|
}
|
|
11970
|
-
}
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
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) {
|
|
11975
12753
|
console.log();
|
|
11976
|
-
console.log(
|
|
11977
|
-
console.log(
|
|
11978
|
-
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."));
|
|
11979
12756
|
console.log();
|
|
11980
|
-
printGuidance();
|
|
11981
12757
|
return;
|
|
11982
12758
|
}
|
|
11983
|
-
const hasCommits = await
|
|
12759
|
+
const hasCommits = await renderFullLog({
|
|
12760
|
+
count: localCount,
|
|
12761
|
+
all: false,
|
|
12762
|
+
showGraph,
|
|
12763
|
+
targetBranch: remoteBranch,
|
|
12764
|
+
protectedBranches,
|
|
12765
|
+
currentBranch
|
|
12766
|
+
});
|
|
11984
12767
|
if (!hasCommits) {
|
|
11985
|
-
printGuidance();
|
|
11986
12768
|
return;
|
|
11987
12769
|
}
|
|
11988
12770
|
} else {
|
|
11989
|
-
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
|
+
});
|
|
11990
12779
|
if (!hasCommits) {
|
|
11991
|
-
printGuidance();
|
|
11992
12780
|
return;
|
|
11993
12781
|
}
|
|
11994
12782
|
}
|
|
11995
|
-
printFooter(mode,
|
|
11996
|
-
printGuidance();
|
|
12783
|
+
printFooter(mode, localCount, overviewRemoteCount, targetBranch);
|
|
11997
12784
|
}
|
|
11998
12785
|
});
|
|
11999
12786
|
async function resolveBaseBranchRef(config) {
|
|
@@ -12015,38 +12802,49 @@ async function resolveBaseBranchRef(config) {
|
|
|
12015
12802
|
}
|
|
12016
12803
|
return null;
|
|
12017
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
|
+
}
|
|
12018
12812
|
function printModeHeader(mode, currentBranch, compareRef, usingFallback = false) {
|
|
12019
12813
|
const branch = currentBranch ?? "HEAD";
|
|
12020
|
-
const fallbackNote = usingFallback ?
|
|
12021
|
-
console.log();
|
|
12814
|
+
const fallbackNote = usingFallback ? import_picocolors12.default.yellow(" (no upstream — comparing against base branch)") : "";
|
|
12022
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;
|
|
12023
12822
|
case "local":
|
|
12024
|
-
console.log(
|
|
12823
|
+
console.log(import_picocolors12.default.dim(` mode: ${import_picocolors12.default.bold("local")} — unpushed commits on ${import_picocolors12.default.bold(branch)}`) + fallbackNote);
|
|
12025
12824
|
if (compareRef) {
|
|
12026
|
-
console.log(
|
|
12825
|
+
console.log(import_picocolors12.default.dim(` comparing: ${import_picocolors12.default.bold(compareRef)} ➜ ${import_picocolors12.default.bold("HEAD")}`));
|
|
12027
12826
|
}
|
|
12028
12827
|
break;
|
|
12029
12828
|
case "remote":
|
|
12030
|
-
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);
|
|
12031
12830
|
if (compareRef) {
|
|
12032
|
-
console.log(
|
|
12831
|
+
console.log(import_picocolors12.default.dim(` branch: ${import_picocolors12.default.bold(compareRef)}`));
|
|
12033
12832
|
}
|
|
12034
12833
|
break;
|
|
12035
12834
|
case "full":
|
|
12036
|
-
console.log(
|
|
12835
|
+
console.log(import_picocolors12.default.dim(` mode: ${import_picocolors12.default.bold("full")} — complete commit history for ${import_picocolors12.default.bold(branch)}`));
|
|
12037
12836
|
break;
|
|
12038
12837
|
case "all":
|
|
12039
|
-
console.log(
|
|
12838
|
+
console.log(import_picocolors12.default.dim(` mode: ${import_picocolors12.default.bold("all")} — commits across all branches`));
|
|
12040
12839
|
break;
|
|
12041
12840
|
}
|
|
12042
12841
|
}
|
|
12043
12842
|
async function renderScopedLog(options) {
|
|
12044
|
-
const {
|
|
12843
|
+
const { count, upstream, showGraph, protectedBranches, currentBranch } = options;
|
|
12045
12844
|
if (showGraph) {
|
|
12046
|
-
const
|
|
12047
|
-
const lines = await graphFn({ count, upstream });
|
|
12845
|
+
const lines = await getLocalCommitsGraph({ count, upstream });
|
|
12048
12846
|
if (lines.length === 0) {
|
|
12049
|
-
printEmptyState(
|
|
12847
|
+
printEmptyState("local");
|
|
12050
12848
|
return false;
|
|
12051
12849
|
}
|
|
12052
12850
|
console.log();
|
|
@@ -12054,15 +12852,14 @@ async function renderScopedLog(options) {
|
|
|
12054
12852
|
console.log(` ${colorizeGraphLine(line, protectedBranches, currentBranch)}`);
|
|
12055
12853
|
}
|
|
12056
12854
|
} else {
|
|
12057
|
-
const
|
|
12058
|
-
const entries = await entryFn({ count, upstream });
|
|
12855
|
+
const entries = await getLocalCommitsEntries({ count, upstream });
|
|
12059
12856
|
if (entries.length === 0) {
|
|
12060
|
-
printEmptyState(
|
|
12857
|
+
printEmptyState("local");
|
|
12061
12858
|
return false;
|
|
12062
12859
|
}
|
|
12063
12860
|
console.log();
|
|
12064
12861
|
for (const entry of entries) {
|
|
12065
|
-
const hashStr =
|
|
12862
|
+
const hashStr = import_picocolors12.default.yellow(entry.hash);
|
|
12066
12863
|
const refsStr = entry.refs ? ` ${colorizeRefs(entry.refs, protectedBranches, currentBranch)}` : "";
|
|
12067
12864
|
const subjectStr = colorizeSubject(entry.subject);
|
|
12068
12865
|
console.log(` ${hashStr}${refsStr} ${subjectStr}`);
|
|
@@ -12070,12 +12867,58 @@ async function renderScopedLog(options) {
|
|
|
12070
12867
|
}
|
|
12071
12868
|
return true;
|
|
12072
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
|
+
}
|
|
12073
12916
|
function printEmptyState(mode) {
|
|
12074
12917
|
console.log();
|
|
12075
12918
|
if (mode === "local") {
|
|
12076
|
-
console.log(
|
|
12919
|
+
console.log(import_picocolors12.default.dim(" No local unpushed commits — you're up to date with remote!"));
|
|
12077
12920
|
} else {
|
|
12078
|
-
console.log(
|
|
12921
|
+
console.log(import_picocolors12.default.dim(" No remote-only commits — your local branch is up to date!"));
|
|
12079
12922
|
}
|
|
12080
12923
|
console.log();
|
|
12081
12924
|
}
|
|
@@ -12084,7 +12927,7 @@ async function renderFullLog(options) {
|
|
|
12084
12927
|
if (showGraph) {
|
|
12085
12928
|
const lines = await getLogGraph({ count, all, branch: targetBranch });
|
|
12086
12929
|
if (lines.length === 0) {
|
|
12087
|
-
console.log(
|
|
12930
|
+
console.log(import_picocolors12.default.dim(" No commits found."));
|
|
12088
12931
|
console.log();
|
|
12089
12932
|
return false;
|
|
12090
12933
|
}
|
|
@@ -12095,13 +12938,13 @@ async function renderFullLog(options) {
|
|
|
12095
12938
|
} else {
|
|
12096
12939
|
const entries = await getLogEntries({ count, all, branch: targetBranch });
|
|
12097
12940
|
if (entries.length === 0) {
|
|
12098
|
-
console.log(
|
|
12941
|
+
console.log(import_picocolors12.default.dim(" No commits found."));
|
|
12099
12942
|
console.log();
|
|
12100
12943
|
return false;
|
|
12101
12944
|
}
|
|
12102
12945
|
console.log();
|
|
12103
12946
|
for (const entry of entries) {
|
|
12104
|
-
const hashStr =
|
|
12947
|
+
const hashStr = import_picocolors12.default.yellow(entry.hash);
|
|
12105
12948
|
const refsStr = entry.refs ? ` ${colorizeRefs(entry.refs, protectedBranches, currentBranch)}` : "";
|
|
12106
12949
|
const subjectStr = colorizeSubject(entry.subject);
|
|
12107
12950
|
console.log(` ${hashStr}${refsStr} ${subjectStr}`);
|
|
@@ -12109,46 +12952,37 @@ async function renderFullLog(options) {
|
|
|
12109
12952
|
}
|
|
12110
12953
|
return true;
|
|
12111
12954
|
}
|
|
12112
|
-
function printFooter(mode, count, targetBranch) {
|
|
12955
|
+
function printFooter(mode, count, overviewRemoteCount, targetBranch) {
|
|
12113
12956
|
console.log();
|
|
12114
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;
|
|
12115
12961
|
case "local":
|
|
12116
|
-
console.log(
|
|
12962
|
+
console.log(import_picocolors12.default.dim(` Showing up to ${count} unpushed commits`));
|
|
12117
12963
|
break;
|
|
12118
12964
|
case "remote":
|
|
12119
|
-
console.log(
|
|
12965
|
+
console.log(import_picocolors12.default.dim(` Showing ${count} most recent commits from the remote branch`));
|
|
12120
12966
|
break;
|
|
12121
12967
|
case "full":
|
|
12122
|
-
console.log(
|
|
12968
|
+
console.log(import_picocolors12.default.dim(` Showing ${count} most recent commits${targetBranch ? ` (${targetBranch})` : ""}`));
|
|
12123
12969
|
break;
|
|
12124
12970
|
case "all":
|
|
12125
|
-
console.log(
|
|
12971
|
+
console.log(import_picocolors12.default.dim(` Showing ${count} most recent commits (all branches)`));
|
|
12126
12972
|
break;
|
|
12127
12973
|
}
|
|
12128
12974
|
}
|
|
12129
|
-
function printGuidance() {
|
|
12130
|
-
console.log();
|
|
12131
|
-
console.log(import_picocolors11.default.dim(" ─── quick guide ───"));
|
|
12132
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log")} local unpushed commits (default)`));
|
|
12133
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log --remote")} commits on remote not yet pulled`));
|
|
12134
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log --full")} full history for the current branch`));
|
|
12135
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log --all")} commits across all branches`));
|
|
12136
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log -n 50")} change the commit limit (default: 20)`));
|
|
12137
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log -b dev")} view log for a specific branch`));
|
|
12138
|
-
console.log(import_picocolors11.default.dim(` ${import_picocolors11.default.bold("contrib log --no-graph")} flat list without graph lines`));
|
|
12139
|
-
console.log();
|
|
12140
|
-
}
|
|
12141
12975
|
function colorizeGraphLine(line, protectedBranches, currentBranch) {
|
|
12142
12976
|
const match = line.match(/^([|/\\*\s_.-]*)([a-f0-9]{7,12})(\s+\(([^)]+)\))?\s*(.*)/);
|
|
12143
12977
|
if (!match) {
|
|
12144
|
-
return
|
|
12978
|
+
return import_picocolors12.default.cyan(line);
|
|
12145
12979
|
}
|
|
12146
12980
|
const [, graphPart = "", hash, , refs, subject = ""] = match;
|
|
12147
12981
|
const parts = [];
|
|
12148
12982
|
if (graphPart) {
|
|
12149
12983
|
parts.push(colorizeGraphChars(graphPart));
|
|
12150
12984
|
}
|
|
12151
|
-
parts.push(
|
|
12985
|
+
parts.push(import_picocolors12.default.yellow(hash));
|
|
12152
12986
|
if (refs) {
|
|
12153
12987
|
parts.push(` (${colorizeRefs(refs, protectedBranches, currentBranch)})`);
|
|
12154
12988
|
}
|
|
@@ -12159,15 +12993,15 @@ function colorizeGraphChars(graphPart) {
|
|
|
12159
12993
|
return graphPart.split("").map((ch) => {
|
|
12160
12994
|
switch (ch) {
|
|
12161
12995
|
case "*":
|
|
12162
|
-
return
|
|
12996
|
+
return import_picocolors12.default.green(ch);
|
|
12163
12997
|
case "|":
|
|
12164
|
-
return
|
|
12998
|
+
return import_picocolors12.default.cyan(ch);
|
|
12165
12999
|
case "/":
|
|
12166
13000
|
case "\\":
|
|
12167
|
-
return
|
|
13001
|
+
return import_picocolors12.default.cyan(ch);
|
|
12168
13002
|
case "-":
|
|
12169
13003
|
case "_":
|
|
12170
|
-
return
|
|
13004
|
+
return import_picocolors12.default.cyan(ch);
|
|
12171
13005
|
default:
|
|
12172
13006
|
return ch;
|
|
12173
13007
|
}
|
|
@@ -12179,50 +13013,50 @@ function colorizeRefs(refs, protectedBranches, currentBranch) {
|
|
|
12179
13013
|
if (trimmed.startsWith("HEAD ->") || trimmed === "HEAD") {
|
|
12180
13014
|
const branchName = trimmed.replace("HEAD -> ", "");
|
|
12181
13015
|
if (trimmed === "HEAD") {
|
|
12182
|
-
return
|
|
13016
|
+
return import_picocolors12.default.bold(import_picocolors12.default.cyan("HEAD"));
|
|
12183
13017
|
}
|
|
12184
|
-
return `${
|
|
13018
|
+
return `${import_picocolors12.default.bold(import_picocolors12.default.cyan("HEAD"))} ${import_picocolors12.default.dim("->")} ${colorizeRefName(branchName, protectedBranches, currentBranch)}`;
|
|
12185
13019
|
}
|
|
12186
13020
|
if (trimmed.startsWith("tag:")) {
|
|
12187
|
-
return
|
|
13021
|
+
return import_picocolors12.default.bold(import_picocolors12.default.magenta(trimmed));
|
|
12188
13022
|
}
|
|
12189
13023
|
return colorizeRefName(trimmed, protectedBranches, currentBranch);
|
|
12190
|
-
}).join(
|
|
13024
|
+
}).join(import_picocolors12.default.dim(", "));
|
|
12191
13025
|
}
|
|
12192
13026
|
function colorizeRefName(name, protectedBranches, currentBranch) {
|
|
12193
13027
|
const isRemote = name.includes("/");
|
|
12194
13028
|
const localName = isRemote ? name.split("/").slice(1).join("/") : name;
|
|
12195
13029
|
if (protectedBranches.includes(localName)) {
|
|
12196
|
-
return isRemote ?
|
|
13030
|
+
return isRemote ? import_picocolors12.default.bold(import_picocolors12.default.red(name)) : import_picocolors12.default.bold(import_picocolors12.default.red(name));
|
|
12197
13031
|
}
|
|
12198
13032
|
if (localName === currentBranch) {
|
|
12199
|
-
return
|
|
13033
|
+
return import_picocolors12.default.bold(import_picocolors12.default.green(name));
|
|
12200
13034
|
}
|
|
12201
13035
|
if (isRemote) {
|
|
12202
|
-
return
|
|
13036
|
+
return import_picocolors12.default.blue(name);
|
|
12203
13037
|
}
|
|
12204
|
-
return
|
|
13038
|
+
return import_picocolors12.default.green(name);
|
|
12205
13039
|
}
|
|
12206
13040
|
function colorizeSubject(subject) {
|
|
12207
13041
|
const emojiMatch = subject.match(/^((?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)+\s*)/u);
|
|
12208
13042
|
if (emojiMatch) {
|
|
12209
13043
|
const emoji = emojiMatch[1];
|
|
12210
13044
|
const rest = subject.slice(emoji.length);
|
|
12211
|
-
return `${emoji}${
|
|
13045
|
+
return `${emoji}${import_picocolors12.default.white(rest)}`;
|
|
12212
13046
|
}
|
|
12213
13047
|
if (subject.startsWith("Merge ")) {
|
|
12214
|
-
return
|
|
13048
|
+
return import_picocolors12.default.dim(subject);
|
|
12215
13049
|
}
|
|
12216
|
-
return
|
|
13050
|
+
return import_picocolors12.default.white(subject);
|
|
12217
13051
|
}
|
|
12218
13052
|
|
|
12219
13053
|
// src/commands/save.ts
|
|
12220
|
-
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
12221
13054
|
import { execFile as execFileCb4 } from "node:child_process";
|
|
13055
|
+
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
12222
13056
|
function gitRun(args) {
|
|
12223
|
-
return new Promise((
|
|
13057
|
+
return new Promise((resolve3) => {
|
|
12224
13058
|
execFileCb4("git", args, (err, stdout2, stderr) => {
|
|
12225
|
-
|
|
13059
|
+
resolve3({
|
|
12226
13060
|
exitCode: err ? err.code === "ENOENT" ? 127 : err.status ?? 1 : 0,
|
|
12227
13061
|
stdout: stdout2 ?? "",
|
|
12228
13062
|
stderr: stderr ?? ""
|
|
@@ -12279,7 +13113,7 @@ var save_default = defineCommand({
|
|
|
12279
13113
|
}
|
|
12280
13114
|
});
|
|
12281
13115
|
async function handleSave(message) {
|
|
12282
|
-
|
|
13116
|
+
projectHeading("save", "\uD83D\uDCBE");
|
|
12283
13117
|
const currentBranch = await getCurrentBranch();
|
|
12284
13118
|
const label = message ?? `work-in-progress on ${currentBranch ?? "unknown"}`;
|
|
12285
13119
|
const stashMsg = `contrib-save: ${label}`;
|
|
@@ -12292,11 +13126,11 @@ async function handleSave(message) {
|
|
|
12292
13126
|
info("No uncommitted changes to save.");
|
|
12293
13127
|
return;
|
|
12294
13128
|
}
|
|
12295
|
-
success(`Saved: ${
|
|
12296
|
-
info(`Use ${
|
|
13129
|
+
success(`Saved: ${import_picocolors13.default.dim(label)}`);
|
|
13130
|
+
info(`Use ${import_picocolors13.default.bold("contrib save --restore")} to bring them back.`, "");
|
|
12297
13131
|
}
|
|
12298
13132
|
async function handleRestore() {
|
|
12299
|
-
|
|
13133
|
+
projectHeading("save --restore", "\uD83D\uDCBE");
|
|
12300
13134
|
const stashes = await getStashList();
|
|
12301
13135
|
if (stashes.length === 0) {
|
|
12302
13136
|
info("No saved changes found.");
|
|
@@ -12309,7 +13143,7 @@ async function handleRestore() {
|
|
|
12309
13143
|
warn("You may have conflicts. Resolve them and run `git stash drop` when done.");
|
|
12310
13144
|
process.exit(1);
|
|
12311
13145
|
}
|
|
12312
|
-
success(`Restored: ${
|
|
13146
|
+
success(`Restored: ${import_picocolors13.default.dim(stashes[0].message)}`);
|
|
12313
13147
|
return;
|
|
12314
13148
|
}
|
|
12315
13149
|
const choices = stashes.map((s2) => `${s2.index} ${s2.message}`);
|
|
@@ -12322,10 +13156,10 @@ async function handleRestore() {
|
|
|
12322
13156
|
process.exit(1);
|
|
12323
13157
|
}
|
|
12324
13158
|
const match = stashes.find((s2) => String(s2.index) === idx);
|
|
12325
|
-
success(`Restored: ${
|
|
13159
|
+
success(`Restored: ${import_picocolors13.default.dim(match?.message ?? "saved changes")}`);
|
|
12326
13160
|
}
|
|
12327
13161
|
async function handleList() {
|
|
12328
|
-
|
|
13162
|
+
projectHeading("save --list", "\uD83D\uDCBE");
|
|
12329
13163
|
const stashes = await getStashList();
|
|
12330
13164
|
if (stashes.length === 0) {
|
|
12331
13165
|
info("No saved changes.");
|
|
@@ -12333,16 +13167,16 @@ async function handleList() {
|
|
|
12333
13167
|
}
|
|
12334
13168
|
console.log();
|
|
12335
13169
|
for (const s2 of stashes) {
|
|
12336
|
-
const idx =
|
|
13170
|
+
const idx = import_picocolors13.default.dim(`[${s2.index}]`);
|
|
12337
13171
|
const msg = s2.message;
|
|
12338
13172
|
console.log(` ${idx} ${msg}`);
|
|
12339
13173
|
}
|
|
12340
13174
|
console.log();
|
|
12341
|
-
info(`Use ${
|
|
12342
|
-
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.`, "");
|
|
12343
13177
|
}
|
|
12344
13178
|
async function handleDrop() {
|
|
12345
|
-
|
|
13179
|
+
projectHeading("save --drop", "\uD83D\uDCBE");
|
|
12346
13180
|
const stashes = await getStashList();
|
|
12347
13181
|
if (stashes.length === 0) {
|
|
12348
13182
|
info("No saved changes to drop.");
|
|
@@ -12357,7 +13191,7 @@ async function handleDrop() {
|
|
|
12357
13191
|
process.exit(1);
|
|
12358
13192
|
}
|
|
12359
13193
|
const match = stashes.find((s2) => String(s2.index) === idx);
|
|
12360
|
-
success(`Dropped: ${
|
|
13194
|
+
success(`Dropped: ${import_picocolors13.default.dim(match?.message ?? "saved changes")}`);
|
|
12361
13195
|
}
|
|
12362
13196
|
async function getStashList() {
|
|
12363
13197
|
const result = await gitRun(["stash", "list"]);
|
|
@@ -12374,38 +13208,23 @@ async function getStashList() {
|
|
|
12374
13208
|
}
|
|
12375
13209
|
|
|
12376
13210
|
// src/commands/setup.ts
|
|
12377
|
-
var
|
|
13211
|
+
var import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
12378
13212
|
async function shouldContinueSetupWithExistingConfig(options) {
|
|
12379
|
-
const {
|
|
12380
|
-
existingConfig,
|
|
12381
|
-
hasConfigFile,
|
|
12382
|
-
confirm,
|
|
12383
|
-
ensureIgnored,
|
|
12384
|
-
onInfo,
|
|
12385
|
-
onWarn,
|
|
12386
|
-
onSuccess,
|
|
12387
|
-
summary
|
|
12388
|
-
} = options;
|
|
13213
|
+
const { existingConfig, hasConfigFile, confirm, onInfo, onWarn, onSuccess, summary } = options;
|
|
12389
13214
|
if (existingConfig) {
|
|
12390
|
-
onInfo("Existing
|
|
13215
|
+
onInfo("Existing repo config detected:");
|
|
12391
13216
|
summary(existingConfig);
|
|
12392
13217
|
const shouldContinue = await confirm("Continue setup and overwrite existing config?");
|
|
12393
13218
|
if (!shouldContinue) {
|
|
12394
|
-
if (ensureIgnored()) {
|
|
12395
|
-
onInfo("Added .contributerc.json to .gitignore to avoid committing personal config.");
|
|
12396
|
-
}
|
|
12397
13219
|
onSuccess("Keeping existing setup.");
|
|
12398
13220
|
return false;
|
|
12399
13221
|
}
|
|
12400
13222
|
return true;
|
|
12401
13223
|
}
|
|
12402
13224
|
if (hasConfigFile) {
|
|
12403
|
-
onWarn("Found
|
|
13225
|
+
onWarn("Found an existing repo config but it appears invalid.");
|
|
12404
13226
|
const shouldContinue = await confirm("Continue setup and overwrite invalid config?");
|
|
12405
13227
|
if (!shouldContinue) {
|
|
12406
|
-
if (ensureIgnored()) {
|
|
12407
|
-
onInfo("Added .contributerc.json to .gitignore to avoid committing personal config.");
|
|
12408
|
-
}
|
|
12409
13228
|
onInfo("Keeping existing file. Run setup again when ready to repair it.");
|
|
12410
13229
|
return false;
|
|
12411
13230
|
}
|
|
@@ -12415,20 +13234,19 @@ async function shouldContinueSetupWithExistingConfig(options) {
|
|
|
12415
13234
|
var setup_default = defineCommand({
|
|
12416
13235
|
meta: {
|
|
12417
13236
|
name: "setup",
|
|
12418
|
-
description: "Initialize contribute-now config for this repo
|
|
13237
|
+
description: "Initialize contribute-now config for this repo using local Git storage"
|
|
12419
13238
|
},
|
|
12420
13239
|
async run() {
|
|
12421
13240
|
if (!await isGitRepo()) {
|
|
12422
13241
|
error("Not inside a git repository. Run this command from within a git repo.");
|
|
12423
13242
|
process.exit(1);
|
|
12424
13243
|
}
|
|
12425
|
-
|
|
13244
|
+
projectHeading("setup", "\uD83D\uDD27");
|
|
12426
13245
|
const existingConfig = readConfig();
|
|
12427
13246
|
const shouldContinue = await shouldContinueSetupWithExistingConfig({
|
|
12428
13247
|
existingConfig,
|
|
12429
13248
|
hasConfigFile: configExists(),
|
|
12430
13249
|
confirm: confirmPrompt,
|
|
12431
|
-
ensureIgnored: ensureGitignored,
|
|
12432
13250
|
onInfo: info,
|
|
12433
13251
|
onWarn: warn,
|
|
12434
13252
|
onSuccess: success,
|
|
@@ -12447,7 +13265,7 @@ var setup_default = defineCommand({
|
|
|
12447
13265
|
workflow = "github-flow";
|
|
12448
13266
|
else if (workflowChoice.startsWith("Git Flow"))
|
|
12449
13267
|
workflow = "git-flow";
|
|
12450
|
-
info(`Workflow: ${
|
|
13268
|
+
info(`Workflow: ${import_picocolors14.default.bold(WORKFLOW_DESCRIPTIONS[workflow])}`);
|
|
12451
13269
|
const conventionChoice = await selectPrompt("Which commit convention should this project use?", [
|
|
12452
13270
|
`${CONVENTION_DESCRIPTIONS["clean-commit"]} (recommended)`,
|
|
12453
13271
|
CONVENTION_DESCRIPTIONS.conventional,
|
|
@@ -12458,6 +13276,8 @@ var setup_default = defineCommand({
|
|
|
12458
13276
|
commitConvention = "conventional";
|
|
12459
13277
|
else if (conventionChoice.includes("No commit"))
|
|
12460
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?");
|
|
12461
13281
|
const remotes = await getRemotes();
|
|
12462
13282
|
if (remotes.length === 0) {
|
|
12463
13283
|
error("No git remotes found. Add a remote first (e.g., git remote add origin <url>).");
|
|
@@ -12511,15 +13331,15 @@ var setup_default = defineCommand({
|
|
|
12511
13331
|
detectedRole = roleChoice;
|
|
12512
13332
|
detectionSource = "user selection";
|
|
12513
13333
|
} else {
|
|
12514
|
-
info(`Detected role: ${
|
|
12515
|
-
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?`);
|
|
12516
13336
|
if (!confirmed) {
|
|
12517
13337
|
const roleChoice = await selectPrompt("Select your role:", ["maintainer", "contributor"]);
|
|
12518
13338
|
detectedRole = roleChoice;
|
|
12519
13339
|
}
|
|
12520
13340
|
}
|
|
12521
13341
|
const defaultConfig = getDefaultConfig();
|
|
12522
|
-
info(
|
|
13342
|
+
info(import_picocolors14.default.dim("Tip: press Enter to keep the default branch name shown in each prompt."));
|
|
12523
13343
|
const mainBranchDefault = defaultConfig.mainBranch;
|
|
12524
13344
|
const mainBranch = await inputPrompt(`Main branch name (default: ${mainBranchDefault} — press Enter to keep)`, mainBranchDefault);
|
|
12525
13345
|
let devBranch;
|
|
@@ -12545,7 +13365,7 @@ var setup_default = defineCommand({
|
|
|
12545
13365
|
error("Setup cannot continue without the upstream remote for contributors.");
|
|
12546
13366
|
process.exit(1);
|
|
12547
13367
|
}
|
|
12548
|
-
success(`Added remote ${
|
|
13368
|
+
success(`Added remote ${import_picocolors14.default.bold(upstreamRemote)} → ${upstreamUrl}`);
|
|
12549
13369
|
} else {
|
|
12550
13370
|
error("An upstream remote URL is required for contributors.");
|
|
12551
13371
|
info("Add it manually: git remote add upstream <url>", "");
|
|
@@ -12561,54 +13381,58 @@ var setup_default = defineCommand({
|
|
|
12561
13381
|
upstream: upstreamRemote,
|
|
12562
13382
|
origin: originRemote,
|
|
12563
13383
|
branchPrefixes: defaultConfig.branchPrefixes,
|
|
12564
|
-
commitConvention
|
|
13384
|
+
commitConvention,
|
|
13385
|
+
aiEnabled: enableAI,
|
|
13386
|
+
showTips
|
|
12565
13387
|
};
|
|
12566
13388
|
writeConfig(config);
|
|
12567
|
-
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.", "");
|
|
12568
13391
|
const syncRemote = config.role === "contributor" ? config.upstream : config.origin;
|
|
12569
|
-
info(`Fetching ${
|
|
13392
|
+
info(`Fetching ${import_picocolors14.default.bold(syncRemote)} to verify branch configuration...`, "");
|
|
12570
13393
|
await fetchRemote(syncRemote);
|
|
12571
13394
|
const mainRef = `${syncRemote}/${config.mainBranch}`;
|
|
12572
13395
|
if (!await refExists(mainRef)) {
|
|
12573
|
-
warn(`Main branch ref ${
|
|
13396
|
+
warn(`Main branch ref ${import_picocolors14.default.bold(mainRef)} not found on remote.`);
|
|
12574
13397
|
warn("Config was saved — verify the branch name and re-run setup if needed.");
|
|
12575
13398
|
}
|
|
12576
13399
|
if (config.devBranch) {
|
|
12577
13400
|
const devRef = `${syncRemote}/${config.devBranch}`;
|
|
12578
13401
|
if (!await refExists(devRef)) {
|
|
12579
|
-
warn(`Dev branch ref ${
|
|
13402
|
+
warn(`Dev branch ref ${import_picocolors14.default.bold(devRef)} not found on remote.`);
|
|
12580
13403
|
warn("Config was saved — verify the branch name and re-run setup if needed.");
|
|
12581
13404
|
}
|
|
12582
13405
|
}
|
|
12583
|
-
if (ensureGitignored()) {
|
|
12584
|
-
info("Added .contributerc.json to .gitignore to avoid committing personal config.");
|
|
12585
|
-
}
|
|
12586
13406
|
console.log();
|
|
12587
|
-
info(`Workflow: ${
|
|
12588
|
-
info(`Convention: ${
|
|
12589
|
-
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)}`);
|
|
12590
13412
|
if (config.devBranch) {
|
|
12591
|
-
info(`Main: ${
|
|
13413
|
+
info(`Main: ${import_picocolors14.default.bold(config.mainBranch)} | Dev: ${import_picocolors14.default.bold(config.devBranch)}`);
|
|
12592
13414
|
} else {
|
|
12593
|
-
info(`Main: ${
|
|
13415
|
+
info(`Main: ${import_picocolors14.default.bold(config.mainBranch)}`);
|
|
12594
13416
|
}
|
|
12595
|
-
info(`Origin: ${
|
|
13417
|
+
info(`Origin: ${import_picocolors14.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors14.default.bold(config.upstream)}` : ""}`);
|
|
12596
13418
|
}
|
|
12597
13419
|
});
|
|
12598
13420
|
function logConfigSummary(config) {
|
|
12599
|
-
info(`Workflow: ${
|
|
12600
|
-
info(`Convention: ${
|
|
12601
|
-
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)}`);
|
|
12602
13426
|
if (config.devBranch) {
|
|
12603
|
-
info(`Main: ${
|
|
13427
|
+
info(`Main: ${import_picocolors14.default.bold(config.mainBranch)} | Dev: ${import_picocolors14.default.bold(config.devBranch)}`);
|
|
12604
13428
|
} else {
|
|
12605
|
-
info(`Main: ${
|
|
13429
|
+
info(`Main: ${import_picocolors14.default.bold(config.mainBranch)}`);
|
|
12606
13430
|
}
|
|
12607
|
-
info(`Origin: ${
|
|
13431
|
+
info(`Origin: ${import_picocolors14.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors14.default.bold(config.upstream)}` : ""}`);
|
|
12608
13432
|
}
|
|
12609
13433
|
|
|
12610
13434
|
// src/commands/start.ts
|
|
12611
|
-
var
|
|
13435
|
+
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
12612
13436
|
var start_default = defineCommand({
|
|
12613
13437
|
meta: {
|
|
12614
13438
|
name: "start",
|
|
@@ -12638,7 +13462,7 @@ var start_default = defineCommand({
|
|
|
12638
13462
|
await assertCleanGitState("starting a new branch");
|
|
12639
13463
|
const config = readConfig();
|
|
12640
13464
|
if (!config) {
|
|
12641
|
-
error("No
|
|
13465
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
12642
13466
|
process.exit(1);
|
|
12643
13467
|
}
|
|
12644
13468
|
if (await hasUncommittedChanges()) {
|
|
@@ -12648,57 +13472,28 @@ var start_default = defineCommand({
|
|
|
12648
13472
|
const { branchPrefixes } = config;
|
|
12649
13473
|
const baseBranch = getBaseBranch(config);
|
|
12650
13474
|
const syncSource = getSyncSource(config);
|
|
12651
|
-
let branchName = args.name;
|
|
12652
|
-
|
|
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
|
+
});
|
|
12653
13483
|
if (!branchName) {
|
|
12654
|
-
|
|
12655
|
-
|
|
12656
|
-
error("A branch name or description is required.");
|
|
12657
|
-
process.exit(1);
|
|
12658
|
-
}
|
|
12659
|
-
branchName = branchName.trim();
|
|
12660
|
-
}
|
|
12661
|
-
const useAI = !args["no-ai"] && looksLikeNaturalLanguage(branchName);
|
|
12662
|
-
if (useAI) {
|
|
12663
|
-
const spinner = createSpinner("Generating branch name suggestion...");
|
|
12664
|
-
const suggested = await suggestBranchName(branchName, args.model);
|
|
12665
|
-
if (suggested) {
|
|
12666
|
-
spinner.success("Branch name suggestion ready.");
|
|
12667
|
-
console.log(`
|
|
12668
|
-
${import_picocolors14.default.dim("AI suggestion:")} ${import_picocolors14.default.bold(import_picocolors14.default.cyan(suggested))}`);
|
|
12669
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors14.default.bold(suggested)} as your branch name?`);
|
|
12670
|
-
if (accepted) {
|
|
12671
|
-
branchName = suggested;
|
|
12672
|
-
} else {
|
|
12673
|
-
branchName = await inputPrompt("Enter branch name", branchName);
|
|
12674
|
-
}
|
|
12675
|
-
} else {
|
|
12676
|
-
spinner.fail("AI did not return a branch name suggestion.");
|
|
12677
|
-
}
|
|
12678
|
-
}
|
|
12679
|
-
if (!hasPrefix(branchName, branchPrefixes)) {
|
|
12680
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors14.default.bold(branchName)}:`, branchPrefixes);
|
|
12681
|
-
branchName = formatBranchName(prefix, branchName);
|
|
12682
|
-
}
|
|
12683
|
-
if (!isValidBranchName(branchName)) {
|
|
12684
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
12685
|
-
process.exit(1);
|
|
12686
|
-
}
|
|
12687
|
-
info(`Creating branch: ${import_picocolors14.default.bold(branchName)}`);
|
|
12688
|
-
if (await branchExists(branchName)) {
|
|
12689
|
-
error(`Branch ${import_picocolors14.default.bold(branchName)} already exists.`);
|
|
12690
|
-
info(` Use ${import_picocolors14.default.bold(`git checkout ${branchName}`)} to switch to it, or choose a different name.`, "");
|
|
12691
|
-
process.exit(1);
|
|
13484
|
+
warn("Start cancelled.");
|
|
13485
|
+
process.exit(0);
|
|
12692
13486
|
}
|
|
13487
|
+
info(`Creating branch: ${import_picocolors15.default.bold(branchName)}`);
|
|
12693
13488
|
await fetchRemote(syncSource.remote);
|
|
12694
13489
|
if (!await refExists(syncSource.ref)) {
|
|
12695
|
-
warn(`Remote ref ${
|
|
13490
|
+
warn(`Remote ref ${import_picocolors15.default.bold(syncSource.ref)} not found. Creating branch from local ${import_picocolors15.default.bold(baseBranch)}.`);
|
|
12696
13491
|
}
|
|
12697
13492
|
const currentBranch = await getCurrentBranch();
|
|
12698
13493
|
if (currentBranch === baseBranch && await refExists(syncSource.ref)) {
|
|
12699
13494
|
const ahead = await countCommitsAhead(baseBranch, syncSource.ref);
|
|
12700
13495
|
if (ahead > 0) {
|
|
12701
|
-
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)}.`);
|
|
12702
13497
|
info(" Syncing will discard those commits. Consider backing them up first (e.g. create a branch).");
|
|
12703
13498
|
const proceed = await confirmPrompt("Discard local commits and sync to remote?");
|
|
12704
13499
|
if (!proceed) {
|
|
@@ -12715,10 +13510,10 @@ var start_default = defineCommand({
|
|
|
12715
13510
|
error(`Failed to create branch: ${result2.stderr}`);
|
|
12716
13511
|
process.exit(1);
|
|
12717
13512
|
}
|
|
12718
|
-
success(`Created ${
|
|
13513
|
+
success(`Created ${import_picocolors15.default.bold(branchName)} from ${import_picocolors15.default.bold(syncSource.ref)}`);
|
|
12719
13514
|
return;
|
|
12720
13515
|
}
|
|
12721
|
-
error(`Failed to update ${
|
|
13516
|
+
error(`Failed to update ${import_picocolors15.default.bold(baseBranch)}: ${updateResult.stderr}`);
|
|
12722
13517
|
info("Make sure your base branch exists locally or the remote ref is available.", "");
|
|
12723
13518
|
process.exit(1);
|
|
12724
13519
|
}
|
|
@@ -12727,12 +13522,12 @@ var start_default = defineCommand({
|
|
|
12727
13522
|
error(`Failed to create branch: ${result.stderr}`);
|
|
12728
13523
|
process.exit(1);
|
|
12729
13524
|
}
|
|
12730
|
-
success(`Created ${
|
|
13525
|
+
success(`Created ${import_picocolors15.default.bold(branchName)} from latest ${import_picocolors15.default.bold(baseBranch)}`);
|
|
12731
13526
|
}
|
|
12732
13527
|
});
|
|
12733
13528
|
|
|
12734
13529
|
// src/commands/status.ts
|
|
12735
|
-
var
|
|
13530
|
+
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
12736
13531
|
var status_default = defineCommand({
|
|
12737
13532
|
meta: {
|
|
12738
13533
|
name: "status",
|
|
@@ -12745,12 +13540,12 @@ var status_default = defineCommand({
|
|
|
12745
13540
|
}
|
|
12746
13541
|
const config = readConfig();
|
|
12747
13542
|
if (!config) {
|
|
12748
|
-
error("No
|
|
13543
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
12749
13544
|
process.exit(1);
|
|
12750
13545
|
}
|
|
12751
|
-
|
|
12752
|
-
console.log(` ${
|
|
12753
|
-
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)}`);
|
|
12754
13549
|
console.log();
|
|
12755
13550
|
await fetchAll();
|
|
12756
13551
|
const currentBranch = await getCurrentBranch();
|
|
@@ -12759,7 +13554,7 @@ var status_default = defineCommand({
|
|
|
12759
13554
|
const isContributor = config.role === "contributor";
|
|
12760
13555
|
const [dirty, fileStatus] = await Promise.all([hasUncommittedChanges(), getFileStatus()]);
|
|
12761
13556
|
if (dirty) {
|
|
12762
|
-
console.log(` ${
|
|
13557
|
+
console.log(` ${import_picocolors16.default.yellow("⚠")} ${import_picocolors16.default.yellow("Uncommitted changes in working tree")}`);
|
|
12763
13558
|
console.log();
|
|
12764
13559
|
}
|
|
12765
13560
|
const mainRemote = `${origin}/${mainBranch}`;
|
|
@@ -12778,28 +13573,32 @@ var status_default = defineCommand({
|
|
|
12778
13573
|
if (isFeatureBranch) {
|
|
12779
13574
|
const branchDiv = await getDivergence(currentBranch, baseBranch);
|
|
12780
13575
|
const branchLine = formatStatus(currentBranch, baseBranch, branchDiv.ahead, branchDiv.behind);
|
|
12781
|
-
console.log(branchLine +
|
|
13576
|
+
console.log(branchLine + import_picocolors16.default.dim(` (current ${import_picocolors16.default.green("*")})`));
|
|
12782
13577
|
branchStatus = await detectBranchStatus(currentBranch, baseBranch);
|
|
12783
13578
|
if (branchStatus.merged) {
|
|
12784
|
-
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")}`);
|
|
12785
13580
|
}
|
|
12786
13581
|
if (branchStatus.stale) {
|
|
12787
|
-
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`)}`);
|
|
12788
13583
|
}
|
|
12789
13584
|
} else if (currentBranch) {
|
|
12790
|
-
console.log(
|
|
13585
|
+
console.log(import_picocolors16.default.dim(` (on ${import_picocolors16.default.bold(currentBranch)} branch)`));
|
|
12791
13586
|
}
|
|
12792
13587
|
let branchesAligned = true;
|
|
12793
13588
|
{
|
|
12794
13589
|
const alignRefs = [];
|
|
12795
13590
|
const devRemote = isContributor ? upstream : origin;
|
|
13591
|
+
const devBranch = hasDevBranch(workflow) ? config.devBranch : undefined;
|
|
12796
13592
|
const hashResults = await Promise.all([
|
|
12797
13593
|
getCommitHash(mainBranch).then((h2) => ({ name: mainBranch, hash: h2 })),
|
|
12798
|
-
getCommitHash(`${origin}/${mainBranch}`).then((h2) => ({
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
|
|
12802
|
-
|
|
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}`,
|
|
12803
13602
|
hash: h2
|
|
12804
13603
|
}))
|
|
12805
13604
|
] : []
|
|
@@ -12813,24 +13612,27 @@ var status_default = defineCommand({
|
|
|
12813
13612
|
for (const { name, hash } of alignRefs) {
|
|
12814
13613
|
if (!groups.has(hash))
|
|
12815
13614
|
groups.set(hash, []);
|
|
12816
|
-
groups.get(hash)
|
|
13615
|
+
const group = groups.get(hash);
|
|
13616
|
+
if (group) {
|
|
13617
|
+
group.push(name);
|
|
13618
|
+
}
|
|
12817
13619
|
}
|
|
12818
13620
|
branchesAligned = groups.size === 1;
|
|
12819
13621
|
console.log();
|
|
12820
|
-
console.log(` ${
|
|
13622
|
+
console.log(` ${import_picocolors16.default.bold("\uD83D\uDD17 Branch Alignment")}`);
|
|
12821
13623
|
for (const [hash, names] of groups) {
|
|
12822
13624
|
const short = hash.slice(0, 7);
|
|
12823
|
-
const nameStr = names.map((n2) =>
|
|
12824
|
-
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}`);
|
|
12825
13627
|
const subject = await getCommitSubject(hash);
|
|
12826
13628
|
if (subject) {
|
|
12827
|
-
console.log(` ${
|
|
13629
|
+
console.log(` ${import_picocolors16.default.dim(subject)}`);
|
|
12828
13630
|
}
|
|
12829
13631
|
}
|
|
12830
13632
|
if (branchesAligned) {
|
|
12831
|
-
console.log(` ${
|
|
13633
|
+
console.log(` ${import_picocolors16.default.green("✓")} ${import_picocolors16.default.green("All branches aligned")} ${import_picocolors16.default.dim("— ready to start")}`);
|
|
12832
13634
|
} else {
|
|
12833
|
-
console.log(` ${
|
|
13635
|
+
console.log(` ${import_picocolors16.default.yellow("⚠")} ${import_picocolors16.default.yellow("Branches are not fully aligned")}`);
|
|
12834
13636
|
}
|
|
12835
13637
|
}
|
|
12836
13638
|
}
|
|
@@ -12838,74 +13640,50 @@ var status_default = defineCommand({
|
|
|
12838
13640
|
if (hasFiles) {
|
|
12839
13641
|
console.log();
|
|
12840
13642
|
if (fileStatus.staged.length > 0) {
|
|
12841
|
-
console.log(` ${
|
|
13643
|
+
console.log(` ${import_picocolors16.default.green("Staged for commit:")}`);
|
|
12842
13644
|
for (const { file, status } of fileStatus.staged) {
|
|
12843
|
-
console.log(` ${
|
|
13645
|
+
console.log(` ${import_picocolors16.default.green("+")} ${import_picocolors16.default.dim(`${status}:`)} ${file}`);
|
|
12844
13646
|
}
|
|
12845
13647
|
}
|
|
12846
13648
|
if (fileStatus.modified.length > 0) {
|
|
12847
|
-
console.log(` ${
|
|
13649
|
+
console.log(` ${import_picocolors16.default.yellow("Unstaged changes:")}`);
|
|
12848
13650
|
for (const { file, status } of fileStatus.modified) {
|
|
12849
|
-
console.log(` ${
|
|
13651
|
+
console.log(` ${import_picocolors16.default.yellow("~")} ${import_picocolors16.default.dim(`${status}:`)} ${file}`);
|
|
12850
13652
|
}
|
|
12851
13653
|
}
|
|
12852
13654
|
if (fileStatus.untracked.length > 0) {
|
|
12853
|
-
console.log(` ${
|
|
13655
|
+
console.log(` ${import_picocolors16.default.red("Untracked files:")}`);
|
|
12854
13656
|
for (const file of fileStatus.untracked) {
|
|
12855
|
-
console.log(` ${
|
|
13657
|
+
console.log(` ${import_picocolors16.default.red("?")} ${file}`);
|
|
12856
13658
|
}
|
|
12857
13659
|
}
|
|
12858
13660
|
} else if (!dirty) {
|
|
12859
|
-
console.log(` ${
|
|
12860
|
-
}
|
|
12861
|
-
const tips = [];
|
|
12862
|
-
if (!branchesAligned) {
|
|
12863
|
-
tips.push(`Run ${import_picocolors15.default.bold("contrib sync")} to align your local branches with the remote`);
|
|
12864
|
-
}
|
|
12865
|
-
if (fileStatus.staged.length > 0) {
|
|
12866
|
-
tips.push(`Run ${import_picocolors15.default.bold("contrib commit")} to commit staged changes`);
|
|
12867
|
-
}
|
|
12868
|
-
if (fileStatus.modified.length > 0 || fileStatus.untracked.length > 0) {
|
|
12869
|
-
tips.push(`Run ${import_picocolors15.default.bold("contrib commit")} to stage and commit changes`);
|
|
12870
|
-
}
|
|
12871
|
-
if (isFeatureBranch && branchStatus) {
|
|
12872
|
-
if (branchStatus.merged) {
|
|
12873
|
-
tips.push(`Run ${import_picocolors15.default.bold("contrib clean")} to delete this merged branch`);
|
|
12874
|
-
} else if (branchStatus.stale) {
|
|
12875
|
-
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`);
|
|
12876
|
-
} else if (fileStatus.staged.length === 0 && fileStatus.modified.length === 0 && fileStatus.untracked.length === 0) {
|
|
12877
|
-
const branchDiv = await getDivergence(currentBranch, `${origin}/${currentBranch}`);
|
|
12878
|
-
if (branchDiv.ahead > 0) {
|
|
12879
|
-
tips.push(`Run ${import_picocolors15.default.bold("contrib submit")} to push and create/update your PR`);
|
|
12880
|
-
}
|
|
12881
|
-
}
|
|
12882
|
-
}
|
|
12883
|
-
if (tips.length > 0) {
|
|
12884
|
-
console.log();
|
|
12885
|
-
console.log(` ${import_picocolors15.default.dim("\uD83D\uDCA1 Tip:")}`);
|
|
12886
|
-
for (const tip of tips) {
|
|
12887
|
-
console.log(` ${import_picocolors15.default.dim(tip)}`);
|
|
12888
|
-
}
|
|
13661
|
+
console.log(` ${import_picocolors16.default.green("✓")} ${import_picocolors16.default.dim("Working tree clean")}`);
|
|
12889
13662
|
}
|
|
12890
13663
|
console.log();
|
|
12891
13664
|
}
|
|
12892
13665
|
});
|
|
12893
13666
|
function formatStatus(branch, base, ahead, behind) {
|
|
12894
|
-
const label =
|
|
13667
|
+
const label = import_picocolors16.default.bold(branch.padEnd(20));
|
|
12895
13668
|
if (ahead === 0 && behind === 0) {
|
|
12896
|
-
return ` ${
|
|
13669
|
+
return ` ${import_picocolors16.default.green("✓")} ${label} ${import_picocolors16.default.dim(`in sync with ${base}`)}`;
|
|
12897
13670
|
}
|
|
12898
13671
|
if (ahead > 0 && behind === 0) {
|
|
12899
|
-
return ` ${
|
|
13672
|
+
return ` ${import_picocolors16.default.yellow("↑")} ${label} ${import_picocolors16.default.yellow(`${ahead} commit${ahead !== 1 ? "s" : ""} ahead of ${base}`)}`;
|
|
12900
13673
|
}
|
|
12901
13674
|
if (behind > 0 && ahead === 0) {
|
|
12902
|
-
return ` ${
|
|
13675
|
+
return ` ${import_picocolors16.default.red("↓")} ${label} ${import_picocolors16.default.red(`${behind} commit${behind !== 1 ? "s" : ""} behind ${base}`)}`;
|
|
12903
13676
|
}
|
|
12904
|
-
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)}`;
|
|
12905
13678
|
}
|
|
12906
13679
|
var STALE_THRESHOLD_DAYS = 14;
|
|
12907
13680
|
async function detectBranchStatus(branch, baseBranch) {
|
|
12908
|
-
const result = {
|
|
13681
|
+
const result = {
|
|
13682
|
+
merged: false,
|
|
13683
|
+
mergedReason: null,
|
|
13684
|
+
stale: false,
|
|
13685
|
+
staleDaysAgo: null
|
|
13686
|
+
};
|
|
12909
13687
|
const div = await getDivergence(branch, baseBranch);
|
|
12910
13688
|
const hasWork = div.ahead > 0;
|
|
12911
13689
|
if (hasWork) {
|
|
@@ -12947,34 +13725,47 @@ async function detectBranchStatus(branch, baseBranch) {
|
|
|
12947
13725
|
}
|
|
12948
13726
|
|
|
12949
13727
|
// src/commands/submit.ts
|
|
12950
|
-
var
|
|
13728
|
+
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
12951
13729
|
async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
12952
|
-
info(`Checking out ${
|
|
13730
|
+
info(`Checking out ${import_picocolors17.default.bold(baseBranch)}...`);
|
|
12953
13731
|
const coResult = await checkoutBranch(baseBranch);
|
|
12954
13732
|
if (coResult.exitCode !== 0) {
|
|
12955
13733
|
error(`Failed to checkout ${baseBranch}: ${coResult.stderr}`);
|
|
12956
13734
|
process.exit(1);
|
|
12957
13735
|
}
|
|
12958
|
-
info(`Squash merging ${
|
|
13736
|
+
info(`Squash merging ${import_picocolors17.default.bold(featureBranch)} into ${import_picocolors17.default.bold(baseBranch)}...`);
|
|
12959
13737
|
const mergeResult = await mergeSquash(featureBranch);
|
|
12960
13738
|
if (mergeResult.exitCode !== 0) {
|
|
12961
13739
|
error(`Squash merge failed: ${mergeResult.stderr}`);
|
|
12962
13740
|
process.exit(1);
|
|
12963
13741
|
}
|
|
12964
13742
|
let message = options?.defaultMsg;
|
|
12965
|
-
if (!message) {
|
|
12966
|
-
const copilotError = await
|
|
13743
|
+
if (!message && options?.useAI !== false) {
|
|
13744
|
+
const copilotError = await checkCopilotAvailable2();
|
|
12967
13745
|
if (!copilotError) {
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
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
|
+
}
|
|
12977
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;
|
|
12978
13769
|
}
|
|
12979
13770
|
} else {
|
|
12980
13771
|
warn(`AI unavailable: ${copilotError}`);
|
|
@@ -12983,28 +13774,28 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
12983
13774
|
let finalMsg = null;
|
|
12984
13775
|
if (message) {
|
|
12985
13776
|
while (!finalMsg) {
|
|
12986
|
-
const
|
|
12987
|
-
|
|
12988
|
-
|
|
12989
|
-
|
|
12990
|
-
|
|
12991
|
-
]);
|
|
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);
|
|
12992
13782
|
if (action === "Accept this message") {
|
|
12993
13783
|
finalMsg = message;
|
|
12994
13784
|
} else if (action === "Edit this message") {
|
|
12995
13785
|
finalMsg = await inputPrompt("Edit commit message", message);
|
|
12996
13786
|
} else if (action === "Regenerate") {
|
|
12997
|
-
const spinner = createSpinner("Regenerating commit message..."
|
|
13787
|
+
const spinner = createSpinner("Regenerating commit message...", {
|
|
13788
|
+
tips: LOADING_TIPS
|
|
13789
|
+
});
|
|
12998
13790
|
const [stagedDiff, stagedFiles] = await Promise.all([getStagedDiff(), getStagedFiles()]);
|
|
12999
13791
|
const regen = await generateCommitMessage(stagedDiff, stagedFiles, options?.model, options?.convention ?? "clean-commit", "squash-merge");
|
|
13000
13792
|
if (regen) {
|
|
13001
13793
|
message = regen;
|
|
13002
13794
|
spinner.success("Commit message regenerated.");
|
|
13003
13795
|
console.log(`
|
|
13004
|
-
${
|
|
13796
|
+
${import_picocolors17.default.dim("AI suggestion:")} ${import_picocolors17.default.bold(import_picocolors17.default.cyan(regen))}`);
|
|
13005
13797
|
} else {
|
|
13006
13798
|
spinner.fail("Regeneration failed.");
|
|
13007
|
-
finalMsg = await inputPrompt("Enter commit message");
|
|
13008
13799
|
}
|
|
13009
13800
|
} else {
|
|
13010
13801
|
finalMsg = await inputPrompt("Enter commit message");
|
|
@@ -13018,13 +13809,13 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
13018
13809
|
error(`Commit failed: ${commitResult.stderr}`);
|
|
13019
13810
|
process.exit(1);
|
|
13020
13811
|
}
|
|
13021
|
-
info(`Pushing ${
|
|
13812
|
+
info(`Pushing ${import_picocolors17.default.bold(baseBranch)} to ${origin}...`);
|
|
13022
13813
|
const pushResult = await pushBranch(origin, baseBranch);
|
|
13023
13814
|
if (pushResult.exitCode !== 0) {
|
|
13024
13815
|
error(`Failed to push ${baseBranch}: ${pushResult.stderr}`);
|
|
13025
13816
|
process.exit(1);
|
|
13026
13817
|
}
|
|
13027
|
-
info(`Deleting local branch ${
|
|
13818
|
+
info(`Deleting local branch ${import_picocolors17.default.bold(featureBranch)}...`);
|
|
13028
13819
|
const delLocal = await forceDeleteBranch(featureBranch);
|
|
13029
13820
|
if (delLocal.exitCode !== 0) {
|
|
13030
13821
|
warn(`Could not delete local branch: ${delLocal.stderr.trim()}`);
|
|
@@ -13032,14 +13823,14 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
13032
13823
|
const remoteBranchRef = `${origin}/${featureBranch}`;
|
|
13033
13824
|
const remoteExists = await branchExists(remoteBranchRef);
|
|
13034
13825
|
if (remoteExists) {
|
|
13035
|
-
info(`Deleting remote branch ${
|
|
13826
|
+
info(`Deleting remote branch ${import_picocolors17.default.bold(featureBranch)}...`);
|
|
13036
13827
|
const delRemote = await deleteRemoteBranch(origin, featureBranch);
|
|
13037
13828
|
if (delRemote.exitCode !== 0) {
|
|
13038
13829
|
warn(`Could not delete remote branch: ${delRemote.stderr.trim()}`);
|
|
13039
13830
|
}
|
|
13040
13831
|
}
|
|
13041
|
-
success(`Squash merged ${
|
|
13042
|
-
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.`, "");
|
|
13043
13834
|
}
|
|
13044
13835
|
var submit_default = defineCommand({
|
|
13045
13836
|
meta: {
|
|
@@ -13052,6 +13843,18 @@ var submit_default = defineCommand({
|
|
|
13052
13843
|
description: "Create PR as draft",
|
|
13053
13844
|
default: false
|
|
13054
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
|
+
},
|
|
13055
13858
|
"no-ai": {
|
|
13056
13859
|
type: "boolean",
|
|
13057
13860
|
description: "Skip AI PR description generation",
|
|
@@ -13070,10 +13873,11 @@ var submit_default = defineCommand({
|
|
|
13070
13873
|
await assertCleanGitState("submitting");
|
|
13071
13874
|
const config = readConfig();
|
|
13072
13875
|
if (!config) {
|
|
13073
|
-
error("No
|
|
13876
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
13074
13877
|
process.exit(1);
|
|
13075
13878
|
}
|
|
13076
13879
|
const { origin } = config;
|
|
13880
|
+
const aiEnabled = isAIEnabled(config, args["no-ai"]);
|
|
13077
13881
|
const baseBranch = getBaseBranch(config);
|
|
13078
13882
|
const protectedBranches = getProtectedBranches(config);
|
|
13079
13883
|
const currentBranch = await getCurrentBranch();
|
|
@@ -13082,8 +13886,8 @@ var submit_default = defineCommand({
|
|
|
13082
13886
|
process.exit(1);
|
|
13083
13887
|
}
|
|
13084
13888
|
if (protectedBranches.includes(currentBranch)) {
|
|
13085
|
-
|
|
13086
|
-
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.`);
|
|
13087
13891
|
await fetchAll();
|
|
13088
13892
|
const remoteRef = `${origin}/${currentBranch}`;
|
|
13089
13893
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
@@ -13092,11 +13896,11 @@ var submit_default = defineCommand({
|
|
|
13092
13896
|
const hasAnything = hasCommits || dirty;
|
|
13093
13897
|
if (!hasAnything) {
|
|
13094
13898
|
error("No local changes or commits to move. Switch to a feature branch first.");
|
|
13095
|
-
info(` Run ${
|
|
13899
|
+
info(` Run ${import_picocolors17.default.bold("contrib start")} to create a new feature branch.`, "");
|
|
13096
13900
|
process.exit(1);
|
|
13097
13901
|
}
|
|
13098
13902
|
if (hasCommits) {
|
|
13099
|
-
info(`Found ${
|
|
13903
|
+
info(`Found ${import_picocolors17.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors17.default.bold(currentBranch)}.`);
|
|
13100
13904
|
}
|
|
13101
13905
|
if (dirty) {
|
|
13102
13906
|
info("You also have uncommitted changes in the working tree.");
|
|
@@ -13112,58 +13916,35 @@ var submit_default = defineCommand({
|
|
|
13112
13916
|
info("No changes made. You are still on your current branch.");
|
|
13113
13917
|
return;
|
|
13114
13918
|
}
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
if (suggested) {
|
|
13124
|
-
spinner.success("Branch name suggestion ready.");
|
|
13125
|
-
console.log(`
|
|
13126
|
-
${import_picocolors16.default.dim("AI suggestion:")} ${import_picocolors16.default.bold(import_picocolors16.default.cyan(suggested))}`);
|
|
13127
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors16.default.bold(suggested)} as your branch name?`);
|
|
13128
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
13129
|
-
} else {
|
|
13130
|
-
spinner.fail("AI did not return a suggestion.");
|
|
13131
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
13132
|
-
}
|
|
13133
|
-
}
|
|
13134
|
-
}
|
|
13135
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
13136
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors16.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
13137
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
13138
|
-
}
|
|
13139
|
-
if (!isValidBranchName(newBranchName)) {
|
|
13140
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
13141
|
-
process.exit(1);
|
|
13142
|
-
}
|
|
13143
|
-
if (await branchExists(newBranchName)) {
|
|
13144
|
-
error(`Branch ${import_picocolors16.default.bold(newBranchName)} already exists. Choose a different name.`);
|
|
13145
|
-
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;
|
|
13146
13927
|
}
|
|
13147
13928
|
const branchResult = await createBranch(newBranchName);
|
|
13148
13929
|
if (branchResult.exitCode !== 0) {
|
|
13149
13930
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
13150
13931
|
process.exit(1);
|
|
13151
13932
|
}
|
|
13152
|
-
success(`Created ${
|
|
13933
|
+
success(`Created ${import_picocolors17.default.bold(newBranchName)} with your changes.`);
|
|
13153
13934
|
await updateLocalBranch(currentBranch, remoteRef);
|
|
13154
|
-
info(`Reset ${
|
|
13935
|
+
info(`Reset ${import_picocolors17.default.bold(currentBranch)} back to ${import_picocolors17.default.bold(remoteRef)} — no damage done.`, "");
|
|
13155
13936
|
console.log();
|
|
13156
|
-
success(`You're now on ${
|
|
13157
|
-
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.`, "");
|
|
13158
13939
|
return;
|
|
13159
13940
|
}
|
|
13160
|
-
|
|
13941
|
+
projectHeading("submit", "\uD83D\uDE80");
|
|
13161
13942
|
const ghInstalled = await checkGhInstalled();
|
|
13162
13943
|
const ghAuthed = ghInstalled && await checkGhAuth();
|
|
13163
13944
|
if (ghInstalled && ghAuthed) {
|
|
13164
13945
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
13165
13946
|
if (mergedPR) {
|
|
13166
|
-
warn(`PR #${mergedPR.number} (${
|
|
13947
|
+
warn(`PR #${mergedPR.number} (${import_picocolors17.default.bold(mergedPR.title)}) was already merged.`);
|
|
13167
13948
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
13168
13949
|
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
13169
13950
|
if (hasWork) {
|
|
@@ -13171,7 +13952,7 @@ var submit_default = defineCommand({
|
|
|
13171
13952
|
warn("You have uncommitted changes in your working tree.");
|
|
13172
13953
|
}
|
|
13173
13954
|
if (localWork.unpushedCommits > 0) {
|
|
13174
|
-
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.`);
|
|
13175
13956
|
}
|
|
13176
13957
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
13177
13958
|
const DISCARD = "Discard all changes and clean up";
|
|
@@ -13182,46 +13963,26 @@ var submit_default = defineCommand({
|
|
|
13182
13963
|
return;
|
|
13183
13964
|
}
|
|
13184
13965
|
if (action === SAVE_NEW_BRANCH) {
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
console.log(`
|
|
13194
|
-
${import_picocolors16.default.dim("AI suggestion:")} ${import_picocolors16.default.bold(import_picocolors16.default.cyan(suggested))}`);
|
|
13195
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors16.default.bold(suggested)} as your branch name?`);
|
|
13196
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
13197
|
-
} else {
|
|
13198
|
-
spinner.fail("AI did not return a suggestion.");
|
|
13199
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
13200
|
-
}
|
|
13201
|
-
}
|
|
13202
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
13203
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors16.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
13204
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
13205
|
-
}
|
|
13206
|
-
if (!isValidBranchName(newBranchName)) {
|
|
13207
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
13208
|
-
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;
|
|
13209
13974
|
}
|
|
13210
13975
|
const staleUpstream = await getUpstreamRef();
|
|
13211
13976
|
const staleUpstreamHash = staleUpstream ? await getCommitHash(staleUpstream) : null;
|
|
13212
|
-
if (await branchExists(newBranchName)) {
|
|
13213
|
-
error(`Branch ${import_picocolors16.default.bold(newBranchName)} already exists. Choose a different name.`);
|
|
13214
|
-
process.exit(1);
|
|
13215
|
-
}
|
|
13216
13977
|
const renameResult = await renameBranch(currentBranch, newBranchName);
|
|
13217
13978
|
if (renameResult.exitCode !== 0) {
|
|
13218
13979
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
13219
13980
|
process.exit(1);
|
|
13220
13981
|
}
|
|
13221
|
-
success(`Renamed ${
|
|
13982
|
+
success(`Renamed ${import_picocolors17.default.bold(currentBranch)} → ${import_picocolors17.default.bold(newBranchName)}`);
|
|
13222
13983
|
await unsetUpstream();
|
|
13223
13984
|
const syncSource2 = getSyncSource(config);
|
|
13224
|
-
info(`Syncing ${
|
|
13985
|
+
info(`Syncing ${import_picocolors17.default.bold(newBranchName)} with latest ${import_picocolors17.default.bold(baseBranch)}...`);
|
|
13225
13986
|
await fetchRemote(syncSource2.remote);
|
|
13226
13987
|
let rebaseResult;
|
|
13227
13988
|
if (staleUpstreamHash) {
|
|
@@ -13232,17 +13993,17 @@ var submit_default = defineCommand({
|
|
|
13232
13993
|
}
|
|
13233
13994
|
if (rebaseResult.exitCode !== 0) {
|
|
13234
13995
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
13235
|
-
info(` ${
|
|
13996
|
+
info(` ${import_picocolors17.default.bold("git rebase --continue")}`, "");
|
|
13236
13997
|
} else {
|
|
13237
|
-
success(`Rebased ${
|
|
13998
|
+
success(`Rebased ${import_picocolors17.default.bold(newBranchName)} onto ${import_picocolors17.default.bold(syncSource2.ref)}.`);
|
|
13238
13999
|
}
|
|
13239
|
-
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.`, "");
|
|
13240
14001
|
return;
|
|
13241
14002
|
}
|
|
13242
14003
|
warn("Discarding local changes...");
|
|
13243
14004
|
}
|
|
13244
14005
|
const syncSource = getSyncSource(config);
|
|
13245
|
-
info(`Switching to ${
|
|
14006
|
+
info(`Switching to ${import_picocolors17.default.bold(baseBranch)} and syncing...`);
|
|
13246
14007
|
await fetchRemote(syncSource.remote);
|
|
13247
14008
|
await resetHard("HEAD");
|
|
13248
14009
|
const coResult = await checkoutBranch(baseBranch);
|
|
@@ -13251,23 +14012,23 @@ var submit_default = defineCommand({
|
|
|
13251
14012
|
process.exit(1);
|
|
13252
14013
|
}
|
|
13253
14014
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
13254
|
-
success(`Synced ${
|
|
13255
|
-
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)}...`);
|
|
13256
14017
|
const delResult = await forceDeleteBranch(currentBranch);
|
|
13257
14018
|
if (delResult.exitCode === 0) {
|
|
13258
|
-
success(`Deleted ${
|
|
14019
|
+
success(`Deleted ${import_picocolors17.default.bold(currentBranch)}.`);
|
|
13259
14020
|
} else {
|
|
13260
14021
|
warn(`Could not delete branch: ${delResult.stderr.trim()}`);
|
|
13261
14022
|
}
|
|
13262
14023
|
console.log();
|
|
13263
|
-
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.`);
|
|
13264
14025
|
return;
|
|
13265
14026
|
}
|
|
13266
14027
|
}
|
|
13267
14028
|
if (ghInstalled && ghAuthed) {
|
|
13268
14029
|
const existingPR = await getPRForBranch(currentBranch);
|
|
13269
14030
|
if (existingPR) {
|
|
13270
|
-
info(`Pushing ${
|
|
14031
|
+
info(`Pushing ${import_picocolors17.default.bold(currentBranch)} to ${origin}...`);
|
|
13271
14032
|
const pushResult2 = await pushSetUpstream(origin, currentBranch);
|
|
13272
14033
|
if (pushResult2.exitCode !== 0) {
|
|
13273
14034
|
error(`Failed to push: ${pushResult2.stderr}`);
|
|
@@ -13278,8 +14039,8 @@ var submit_default = defineCommand({
|
|
|
13278
14039
|
}
|
|
13279
14040
|
process.exit(1);
|
|
13280
14041
|
}
|
|
13281
|
-
success(`Pushed changes to existing PR #${existingPR.number}: ${
|
|
13282
|
-
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)}`);
|
|
13283
14044
|
return;
|
|
13284
14045
|
}
|
|
13285
14046
|
}
|
|
@@ -13287,22 +14048,24 @@ var submit_default = defineCommand({
|
|
|
13287
14048
|
let prBody = null;
|
|
13288
14049
|
async function tryGenerateAI() {
|
|
13289
14050
|
const [copilotError, commits, diff] = await Promise.all([
|
|
13290
|
-
|
|
14051
|
+
checkCopilotAvailable2(),
|
|
13291
14052
|
getLog(baseBranch, "HEAD"),
|
|
13292
14053
|
getLogDiff(baseBranch, "HEAD")
|
|
13293
14054
|
]);
|
|
13294
14055
|
if (!copilotError) {
|
|
13295
|
-
const spinner = createSpinner("Generating AI PR description..."
|
|
14056
|
+
const spinner = createSpinner("Generating AI PR description...", {
|
|
14057
|
+
tips: LOADING_TIPS
|
|
14058
|
+
});
|
|
13296
14059
|
const result = await generatePRDescription(commits, diff, args.model, config.commitConvention);
|
|
13297
14060
|
if (result) {
|
|
13298
14061
|
prTitle = result.title;
|
|
13299
14062
|
prBody = result.body;
|
|
13300
14063
|
spinner.success("PR description generated.");
|
|
13301
14064
|
console.log(`
|
|
13302
|
-
${
|
|
14065
|
+
${import_picocolors17.default.dim("AI title:")} ${import_picocolors17.default.bold(import_picocolors17.default.cyan(prTitle))}`);
|
|
13303
14066
|
console.log(`
|
|
13304
|
-
${
|
|
13305
|
-
console.log(
|
|
14067
|
+
${import_picocolors17.default.dim("AI body preview:")}`);
|
|
14068
|
+
console.log(import_picocolors17.default.dim(prBody.slice(0, 300) + (prBody.length > 300 ? "..." : "")));
|
|
13306
14069
|
} else {
|
|
13307
14070
|
spinner.fail("AI did not return a PR description.");
|
|
13308
14071
|
}
|
|
@@ -13315,8 +14078,28 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13315
14078
|
const REGENERATE = "Regenerate AI description";
|
|
13316
14079
|
let submitAction = "cancel";
|
|
13317
14080
|
const isMaintainer = config.role === "maintainer";
|
|
13318
|
-
if (
|
|
13319
|
-
|
|
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
|
+
]);
|
|
13320
14103
|
if (maintainerChoice === CANCEL) {
|
|
13321
14104
|
warn("Submit cancelled.");
|
|
13322
14105
|
return;
|
|
@@ -13324,12 +14107,13 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13324
14107
|
if (maintainerChoice === SQUASH_LOCAL) {
|
|
13325
14108
|
await performSquashMerge(origin, baseBranch, currentBranch, {
|
|
13326
14109
|
model: args.model,
|
|
13327
|
-
convention: config.commitConvention
|
|
14110
|
+
convention: config.commitConvention,
|
|
14111
|
+
useAI: aiEnabled
|
|
13328
14112
|
});
|
|
13329
14113
|
return;
|
|
13330
14114
|
}
|
|
13331
14115
|
}
|
|
13332
|
-
if (
|
|
14116
|
+
if (aiEnabled) {
|
|
13333
14117
|
await tryGenerateAI();
|
|
13334
14118
|
}
|
|
13335
14119
|
let actionResolved = false;
|
|
@@ -13368,7 +14152,7 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13368
14152
|
}
|
|
13369
14153
|
} else {
|
|
13370
14154
|
const choices = [];
|
|
13371
|
-
if (
|
|
14155
|
+
if (aiEnabled)
|
|
13372
14156
|
choices.push(REGENERATE);
|
|
13373
14157
|
choices.push("Write title & body manually", "Use gh --fill (auto-fill from commits)", CANCEL);
|
|
13374
14158
|
const action = await selectPrompt("How would you like to create the PR?", choices);
|
|
@@ -13392,7 +14176,7 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13392
14176
|
warn("Submit cancelled.");
|
|
13393
14177
|
return;
|
|
13394
14178
|
}
|
|
13395
|
-
info(`Pushing ${
|
|
14179
|
+
info(`Pushing ${import_picocolors17.default.bold(currentBranch)} to ${origin}...`);
|
|
13396
14180
|
const pushResult = await pushSetUpstream(origin, currentBranch);
|
|
13397
14181
|
if (pushResult.exitCode !== 0) {
|
|
13398
14182
|
error(`Failed to push: ${pushResult.stderr}`);
|
|
@@ -13411,7 +14195,7 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13411
14195
|
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/compare/${baseBranch}...${currentBranch}?expand=1`;
|
|
13412
14196
|
console.log();
|
|
13413
14197
|
info("Create your PR manually:", "");
|
|
13414
|
-
console.log(` ${
|
|
14198
|
+
console.log(` ${import_picocolors17.default.cyan(prUrl)}`);
|
|
13415
14199
|
} else {
|
|
13416
14200
|
info("gh CLI not available. Create your PR manually on GitHub.", "");
|
|
13417
14201
|
}
|
|
@@ -13445,7 +14229,7 @@ ${import_picocolors16.default.dim("AI body preview:")}`);
|
|
|
13445
14229
|
});
|
|
13446
14230
|
|
|
13447
14231
|
// src/commands/switch.ts
|
|
13448
|
-
var
|
|
14232
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
13449
14233
|
var switch_default = defineCommand({
|
|
13450
14234
|
meta: {
|
|
13451
14235
|
name: "switch",
|
|
@@ -13466,7 +14250,7 @@ var switch_default = defineCommand({
|
|
|
13466
14250
|
const config = readConfig();
|
|
13467
14251
|
const protectedBranches = config ? getProtectedBranches(config) : ["main", "master"];
|
|
13468
14252
|
const currentBranch = await getCurrentBranch();
|
|
13469
|
-
|
|
14253
|
+
projectHeading("switch", "\uD83D\uDD00");
|
|
13470
14254
|
let targetBranch = args.name;
|
|
13471
14255
|
if (!targetBranch) {
|
|
13472
14256
|
const localBranches = await getLocalBranches();
|
|
@@ -13477,11 +14261,11 @@ var switch_default = defineCommand({
|
|
|
13477
14261
|
const choices = localBranches.filter((b2) => b2.name !== currentBranch).map((b2) => {
|
|
13478
14262
|
const labels = [];
|
|
13479
14263
|
if (protectedBranches.includes(b2.name))
|
|
13480
|
-
labels.push(
|
|
14264
|
+
labels.push(import_picocolors18.default.red("protected"));
|
|
13481
14265
|
if (b2.upstream)
|
|
13482
|
-
labels.push(
|
|
14266
|
+
labels.push(import_picocolors18.default.dim(`→ ${b2.upstream}`));
|
|
13483
14267
|
if (b2.gone)
|
|
13484
|
-
labels.push(
|
|
14268
|
+
labels.push(import_picocolors18.default.red("remote gone"));
|
|
13485
14269
|
const suffix = labels.length > 0 ? ` ${labels.join(" · ")}` : "";
|
|
13486
14270
|
return `${b2.name}${suffix}`;
|
|
13487
14271
|
});
|
|
@@ -13493,7 +14277,7 @@ var switch_default = defineCommand({
|
|
|
13493
14277
|
targetBranch = selected.split(/\s{2,}/)[0].trim();
|
|
13494
14278
|
}
|
|
13495
14279
|
if (targetBranch === currentBranch) {
|
|
13496
|
-
info(`Already on ${
|
|
14280
|
+
info(`Already on ${import_picocolors18.default.bold(targetBranch)}.`);
|
|
13497
14281
|
return;
|
|
13498
14282
|
}
|
|
13499
14283
|
if (await hasUncommittedChanges()) {
|
|
@@ -13512,7 +14296,7 @@ var switch_default = defineCommand({
|
|
|
13512
14296
|
const stashMsg = `contrib-save: auto-save from ${currentBranch}`;
|
|
13513
14297
|
try {
|
|
13514
14298
|
await exec("git", ["stash", "push", "-m", stashMsg]);
|
|
13515
|
-
info(`Saved changes: ${
|
|
14299
|
+
info(`Saved changes: ${import_picocolors18.default.dim(stashMsg)}`);
|
|
13516
14300
|
} catch {
|
|
13517
14301
|
error("Failed to save changes. Please commit or save manually.");
|
|
13518
14302
|
process.exit(1);
|
|
@@ -13528,9 +14312,9 @@ var switch_default = defineCommand({
|
|
|
13528
14312
|
}
|
|
13529
14313
|
process.exit(1);
|
|
13530
14314
|
}
|
|
13531
|
-
success(`Switched to ${
|
|
13532
|
-
info(`Your changes from ${
|
|
13533
|
-
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.`, "");
|
|
13534
14318
|
return;
|
|
13535
14319
|
}
|
|
13536
14320
|
const result = await checkoutBranch(targetBranch);
|
|
@@ -13538,12 +14322,12 @@ var switch_default = defineCommand({
|
|
|
13538
14322
|
error(`Failed to switch to ${targetBranch}: ${result.stderr}`);
|
|
13539
14323
|
process.exit(1);
|
|
13540
14324
|
}
|
|
13541
|
-
success(`Switched to ${
|
|
14325
|
+
success(`Switched to ${import_picocolors18.default.bold(targetBranch)}`);
|
|
13542
14326
|
}
|
|
13543
14327
|
});
|
|
13544
14328
|
|
|
13545
14329
|
// src/commands/sync.ts
|
|
13546
|
-
var
|
|
14330
|
+
var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
13547
14331
|
var sync_default = defineCommand({
|
|
13548
14332
|
meta: {
|
|
13549
14333
|
name: "sync",
|
|
@@ -13574,7 +14358,7 @@ var sync_default = defineCommand({
|
|
|
13574
14358
|
await assertCleanGitState("syncing");
|
|
13575
14359
|
const config = readConfig();
|
|
13576
14360
|
if (!config) {
|
|
13577
|
-
error("No
|
|
14361
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
13578
14362
|
process.exit(1);
|
|
13579
14363
|
}
|
|
13580
14364
|
const { workflow, role, origin } = config;
|
|
@@ -13582,7 +14366,7 @@ var sync_default = defineCommand({
|
|
|
13582
14366
|
error("You have uncommitted changes. Please commit or stash them before syncing.");
|
|
13583
14367
|
process.exit(1);
|
|
13584
14368
|
}
|
|
13585
|
-
|
|
14369
|
+
projectHeading(`sync (${workflow}, ${role})`, "\uD83D\uDD04");
|
|
13586
14370
|
const baseBranch = getBaseBranch(config);
|
|
13587
14371
|
const syncSource = getSyncSource(config);
|
|
13588
14372
|
info(`Fetching ${syncSource.remote}...`);
|
|
@@ -13595,24 +14379,24 @@ var sync_default = defineCommand({
|
|
|
13595
14379
|
await fetchRemote(origin);
|
|
13596
14380
|
}
|
|
13597
14381
|
if (!await refExists(syncSource.ref)) {
|
|
13598
|
-
error(`Remote ref ${
|
|
14382
|
+
error(`Remote ref ${import_picocolors19.default.bold(syncSource.ref)} does not exist.`);
|
|
13599
14383
|
info("This can happen if the branch was renamed or deleted on the remote.", "");
|
|
13600
|
-
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")}.`, "");
|
|
13601
14385
|
process.exit(1);
|
|
13602
14386
|
}
|
|
13603
14387
|
let allowMergeCommit = false;
|
|
13604
14388
|
const div = await getDivergence(baseBranch, syncSource.ref);
|
|
13605
14389
|
if (div.ahead > 0 || div.behind > 0) {
|
|
13606
|
-
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}`);
|
|
13607
14391
|
} else {
|
|
13608
|
-
info(`${
|
|
14392
|
+
info(`${import_picocolors19.default.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
|
|
13609
14393
|
}
|
|
13610
14394
|
if (div.ahead > 0) {
|
|
13611
14395
|
const currentBranch = await getCurrentBranch();
|
|
13612
14396
|
const protectedBranches = getProtectedBranches(config);
|
|
13613
14397
|
const isOnProtected = currentBranch && protectedBranches.includes(currentBranch);
|
|
13614
14398
|
if (isOnProtected) {
|
|
13615
|
-
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.`);
|
|
13616
14400
|
info("Pulling now could create a merge commit, which breaks clean history.");
|
|
13617
14401
|
console.log();
|
|
13618
14402
|
const MOVE_BRANCH = "Move my commits to a new feature branch, then sync";
|
|
@@ -13628,44 +14412,21 @@ var sync_default = defineCommand({
|
|
|
13628
14412
|
return;
|
|
13629
14413
|
}
|
|
13630
14414
|
if (action === MOVE_BRANCH) {
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
if (suggested) {
|
|
13640
|
-
spinner.success("Branch name suggestion ready.");
|
|
13641
|
-
console.log(`
|
|
13642
|
-
${import_picocolors18.default.dim("AI suggestion:")} ${import_picocolors18.default.bold(import_picocolors18.default.cyan(suggested))}`);
|
|
13643
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors18.default.bold(suggested)} as your branch name?`);
|
|
13644
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
13645
|
-
} else {
|
|
13646
|
-
spinner.fail("AI did not return a suggestion.");
|
|
13647
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
13648
|
-
}
|
|
13649
|
-
}
|
|
13650
|
-
}
|
|
13651
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
13652
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors18.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
13653
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
13654
|
-
}
|
|
13655
|
-
if (!isValidBranchName(newBranchName)) {
|
|
13656
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
13657
|
-
process.exit(1);
|
|
13658
|
-
}
|
|
13659
|
-
if (await branchExists(newBranchName)) {
|
|
13660
|
-
error(`Branch ${import_picocolors18.default.bold(newBranchName)} already exists. Choose a different name.`);
|
|
13661
|
-
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;
|
|
13662
14423
|
}
|
|
13663
14424
|
const branchResult = await createBranch(newBranchName);
|
|
13664
14425
|
if (branchResult.exitCode !== 0) {
|
|
13665
14426
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
13666
14427
|
process.exit(1);
|
|
13667
14428
|
}
|
|
13668
|
-
success(`Created ${
|
|
14429
|
+
success(`Created ${import_picocolors19.default.bold(newBranchName)} with your commits.`);
|
|
13669
14430
|
const coResult2 = await checkoutBranch(baseBranch);
|
|
13670
14431
|
if (coResult2.exitCode !== 0) {
|
|
13671
14432
|
error(`Failed to checkout ${baseBranch}: ${coResult2.stderr}`);
|
|
@@ -13673,11 +14434,11 @@ var sync_default = defineCommand({
|
|
|
13673
14434
|
}
|
|
13674
14435
|
const remoteRef = syncSource.ref;
|
|
13675
14436
|
await updateLocalBranch(baseBranch, remoteRef);
|
|
13676
|
-
success(`Reset ${
|
|
13677
|
-
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}`);
|
|
13678
14439
|
console.log();
|
|
13679
|
-
info(`Your commits are safe on ${
|
|
13680
|
-
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)}.`, "");
|
|
13681
14442
|
return;
|
|
13682
14443
|
}
|
|
13683
14444
|
allowMergeCommit = true;
|
|
@@ -13685,7 +14446,7 @@ var sync_default = defineCommand({
|
|
|
13685
14446
|
}
|
|
13686
14447
|
}
|
|
13687
14448
|
if (!args.yes) {
|
|
13688
|
-
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)}.`);
|
|
13689
14450
|
if (!ok)
|
|
13690
14451
|
process.exit(0);
|
|
13691
14452
|
}
|
|
@@ -13699,8 +14460,8 @@ var sync_default = defineCommand({
|
|
|
13699
14460
|
if (allowMergeCommit) {
|
|
13700
14461
|
error(`Pull failed: ${pullResult.stderr.trim()}`);
|
|
13701
14462
|
} else {
|
|
13702
|
-
error(`Fast-forward pull failed. Your local ${
|
|
13703
|
-
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.`, "");
|
|
13704
14465
|
}
|
|
13705
14466
|
process.exit(1);
|
|
13706
14467
|
}
|
|
@@ -13708,7 +14469,7 @@ var sync_default = defineCommand({
|
|
|
13708
14469
|
if (hasDevBranch(workflow) && role === "maintainer") {
|
|
13709
14470
|
const mainDiv = await getDivergence(config.mainBranch, `${origin}/${config.mainBranch}`);
|
|
13710
14471
|
if (mainDiv.behind > 0) {
|
|
13711
|
-
info(`Also syncing ${
|
|
14472
|
+
info(`Also syncing ${import_picocolors19.default.bold(config.mainBranch)}...`);
|
|
13712
14473
|
const mainCoResult = await checkoutBranch(config.mainBranch);
|
|
13713
14474
|
if (mainCoResult.exitCode === 0) {
|
|
13714
14475
|
const mainPullResult = await pullFastForwardOnly(origin, config.mainBranch);
|
|
@@ -13743,23 +14504,26 @@ var sync_default = defineCommand({
|
|
|
13743
14504
|
for (const { name, hash } of refs) {
|
|
13744
14505
|
if (!groups.has(hash))
|
|
13745
14506
|
groups.set(hash, []);
|
|
13746
|
-
groups.get(hash)
|
|
14507
|
+
const group = groups.get(hash);
|
|
14508
|
+
if (group) {
|
|
14509
|
+
group.push(name);
|
|
14510
|
+
}
|
|
13747
14511
|
}
|
|
13748
14512
|
console.log();
|
|
13749
|
-
console.log(` ${
|
|
14513
|
+
console.log(` ${import_picocolors19.default.bold("\uD83D\uDD17 Branch Alignment")}`);
|
|
13750
14514
|
for (const [hash, names] of groups) {
|
|
13751
14515
|
const short = hash.slice(0, 7);
|
|
13752
|
-
const nameStr = names.map((n2) =>
|
|
13753
|
-
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}`);
|
|
13754
14518
|
const subject = await getCommitSubject(hash);
|
|
13755
14519
|
if (subject) {
|
|
13756
|
-
console.log(` ${
|
|
14520
|
+
console.log(` ${import_picocolors19.default.dim(subject)}`);
|
|
13757
14521
|
}
|
|
13758
14522
|
}
|
|
13759
14523
|
if (groups.size === 1) {
|
|
13760
|
-
console.log(` ${
|
|
14524
|
+
console.log(` ${import_picocolors19.default.green("✓")} ${import_picocolors19.default.green("All branches aligned")} ${import_picocolors19.default.dim("— ready to start")}`);
|
|
13761
14525
|
} else {
|
|
13762
|
-
console.log(` ${
|
|
14526
|
+
console.log(` ${import_picocolors19.default.yellow("⚠")} ${import_picocolors19.default.yellow("Branches are not fully aligned")}`);
|
|
13763
14527
|
}
|
|
13764
14528
|
}
|
|
13765
14529
|
}
|
|
@@ -13768,7 +14532,10 @@ var sync_default = defineCommand({
|
|
|
13768
14532
|
|
|
13769
14533
|
// src/commands/update.ts
|
|
13770
14534
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
13771
|
-
var
|
|
14535
|
+
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
14536
|
+
function hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, hasUncommittedChanges2) {
|
|
14537
|
+
return hasUncommittedChanges2 || uniqueCommitsAheadOfBase > 0;
|
|
14538
|
+
}
|
|
13772
14539
|
var update_default = defineCommand({
|
|
13773
14540
|
meta: {
|
|
13774
14541
|
name: "update",
|
|
@@ -13793,7 +14560,7 @@ var update_default = defineCommand({
|
|
|
13793
14560
|
await assertCleanGitState("updating");
|
|
13794
14561
|
const config = readConfig();
|
|
13795
14562
|
if (!config) {
|
|
13796
|
-
error("No
|
|
14563
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
13797
14564
|
process.exit(1);
|
|
13798
14565
|
}
|
|
13799
14566
|
const baseBranch = getBaseBranch(config);
|
|
@@ -13805,8 +14572,8 @@ var update_default = defineCommand({
|
|
|
13805
14572
|
process.exit(1);
|
|
13806
14573
|
}
|
|
13807
14574
|
if (protectedBranches.includes(currentBranch)) {
|
|
13808
|
-
|
|
13809
|
-
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.`);
|
|
13810
14577
|
await fetchAll();
|
|
13811
14578
|
const { origin } = config;
|
|
13812
14579
|
const remoteRef = `${origin}/${currentBranch}`;
|
|
@@ -13815,12 +14582,12 @@ var update_default = defineCommand({
|
|
|
13815
14582
|
const hasCommits = localWork.unpushedCommits > 0;
|
|
13816
14583
|
const hasAnything = hasCommits || dirty;
|
|
13817
14584
|
if (!hasAnything) {
|
|
13818
|
-
info(`No local changes found on ${
|
|
13819
|
-
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.`);
|
|
13820
14587
|
process.exit(1);
|
|
13821
14588
|
}
|
|
13822
14589
|
if (hasCommits) {
|
|
13823
|
-
info(`Found ${
|
|
14590
|
+
info(`Found ${import_picocolors20.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors20.default.bold(currentBranch)}.`);
|
|
13824
14591
|
}
|
|
13825
14592
|
if (dirty) {
|
|
13826
14593
|
info("You also have uncommitted changes in the working tree.");
|
|
@@ -13836,111 +14603,73 @@ var update_default = defineCommand({
|
|
|
13836
14603
|
info("No changes made. You are still on your current branch.");
|
|
13837
14604
|
return;
|
|
13838
14605
|
}
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
if (suggested) {
|
|
13848
|
-
spinner.success("Branch name suggestion ready.");
|
|
13849
|
-
console.log(`
|
|
13850
|
-
${import_picocolors19.default.dim("AI suggestion:")} ${import_picocolors19.default.bold(import_picocolors19.default.cyan(suggested))}`);
|
|
13851
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors19.default.bold(suggested)} as your branch name?`);
|
|
13852
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
13853
|
-
} else {
|
|
13854
|
-
spinner.fail("AI did not return a suggestion.");
|
|
13855
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
13856
|
-
}
|
|
13857
|
-
}
|
|
13858
|
-
}
|
|
13859
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
13860
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors19.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
13861
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
13862
|
-
}
|
|
13863
|
-
if (!isValidBranchName(newBranchName)) {
|
|
13864
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
13865
|
-
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;
|
|
13866
14614
|
}
|
|
13867
14615
|
const branchResult = await createBranch(newBranchName);
|
|
13868
14616
|
if (branchResult.exitCode !== 0) {
|
|
13869
14617
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
13870
14618
|
process.exit(1);
|
|
13871
14619
|
}
|
|
13872
|
-
success(`Created ${
|
|
14620
|
+
success(`Created ${import_picocolors20.default.bold(newBranchName)} with your changes.`);
|
|
13873
14621
|
await updateLocalBranch(currentBranch, remoteRef);
|
|
13874
|
-
info(`Reset ${
|
|
14622
|
+
info(`Reset ${import_picocolors20.default.bold(currentBranch)} back to ${import_picocolors20.default.bold(remoteRef)} — no damage done.`, "");
|
|
13875
14623
|
console.log();
|
|
13876
|
-
success(`You're now on ${
|
|
13877
|
-
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)}.`, "");
|
|
13878
14626
|
return;
|
|
13879
14627
|
}
|
|
13880
14628
|
if (await hasUncommittedChanges()) {
|
|
13881
14629
|
error("You have uncommitted changes. Please commit or stash them first.");
|
|
13882
14630
|
process.exit(1);
|
|
13883
14631
|
}
|
|
13884
|
-
|
|
14632
|
+
projectHeading("update", "\uD83D\uDD03");
|
|
13885
14633
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
13886
14634
|
if (mergedPR) {
|
|
13887
|
-
warn(`PR #${mergedPR.number} (${
|
|
13888
|
-
info(`Link: ${
|
|
13889
|
-
const
|
|
13890
|
-
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);
|
|
13891
14640
|
if (hasWork) {
|
|
13892
|
-
if (
|
|
14641
|
+
if (dirty) {
|
|
13893
14642
|
info("You have uncommitted local changes.");
|
|
13894
14643
|
}
|
|
13895
|
-
if (
|
|
13896
|
-
info(`You have ${
|
|
14644
|
+
if (uniqueCommitsAheadOfBase > 0) {
|
|
14645
|
+
info(`You have ${uniqueCommitsAheadOfBase} local commit(s) not in ${import_picocolors20.default.bold(syncSource.ref)}.`);
|
|
13897
14646
|
}
|
|
13898
14647
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
13899
14648
|
const DISCARD = "Discard all changes and clean up";
|
|
13900
14649
|
const CANCEL = "Cancel";
|
|
13901
|
-
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]);
|
|
13902
14651
|
if (action === CANCEL) {
|
|
13903
14652
|
info("No changes made. You are still on your current branch.");
|
|
13904
14653
|
return;
|
|
13905
14654
|
}
|
|
13906
14655
|
if (action === SAVE_NEW_BRANCH) {
|
|
13907
|
-
|
|
13908
|
-
|
|
13909
|
-
|
|
13910
|
-
|
|
13911
|
-
|
|
13912
|
-
|
|
13913
|
-
|
|
13914
|
-
|
|
13915
|
-
console.log(`
|
|
13916
|
-
${import_picocolors19.default.dim("AI suggestion:")} ${import_picocolors19.default.bold(import_picocolors19.default.cyan(suggested))}`);
|
|
13917
|
-
const accepted = await confirmPrompt(`Use ${import_picocolors19.default.bold(suggested)} as your branch name?`);
|
|
13918
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
13919
|
-
} else {
|
|
13920
|
-
spinner.fail("AI did not return a suggestion.");
|
|
13921
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
13922
|
-
}
|
|
13923
|
-
}
|
|
13924
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
13925
|
-
const prefix = await selectPrompt(`Choose a branch type for ${import_picocolors19.default.bold(newBranchName)}:`, config.branchPrefixes);
|
|
13926
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
13927
|
-
}
|
|
13928
|
-
if (!isValidBranchName(newBranchName)) {
|
|
13929
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
13930
|
-
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;
|
|
13931
14664
|
}
|
|
13932
14665
|
const staleUpstream = await getUpstreamRef();
|
|
13933
14666
|
const staleUpstreamHash = staleUpstream ? await getCommitHash(staleUpstream) : null;
|
|
13934
|
-
if (await branchExists(newBranchName)) {
|
|
13935
|
-
error(`Branch ${import_picocolors19.default.bold(newBranchName)} already exists. Choose a different name.`);
|
|
13936
|
-
process.exit(1);
|
|
13937
|
-
}
|
|
13938
14667
|
const renameResult = await renameBranch(currentBranch, newBranchName);
|
|
13939
14668
|
if (renameResult.exitCode !== 0) {
|
|
13940
14669
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
13941
14670
|
process.exit(1);
|
|
13942
14671
|
}
|
|
13943
|
-
success(`Renamed ${
|
|
14672
|
+
success(`Renamed ${import_picocolors20.default.bold(currentBranch)} → ${import_picocolors20.default.bold(newBranchName)}`);
|
|
13944
14673
|
await unsetUpstream();
|
|
13945
14674
|
await fetchRemote(syncSource.remote);
|
|
13946
14675
|
let rebaseResult2;
|
|
@@ -13952,14 +14681,20 @@ var update_default = defineCommand({
|
|
|
13952
14681
|
}
|
|
13953
14682
|
if (rebaseResult2.exitCode !== 0) {
|
|
13954
14683
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
13955
|
-
info(` ${
|
|
14684
|
+
info(` ${import_picocolors20.default.bold("git rebase --continue")}`, "");
|
|
13956
14685
|
} else {
|
|
13957
|
-
success(`Rebased ${
|
|
14686
|
+
success(`Rebased ${import_picocolors20.default.bold(newBranchName)} onto ${import_picocolors20.default.bold(syncSource.ref)}.`);
|
|
13958
14687
|
}
|
|
13959
|
-
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.`, "");
|
|
13960
14689
|
return;
|
|
13961
14690
|
}
|
|
13962
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
|
+
}
|
|
13963
14698
|
}
|
|
13964
14699
|
await fetchRemote(syncSource.remote);
|
|
13965
14700
|
await resetHard("HEAD");
|
|
@@ -13969,30 +14704,30 @@ var update_default = defineCommand({
|
|
|
13969
14704
|
process.exit(1);
|
|
13970
14705
|
}
|
|
13971
14706
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
13972
|
-
success(`Synced ${
|
|
13973
|
-
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)}...`);
|
|
13974
14709
|
await forceDeleteBranch(currentBranch);
|
|
13975
|
-
success(`Deleted ${
|
|
13976
|
-
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.`, "");
|
|
13977
14712
|
return;
|
|
13978
14713
|
}
|
|
13979
|
-
info(`Updating ${
|
|
14714
|
+
info(`Updating ${import_picocolors20.default.bold(currentBranch)} with latest ${import_picocolors20.default.bold(baseBranch)}...`);
|
|
13980
14715
|
await fetchRemote(syncSource.remote);
|
|
13981
14716
|
if (!await refExists(syncSource.ref)) {
|
|
13982
|
-
error(`Remote ref ${
|
|
14717
|
+
error(`Remote ref ${import_picocolors20.default.bold(syncSource.ref)} does not exist.`);
|
|
13983
14718
|
error("Run `git fetch --all` and verify your remote configuration.");
|
|
13984
14719
|
process.exit(1);
|
|
13985
14720
|
}
|
|
13986
14721
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
13987
14722
|
const rebaseStrategy = await determineRebaseStrategy(currentBranch, syncSource.ref);
|
|
13988
14723
|
if (rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase) {
|
|
13989
|
-
info(
|
|
14724
|
+
info(import_picocolors20.default.dim(`Using --onto rebase (branch was based on a different ref)`));
|
|
13990
14725
|
}
|
|
13991
14726
|
const rebaseResult = rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase ? await rebaseOnto(syncSource.ref, rebaseStrategy.ontoOldBase) : await rebase(syncSource.ref);
|
|
13992
14727
|
if (rebaseResult.exitCode !== 0) {
|
|
13993
14728
|
warn("Rebase hit conflicts. Resolve them manually.");
|
|
13994
14729
|
console.log();
|
|
13995
|
-
if (
|
|
14730
|
+
if (isAIEnabled(config, args["no-ai"])) {
|
|
13996
14731
|
const copilotError = await checkCopilotAvailable();
|
|
13997
14732
|
if (!copilotError) {
|
|
13998
14733
|
info("Fetching AI conflict resolution suggestions...");
|
|
@@ -14010,15 +14745,17 @@ ${content.slice(0, 2000)}
|
|
|
14010
14745
|
} catch {}
|
|
14011
14746
|
}
|
|
14012
14747
|
if (conflictDiff) {
|
|
14013
|
-
const spinner = createSpinner("Analyzing conflicts with AI..."
|
|
14748
|
+
const spinner = createSpinner("Analyzing conflicts with AI...", {
|
|
14749
|
+
tips: LOADING_TIPS
|
|
14750
|
+
});
|
|
14014
14751
|
const suggestion = await suggestConflictResolution(conflictDiff, args.model);
|
|
14015
14752
|
if (suggestion) {
|
|
14016
14753
|
spinner.success("AI conflict guidance ready.");
|
|
14017
14754
|
console.log(`
|
|
14018
|
-
${
|
|
14019
|
-
console.log(
|
|
14755
|
+
${import_picocolors20.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
14756
|
+
console.log(import_picocolors20.default.dim("─".repeat(60)));
|
|
14020
14757
|
console.log(suggestion);
|
|
14021
|
-
console.log(
|
|
14758
|
+
console.log(import_picocolors20.default.dim("─".repeat(60)));
|
|
14022
14759
|
console.log();
|
|
14023
14760
|
} else {
|
|
14024
14761
|
spinner.fail("AI could not analyze the conflicts.");
|
|
@@ -14026,20 +14763,21 @@ ${import_picocolors19.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance
|
|
|
14026
14763
|
}
|
|
14027
14764
|
}
|
|
14028
14765
|
}
|
|
14029
|
-
console.log(
|
|
14766
|
+
console.log(import_picocolors20.default.bold("To resolve:"));
|
|
14030
14767
|
console.log(` 1. Fix conflicts in the affected files`);
|
|
14031
|
-
console.log(` 2. ${
|
|
14032
|
-
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")}`);
|
|
14033
14770
|
console.log();
|
|
14034
|
-
console.log(` Or abort: ${
|
|
14771
|
+
console.log(` Or abort: ${import_picocolors20.default.cyan("git rebase --abort")}`);
|
|
14035
14772
|
process.exit(1);
|
|
14036
14773
|
}
|
|
14037
|
-
success(`${
|
|
14774
|
+
success(`${import_picocolors20.default.bold(currentBranch)} has been rebased onto latest ${import_picocolors20.default.bold(baseBranch)}`);
|
|
14038
14775
|
}
|
|
14039
14776
|
});
|
|
14040
14777
|
|
|
14041
14778
|
// src/commands/validate.ts
|
|
14042
|
-
|
|
14779
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
14780
|
+
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
14043
14781
|
var validate_default = defineCommand({
|
|
14044
14782
|
meta: {
|
|
14045
14783
|
name: "validate",
|
|
@@ -14049,28 +14787,37 @@ var validate_default = defineCommand({
|
|
|
14049
14787
|
message: {
|
|
14050
14788
|
type: "positional",
|
|
14051
14789
|
description: "The commit message to validate",
|
|
14052
|
-
required:
|
|
14790
|
+
required: false
|
|
14791
|
+
},
|
|
14792
|
+
file: {
|
|
14793
|
+
type: "string",
|
|
14794
|
+
description: "Path to a commit message file; only the first line is validated"
|
|
14053
14795
|
}
|
|
14054
14796
|
},
|
|
14055
14797
|
async run({ args }) {
|
|
14056
14798
|
const config = readConfig();
|
|
14057
14799
|
if (!config) {
|
|
14058
|
-
error("No
|
|
14800
|
+
error("No repo config found. Run `contrib setup` first.");
|
|
14059
14801
|
process.exit(1);
|
|
14060
14802
|
}
|
|
14803
|
+
projectHeading("validate", "✅");
|
|
14061
14804
|
const convention = config.commitConvention;
|
|
14062
14805
|
if (convention === "none") {
|
|
14063
14806
|
info('Commit convention is set to "none". All messages are accepted.');
|
|
14064
14807
|
process.exit(0);
|
|
14065
14808
|
}
|
|
14066
|
-
const message = args.message;
|
|
14809
|
+
const message = args.file ? readFileSync5(args.file, "utf-8").split(/\r?\n/, 1)[0] ?? "" : args.message;
|
|
14810
|
+
if (!message) {
|
|
14811
|
+
error("No commit message provided. Pass a message or use --file <path>.");
|
|
14812
|
+
process.exit(1);
|
|
14813
|
+
}
|
|
14067
14814
|
if (validateCommitMessage(message, convention)) {
|
|
14068
14815
|
success(`Valid ${CONVENTION_LABELS[convention]} message.`);
|
|
14069
14816
|
process.exit(0);
|
|
14070
14817
|
}
|
|
14071
14818
|
const errors = getValidationError(convention);
|
|
14072
14819
|
for (const line of errors) {
|
|
14073
|
-
console.error(
|
|
14820
|
+
console.error(import_picocolors21.default.red(` ✗ ${line}`));
|
|
14074
14821
|
}
|
|
14075
14822
|
process.exit(1);
|
|
14076
14823
|
}
|
|
@@ -15369,8 +16116,8 @@ var figlet = (() => {
|
|
|
15369
16116
|
}
|
|
15370
16117
|
};
|
|
15371
16118
|
me2.fonts = function(callback) {
|
|
15372
|
-
return new Promise(function(
|
|
15373
|
-
|
|
16119
|
+
return new Promise(function(resolve3, reject) {
|
|
16120
|
+
resolve3(fontList);
|
|
15374
16121
|
if (callback) {
|
|
15375
16122
|
callback(null, fontList);
|
|
15376
16123
|
}
|
|
@@ -15392,12 +16139,12 @@ var nodeFiglet = figlet;
|
|
|
15392
16139
|
nodeFiglet.defaults({ fontPath });
|
|
15393
16140
|
nodeFiglet.loadFont = function(name, callback) {
|
|
15394
16141
|
const actualFontName = getFontName(name);
|
|
15395
|
-
return new Promise((
|
|
16142
|
+
return new Promise((resolve3, reject) => {
|
|
15396
16143
|
if (nodeFiglet.figFonts[actualFontName]) {
|
|
15397
16144
|
if (callback) {
|
|
15398
16145
|
callback(null, nodeFiglet.figFonts[actualFontName].options);
|
|
15399
16146
|
}
|
|
15400
|
-
|
|
16147
|
+
resolve3(nodeFiglet.figFonts[actualFontName].options);
|
|
15401
16148
|
return;
|
|
15402
16149
|
}
|
|
15403
16150
|
fs2.readFile(path2.join(nodeFiglet.defaults().fontPath, actualFontName + ".flf"), { encoding: "utf-8" }, (err, fontData) => {
|
|
@@ -15414,7 +16161,7 @@ nodeFiglet.loadFont = function(name, callback) {
|
|
|
15414
16161
|
if (callback) {
|
|
15415
16162
|
callback(null, font);
|
|
15416
16163
|
}
|
|
15417
|
-
|
|
16164
|
+
resolve3(font);
|
|
15418
16165
|
} catch (error2) {
|
|
15419
16166
|
const typedError = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
15420
16167
|
if (callback) {
|
|
@@ -15436,7 +16183,7 @@ nodeFiglet.loadFontSync = function(font) {
|
|
|
15436
16183
|
return nodeFiglet.parseFont(actualFontName, fontData);
|
|
15437
16184
|
};
|
|
15438
16185
|
nodeFiglet.fonts = function(next) {
|
|
15439
|
-
return new Promise((
|
|
16186
|
+
return new Promise((resolve3, reject) => {
|
|
15440
16187
|
const fontList2 = [];
|
|
15441
16188
|
fs2.readdir(nodeFiglet.defaults().fontPath, (err, files) => {
|
|
15442
16189
|
if (err) {
|
|
@@ -15450,7 +16197,7 @@ nodeFiglet.fonts = function(next) {
|
|
|
15450
16197
|
}
|
|
15451
16198
|
});
|
|
15452
16199
|
next && next(null, fontList2);
|
|
15453
|
-
|
|
16200
|
+
resolve3(fontList2);
|
|
15454
16201
|
});
|
|
15455
16202
|
});
|
|
15456
16203
|
};
|
|
@@ -15465,7 +16212,37 @@ nodeFiglet.fontsSync = function() {
|
|
|
15465
16212
|
};
|
|
15466
16213
|
|
|
15467
16214
|
// src/ui/banner.ts
|
|
15468
|
-
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
|
|
15469
16246
|
var LOGO_BIG;
|
|
15470
16247
|
try {
|
|
15471
16248
|
LOGO_BIG = nodeFiglet.textSync(`Contribute
|
|
@@ -15487,19 +16264,194 @@ function getAuthor() {
|
|
|
15487
16264
|
}
|
|
15488
16265
|
function showBanner(variant = "small") {
|
|
15489
16266
|
const logo = variant === "big" ? LOGO_BIG : LOGO_SMALL;
|
|
15490
|
-
console.log(
|
|
16267
|
+
console.log(import_picocolors22.default.cyan(`
|
|
15491
16268
|
${logo}`));
|
|
15492
|
-
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
|
+
}
|
|
15493
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)}┘`)}`);
|
|
15494
16352
|
console.log();
|
|
15495
|
-
console.log(` ${
|
|
15496
|
-
console.log(` ${
|
|
15497
|
-
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"))}`);
|
|
15498
16355
|
}
|
|
15499
16356
|
console.log();
|
|
15500
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
|
+
}
|
|
15501
16439
|
|
|
15502
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);
|
|
15503
16455
|
var isVersion = process.argv.includes("--version") || process.argv.includes("-v");
|
|
15504
16456
|
if (!isVersion) {
|
|
15505
16457
|
const subCommands = [
|