contribute-now 0.5.0 → 0.6.0-dev.211431e
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 +11 -1
- package/dist/index.js +479 -215
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
|
-
import { defineCommand as
|
|
6
|
+
import { defineCommand as defineCommand16, runMain } from "citty";
|
|
7
7
|
|
|
8
8
|
// src/commands/branch.ts
|
|
9
9
|
import { defineCommand } from "citty";
|
|
@@ -2268,12 +2268,13 @@ import pc7 from "picocolors";
|
|
|
2268
2268
|
// package.json
|
|
2269
2269
|
var package_default = {
|
|
2270
2270
|
name: "contribute-now",
|
|
2271
|
-
version: "0.
|
|
2271
|
+
version: "0.6.0-dev.211431e",
|
|
2272
2272
|
description: "Developer CLI that automates git workflows — branching, syncing, committing, and PRs — with multi-workflow and commit convention support.",
|
|
2273
2273
|
type: "module",
|
|
2274
2274
|
bin: {
|
|
2275
2275
|
contrib: "dist/index.js",
|
|
2276
|
-
contribute: "dist/index.js"
|
|
2276
|
+
contribute: "dist/index.js",
|
|
2277
|
+
cn: "dist/index.js"
|
|
2277
2278
|
},
|
|
2278
2279
|
files: [
|
|
2279
2280
|
"dist"
|
|
@@ -3090,9 +3091,167 @@ function colorizeSubject(subject) {
|
|
|
3090
3091
|
return pc9.white(subject);
|
|
3091
3092
|
}
|
|
3092
3093
|
|
|
3093
|
-
// src/commands/
|
|
3094
|
+
// src/commands/save.ts
|
|
3094
3095
|
import { defineCommand as defineCommand7 } from "citty";
|
|
3095
3096
|
import pc10 from "picocolors";
|
|
3097
|
+
import { execFile as execFileCb4 } from "node:child_process";
|
|
3098
|
+
function gitRun(args) {
|
|
3099
|
+
return new Promise((resolve) => {
|
|
3100
|
+
execFileCb4("git", args, (err, stdout, stderr) => {
|
|
3101
|
+
resolve({
|
|
3102
|
+
exitCode: err ? err.code === "ENOENT" ? 127 : err.status ?? 1 : 0,
|
|
3103
|
+
stdout: stdout ?? "",
|
|
3104
|
+
stderr: stderr ?? ""
|
|
3105
|
+
});
|
|
3106
|
+
});
|
|
3107
|
+
});
|
|
3108
|
+
}
|
|
3109
|
+
var save_default = defineCommand7({
|
|
3110
|
+
meta: {
|
|
3111
|
+
name: "save",
|
|
3112
|
+
description: "Save uncommitted changes for later. Use --list, --restore, or --drop to manage saves."
|
|
3113
|
+
},
|
|
3114
|
+
args: {
|
|
3115
|
+
list: {
|
|
3116
|
+
type: "boolean",
|
|
3117
|
+
alias: "l",
|
|
3118
|
+
description: "List all saved changes"
|
|
3119
|
+
},
|
|
3120
|
+
restore: {
|
|
3121
|
+
type: "boolean",
|
|
3122
|
+
alias: "r",
|
|
3123
|
+
description: "Restore previously saved changes"
|
|
3124
|
+
},
|
|
3125
|
+
drop: {
|
|
3126
|
+
type: "boolean",
|
|
3127
|
+
alias: "d",
|
|
3128
|
+
description: "Discard a specific save entry"
|
|
3129
|
+
},
|
|
3130
|
+
message: {
|
|
3131
|
+
type: "string",
|
|
3132
|
+
alias: "m",
|
|
3133
|
+
description: "Description for saved changes (used with default save)"
|
|
3134
|
+
}
|
|
3135
|
+
},
|
|
3136
|
+
async run({ args }) {
|
|
3137
|
+
if (!await isGitRepo()) {
|
|
3138
|
+
error("Not inside a git repository.");
|
|
3139
|
+
process.exit(1);
|
|
3140
|
+
}
|
|
3141
|
+
const flags = [args.list, args.restore, args.drop].filter(Boolean);
|
|
3142
|
+
if (flags.length > 1) {
|
|
3143
|
+
error("Please use only one flag at a time: --list, --restore, or --drop.");
|
|
3144
|
+
process.exit(1);
|
|
3145
|
+
}
|
|
3146
|
+
if (args.list) {
|
|
3147
|
+
await handleList();
|
|
3148
|
+
} else if (args.restore) {
|
|
3149
|
+
await handleRestore();
|
|
3150
|
+
} else if (args.drop) {
|
|
3151
|
+
await handleDrop();
|
|
3152
|
+
} else {
|
|
3153
|
+
await handleSave(args.message);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
});
|
|
3157
|
+
async function handleSave(message) {
|
|
3158
|
+
heading("\uD83D\uDCBE contrib save");
|
|
3159
|
+
const currentBranch = await getCurrentBranch();
|
|
3160
|
+
const label = message ?? `work-in-progress on ${currentBranch ?? "unknown"}`;
|
|
3161
|
+
const stashMsg = `contrib-save: ${label}`;
|
|
3162
|
+
const result = await gitRun(["stash", "push", "-m", stashMsg]);
|
|
3163
|
+
if (result.exitCode !== 0) {
|
|
3164
|
+
error(`Failed to save: ${result.stderr}`);
|
|
3165
|
+
process.exit(1);
|
|
3166
|
+
}
|
|
3167
|
+
if (result.stdout.includes("No local changes to save")) {
|
|
3168
|
+
info("No uncommitted changes to save.");
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
success(`Saved: ${pc10.dim(label)}`);
|
|
3172
|
+
info(`Use ${pc10.bold("contrib save --restore")} to bring them back.`);
|
|
3173
|
+
}
|
|
3174
|
+
async function handleRestore() {
|
|
3175
|
+
heading("\uD83D\uDCBE contrib save --restore");
|
|
3176
|
+
const stashes = await getStashList();
|
|
3177
|
+
if (stashes.length === 0) {
|
|
3178
|
+
info("No saved changes found.");
|
|
3179
|
+
return;
|
|
3180
|
+
}
|
|
3181
|
+
if (stashes.length === 1) {
|
|
3182
|
+
const result2 = await gitRun(["stash", "pop", "stash@{0}"]);
|
|
3183
|
+
if (result2.exitCode !== 0) {
|
|
3184
|
+
error(`Failed to restore: ${result2.stderr}`);
|
|
3185
|
+
warn("You may have conflicts. Resolve them and run `git stash drop` when done.");
|
|
3186
|
+
process.exit(1);
|
|
3187
|
+
}
|
|
3188
|
+
success(`Restored: ${pc10.dim(stashes[0].message)}`);
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
const choices = stashes.map((s) => `${s.index} ${s.message}`);
|
|
3192
|
+
const selected = await selectPrompt("Which save to restore?", choices);
|
|
3193
|
+
const idx = selected.split(/\s{2,}/)[0].trim();
|
|
3194
|
+
const result = await gitRun(["stash", "pop", `stash@{${idx}}`]);
|
|
3195
|
+
if (result.exitCode !== 0) {
|
|
3196
|
+
error(`Failed to restore: ${result.stderr}`);
|
|
3197
|
+
warn("You may have conflicts. Resolve them and run `git stash drop` when done.");
|
|
3198
|
+
process.exit(1);
|
|
3199
|
+
}
|
|
3200
|
+
const match = stashes.find((s) => String(s.index) === idx);
|
|
3201
|
+
success(`Restored: ${pc10.dim(match?.message ?? "saved changes")}`);
|
|
3202
|
+
}
|
|
3203
|
+
async function handleList() {
|
|
3204
|
+
heading("\uD83D\uDCBE contrib save --list");
|
|
3205
|
+
const stashes = await getStashList();
|
|
3206
|
+
if (stashes.length === 0) {
|
|
3207
|
+
info("No saved changes.");
|
|
3208
|
+
return;
|
|
3209
|
+
}
|
|
3210
|
+
console.log();
|
|
3211
|
+
for (const s of stashes) {
|
|
3212
|
+
const idx = pc10.dim(`[${s.index}]`);
|
|
3213
|
+
const msg = s.message;
|
|
3214
|
+
console.log(` ${idx} ${msg}`);
|
|
3215
|
+
}
|
|
3216
|
+
console.log();
|
|
3217
|
+
info(`Use ${pc10.bold("contrib save --restore")} to bring changes back.`);
|
|
3218
|
+
info(`Use ${pc10.bold("contrib save --drop")} to discard saved changes.`);
|
|
3219
|
+
}
|
|
3220
|
+
async function handleDrop() {
|
|
3221
|
+
heading("\uD83D\uDCBE contrib save --drop");
|
|
3222
|
+
const stashes = await getStashList();
|
|
3223
|
+
if (stashes.length === 0) {
|
|
3224
|
+
info("No saved changes to drop.");
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
const choices = stashes.map((s) => `${s.index} ${s.message}`);
|
|
3228
|
+
const selected = await selectPrompt("Which save to drop?", choices);
|
|
3229
|
+
const idx = selected.split(/\s{2,}/)[0].trim();
|
|
3230
|
+
const result = await gitRun(["stash", "drop", `stash@{${idx}}`]);
|
|
3231
|
+
if (result.exitCode !== 0) {
|
|
3232
|
+
error(`Failed to drop: ${result.stderr}`);
|
|
3233
|
+
process.exit(1);
|
|
3234
|
+
}
|
|
3235
|
+
const match = stashes.find((s) => String(s.index) === idx);
|
|
3236
|
+
success(`Dropped: ${pc10.dim(match?.message ?? "saved changes")}`);
|
|
3237
|
+
}
|
|
3238
|
+
async function getStashList() {
|
|
3239
|
+
const result = await gitRun(["stash", "list"]);
|
|
3240
|
+
if (result.exitCode !== 0 || !result.stdout.trim())
|
|
3241
|
+
return [];
|
|
3242
|
+
return result.stdout.trimEnd().split(`
|
|
3243
|
+
`).filter(Boolean).map((line) => {
|
|
3244
|
+
const idxMatch = line.match(/^stash@\{(\d+)\}/);
|
|
3245
|
+
const index = idxMatch ? Number.parseInt(idxMatch[1], 10) : 0;
|
|
3246
|
+
const parts = line.split(": ");
|
|
3247
|
+
const message = parts.length > 2 ? parts.slice(2).join(": ") : parts[parts.length - 1];
|
|
3248
|
+
return { index, message };
|
|
3249
|
+
});
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
// src/commands/setup.ts
|
|
3253
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
3254
|
+
import pc11 from "picocolors";
|
|
3096
3255
|
async function shouldContinueSetupWithExistingConfig(options) {
|
|
3097
3256
|
const {
|
|
3098
3257
|
existingConfig,
|
|
@@ -3130,7 +3289,7 @@ async function shouldContinueSetupWithExistingConfig(options) {
|
|
|
3130
3289
|
}
|
|
3131
3290
|
return true;
|
|
3132
3291
|
}
|
|
3133
|
-
var setup_default =
|
|
3292
|
+
var setup_default = defineCommand8({
|
|
3134
3293
|
meta: {
|
|
3135
3294
|
name: "setup",
|
|
3136
3295
|
description: "Initialize contribute-now config for this repo (.contributerc.json)"
|
|
@@ -3165,7 +3324,7 @@ var setup_default = defineCommand7({
|
|
|
3165
3324
|
workflow = "github-flow";
|
|
3166
3325
|
else if (workflowChoice.startsWith("Git Flow"))
|
|
3167
3326
|
workflow = "git-flow";
|
|
3168
|
-
info(`Workflow: ${
|
|
3327
|
+
info(`Workflow: ${pc11.bold(WORKFLOW_DESCRIPTIONS[workflow])}`);
|
|
3169
3328
|
const conventionChoice = await selectPrompt("Which commit convention should this project use?", [
|
|
3170
3329
|
`${CONVENTION_DESCRIPTIONS["clean-commit"]} (recommended)`,
|
|
3171
3330
|
CONVENTION_DESCRIPTIONS.conventional,
|
|
@@ -3229,15 +3388,15 @@ var setup_default = defineCommand7({
|
|
|
3229
3388
|
detectedRole = roleChoice;
|
|
3230
3389
|
detectionSource = "user selection";
|
|
3231
3390
|
} else {
|
|
3232
|
-
info(`Detected role: ${
|
|
3233
|
-
const confirmed = await confirmPrompt(`Role detected as ${
|
|
3391
|
+
info(`Detected role: ${pc11.bold(detectedRole)} (via ${detectionSource})`);
|
|
3392
|
+
const confirmed = await confirmPrompt(`Role detected as ${pc11.bold(detectedRole)}. Is this correct?`);
|
|
3234
3393
|
if (!confirmed) {
|
|
3235
3394
|
const roleChoice = await selectPrompt("Select your role:", ["maintainer", "contributor"]);
|
|
3236
3395
|
detectedRole = roleChoice;
|
|
3237
3396
|
}
|
|
3238
3397
|
}
|
|
3239
3398
|
const defaultConfig = getDefaultConfig();
|
|
3240
|
-
info(
|
|
3399
|
+
info(pc11.dim("Tip: press Enter to keep the default branch name shown in each prompt."));
|
|
3241
3400
|
const mainBranchDefault = defaultConfig.mainBranch;
|
|
3242
3401
|
const mainBranch = await inputPrompt(`Main branch name (default: ${mainBranchDefault} — press Enter to keep)`, mainBranchDefault);
|
|
3243
3402
|
let devBranch;
|
|
@@ -3263,7 +3422,7 @@ var setup_default = defineCommand7({
|
|
|
3263
3422
|
error("Setup cannot continue without the upstream remote for contributors.");
|
|
3264
3423
|
process.exit(1);
|
|
3265
3424
|
}
|
|
3266
|
-
success(`Added remote ${
|
|
3425
|
+
success(`Added remote ${pc11.bold(upstreamRemote)} → ${upstreamUrl}`);
|
|
3267
3426
|
} else {
|
|
3268
3427
|
error("An upstream remote URL is required for contributors.");
|
|
3269
3428
|
info("Add it manually: git remote add upstream <url>");
|
|
@@ -3284,17 +3443,17 @@ var setup_default = defineCommand7({
|
|
|
3284
3443
|
writeConfig(config);
|
|
3285
3444
|
success(`✅ Config written to .contributerc.json`);
|
|
3286
3445
|
const syncRemote = config.role === "contributor" ? config.upstream : config.origin;
|
|
3287
|
-
info(`Fetching ${
|
|
3446
|
+
info(`Fetching ${pc11.bold(syncRemote)} to verify branch configuration...`);
|
|
3288
3447
|
await fetchRemote(syncRemote);
|
|
3289
3448
|
const mainRef = `${syncRemote}/${config.mainBranch}`;
|
|
3290
3449
|
if (!await refExists(mainRef)) {
|
|
3291
|
-
warn(`Main branch ref ${
|
|
3450
|
+
warn(`Main branch ref ${pc11.bold(mainRef)} not found on remote.`);
|
|
3292
3451
|
warn("Config was saved — verify the branch name and re-run setup if needed.");
|
|
3293
3452
|
}
|
|
3294
3453
|
if (config.devBranch) {
|
|
3295
3454
|
const devRef = `${syncRemote}/${config.devBranch}`;
|
|
3296
3455
|
if (!await refExists(devRef)) {
|
|
3297
|
-
warn(`Dev branch ref ${
|
|
3456
|
+
warn(`Dev branch ref ${pc11.bold(devRef)} not found on remote.`);
|
|
3298
3457
|
warn("Config was saved — verify the branch name and re-run setup if needed.");
|
|
3299
3458
|
}
|
|
3300
3459
|
}
|
|
@@ -3302,33 +3461,33 @@ var setup_default = defineCommand7({
|
|
|
3302
3461
|
info("Added .contributerc.json to .gitignore to avoid committing personal config.");
|
|
3303
3462
|
}
|
|
3304
3463
|
console.log();
|
|
3305
|
-
info(`Workflow: ${
|
|
3306
|
-
info(`Convention: ${
|
|
3307
|
-
info(`Role: ${
|
|
3464
|
+
info(`Workflow: ${pc11.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
3465
|
+
info(`Convention: ${pc11.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
|
|
3466
|
+
info(`Role: ${pc11.bold(config.role)}`);
|
|
3308
3467
|
if (config.devBranch) {
|
|
3309
|
-
info(`Main: ${
|
|
3468
|
+
info(`Main: ${pc11.bold(config.mainBranch)} | Dev: ${pc11.bold(config.devBranch)}`);
|
|
3310
3469
|
} else {
|
|
3311
|
-
info(`Main: ${
|
|
3470
|
+
info(`Main: ${pc11.bold(config.mainBranch)}`);
|
|
3312
3471
|
}
|
|
3313
|
-
info(`Origin: ${
|
|
3472
|
+
info(`Origin: ${pc11.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${pc11.bold(config.upstream)}` : ""}`);
|
|
3314
3473
|
}
|
|
3315
3474
|
});
|
|
3316
3475
|
function logConfigSummary(config) {
|
|
3317
|
-
info(`Workflow: ${
|
|
3318
|
-
info(`Convention: ${
|
|
3319
|
-
info(`Role: ${
|
|
3476
|
+
info(`Workflow: ${pc11.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
3477
|
+
info(`Convention: ${pc11.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
|
|
3478
|
+
info(`Role: ${pc11.bold(config.role)}`);
|
|
3320
3479
|
if (config.devBranch) {
|
|
3321
|
-
info(`Main: ${
|
|
3480
|
+
info(`Main: ${pc11.bold(config.mainBranch)} | Dev: ${pc11.bold(config.devBranch)}`);
|
|
3322
3481
|
} else {
|
|
3323
|
-
info(`Main: ${
|
|
3482
|
+
info(`Main: ${pc11.bold(config.mainBranch)}`);
|
|
3324
3483
|
}
|
|
3325
|
-
info(`Origin: ${
|
|
3484
|
+
info(`Origin: ${pc11.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${pc11.bold(config.upstream)}` : ""}`);
|
|
3326
3485
|
}
|
|
3327
3486
|
|
|
3328
3487
|
// src/commands/start.ts
|
|
3329
|
-
import { defineCommand as
|
|
3330
|
-
import
|
|
3331
|
-
var start_default =
|
|
3488
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
3489
|
+
import pc12 from "picocolors";
|
|
3490
|
+
var start_default = defineCommand9({
|
|
3332
3491
|
meta: {
|
|
3333
3492
|
name: "start",
|
|
3334
3493
|
description: "Create a new feature branch from the latest base branch"
|
|
@@ -3384,8 +3543,8 @@ var start_default = defineCommand8({
|
|
|
3384
3543
|
if (suggested) {
|
|
3385
3544
|
spinner.success("Branch name suggestion ready.");
|
|
3386
3545
|
console.log(`
|
|
3387
|
-
${
|
|
3388
|
-
const accepted = await confirmPrompt(`Use ${
|
|
3546
|
+
${pc12.dim("AI suggestion:")} ${pc12.bold(pc12.cyan(suggested))}`);
|
|
3547
|
+
const accepted = await confirmPrompt(`Use ${pc12.bold(suggested)} as your branch name?`);
|
|
3389
3548
|
if (accepted) {
|
|
3390
3549
|
branchName = suggested;
|
|
3391
3550
|
} else {
|
|
@@ -3396,28 +3555,28 @@ var start_default = defineCommand8({
|
|
|
3396
3555
|
}
|
|
3397
3556
|
}
|
|
3398
3557
|
if (!hasPrefix(branchName, branchPrefixes)) {
|
|
3399
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
3558
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc12.bold(branchName)}:`, branchPrefixes);
|
|
3400
3559
|
branchName = formatBranchName(prefix, branchName);
|
|
3401
3560
|
}
|
|
3402
3561
|
if (!isValidBranchName(branchName)) {
|
|
3403
3562
|
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
3404
3563
|
process.exit(1);
|
|
3405
3564
|
}
|
|
3406
|
-
info(`Creating branch: ${
|
|
3565
|
+
info(`Creating branch: ${pc12.bold(branchName)}`);
|
|
3407
3566
|
if (await branchExists(branchName)) {
|
|
3408
|
-
error(`Branch ${
|
|
3409
|
-
info(` Use ${
|
|
3567
|
+
error(`Branch ${pc12.bold(branchName)} already exists.`);
|
|
3568
|
+
info(` Use ${pc12.bold(`git checkout ${branchName}`)} to switch to it, or choose a different name.`);
|
|
3410
3569
|
process.exit(1);
|
|
3411
3570
|
}
|
|
3412
3571
|
await fetchRemote(syncSource.remote);
|
|
3413
3572
|
if (!await refExists(syncSource.ref)) {
|
|
3414
|
-
warn(`Remote ref ${
|
|
3573
|
+
warn(`Remote ref ${pc12.bold(syncSource.ref)} not found. Creating branch from local ${pc12.bold(baseBranch)}.`);
|
|
3415
3574
|
}
|
|
3416
3575
|
const currentBranch = await getCurrentBranch();
|
|
3417
3576
|
if (currentBranch === baseBranch && await refExists(syncSource.ref)) {
|
|
3418
3577
|
const ahead = await countCommitsAhead(baseBranch, syncSource.ref);
|
|
3419
3578
|
if (ahead > 0) {
|
|
3420
|
-
warn(`You are on ${
|
|
3579
|
+
warn(`You are on ${pc12.bold(baseBranch)} with ${pc12.bold(String(ahead))} local commit${ahead > 1 ? "s" : ""} not in ${pc12.bold(syncSource.ref)}.`);
|
|
3421
3580
|
info(" Syncing will discard those commits. Consider backing them up first (e.g. create a branch).");
|
|
3422
3581
|
const proceed = await confirmPrompt("Discard local commits and sync to remote?");
|
|
3423
3582
|
if (!proceed) {
|
|
@@ -3434,10 +3593,10 @@ var start_default = defineCommand8({
|
|
|
3434
3593
|
error(`Failed to create branch: ${result2.stderr}`);
|
|
3435
3594
|
process.exit(1);
|
|
3436
3595
|
}
|
|
3437
|
-
success(`✅ Created ${
|
|
3596
|
+
success(`✅ Created ${pc12.bold(branchName)} from ${pc12.bold(syncSource.ref)}`);
|
|
3438
3597
|
return;
|
|
3439
3598
|
}
|
|
3440
|
-
error(`Failed to update ${
|
|
3599
|
+
error(`Failed to update ${pc12.bold(baseBranch)}: ${updateResult.stderr}`);
|
|
3441
3600
|
info("Make sure your base branch exists locally or the remote ref is available.");
|
|
3442
3601
|
process.exit(1);
|
|
3443
3602
|
}
|
|
@@ -3446,14 +3605,14 @@ var start_default = defineCommand8({
|
|
|
3446
3605
|
error(`Failed to create branch: ${result.stderr}`);
|
|
3447
3606
|
process.exit(1);
|
|
3448
3607
|
}
|
|
3449
|
-
success(`✅ Created ${
|
|
3608
|
+
success(`✅ Created ${pc12.bold(branchName)} from latest ${pc12.bold(baseBranch)}`);
|
|
3450
3609
|
}
|
|
3451
3610
|
});
|
|
3452
3611
|
|
|
3453
3612
|
// src/commands/status.ts
|
|
3454
|
-
import { defineCommand as
|
|
3455
|
-
import
|
|
3456
|
-
var status_default =
|
|
3613
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
3614
|
+
import pc13 from "picocolors";
|
|
3615
|
+
var status_default = defineCommand10({
|
|
3457
3616
|
meta: {
|
|
3458
3617
|
name: "status",
|
|
3459
3618
|
description: "Show sync status of branches"
|
|
@@ -3469,8 +3628,8 @@ var status_default = defineCommand9({
|
|
|
3469
3628
|
process.exit(1);
|
|
3470
3629
|
}
|
|
3471
3630
|
heading("\uD83D\uDCCA contribute-now status");
|
|
3472
|
-
console.log(` ${
|
|
3473
|
-
console.log(` ${
|
|
3631
|
+
console.log(` ${pc13.dim("Workflow:")} ${pc13.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
3632
|
+
console.log(` ${pc13.dim("Role:")} ${pc13.bold(config.role)}`);
|
|
3474
3633
|
console.log();
|
|
3475
3634
|
await fetchAll();
|
|
3476
3635
|
const currentBranch = await getCurrentBranch();
|
|
@@ -3479,7 +3638,7 @@ var status_default = defineCommand9({
|
|
|
3479
3638
|
const isContributor = config.role === "contributor";
|
|
3480
3639
|
const [dirty, fileStatus] = await Promise.all([hasUncommittedChanges(), getFileStatus()]);
|
|
3481
3640
|
if (dirty) {
|
|
3482
|
-
console.log(` ${
|
|
3641
|
+
console.log(` ${pc13.yellow("⚠")} ${pc13.yellow("Uncommitted changes in working tree")}`);
|
|
3483
3642
|
console.log();
|
|
3484
3643
|
}
|
|
3485
3644
|
const mainRemote = `${origin}/${mainBranch}`;
|
|
@@ -3498,82 +3657,82 @@ var status_default = defineCommand9({
|
|
|
3498
3657
|
if (isFeatureBranch) {
|
|
3499
3658
|
const branchDiv = await getDivergence(currentBranch, baseBranch);
|
|
3500
3659
|
const branchLine = formatStatus(currentBranch, baseBranch, branchDiv.ahead, branchDiv.behind);
|
|
3501
|
-
console.log(branchLine +
|
|
3660
|
+
console.log(branchLine + pc13.dim(` (current ${pc13.green("*")})`));
|
|
3502
3661
|
branchStatus = await detectBranchStatus(currentBranch, baseBranch);
|
|
3503
3662
|
if (branchStatus.merged) {
|
|
3504
|
-
console.log(` ${
|
|
3663
|
+
console.log(` ${pc13.green("✓")} ${pc13.green("Branch merged")} — ${pc13.dim(branchStatus.mergedReason ?? "all commits reachable from base")}`);
|
|
3505
3664
|
}
|
|
3506
3665
|
if (branchStatus.stale) {
|
|
3507
|
-
console.log(` ${
|
|
3666
|
+
console.log(` ${pc13.yellow("⏳")} ${pc13.yellow("Branch is stale")} — ${pc13.dim(`last commit ${branchStatus.staleDaysAgo} days ago`)}`);
|
|
3508
3667
|
}
|
|
3509
3668
|
} else if (currentBranch) {
|
|
3510
|
-
console.log(
|
|
3669
|
+
console.log(pc13.dim(` (on ${pc13.bold(currentBranch)} branch)`));
|
|
3511
3670
|
}
|
|
3512
3671
|
const hasFiles = fileStatus.staged.length > 0 || fileStatus.modified.length > 0 || fileStatus.untracked.length > 0;
|
|
3513
3672
|
if (hasFiles) {
|
|
3514
3673
|
console.log();
|
|
3515
3674
|
if (fileStatus.staged.length > 0) {
|
|
3516
|
-
console.log(` ${
|
|
3675
|
+
console.log(` ${pc13.green("Staged for commit:")}`);
|
|
3517
3676
|
for (const { file, status } of fileStatus.staged) {
|
|
3518
|
-
console.log(` ${
|
|
3677
|
+
console.log(` ${pc13.green("+")} ${pc13.dim(`${status}:`)} ${file}`);
|
|
3519
3678
|
}
|
|
3520
3679
|
}
|
|
3521
3680
|
if (fileStatus.modified.length > 0) {
|
|
3522
|
-
console.log(` ${
|
|
3681
|
+
console.log(` ${pc13.yellow("Unstaged changes:")}`);
|
|
3523
3682
|
for (const { file, status } of fileStatus.modified) {
|
|
3524
|
-
console.log(` ${
|
|
3683
|
+
console.log(` ${pc13.yellow("~")} ${pc13.dim(`${status}:`)} ${file}`);
|
|
3525
3684
|
}
|
|
3526
3685
|
}
|
|
3527
3686
|
if (fileStatus.untracked.length > 0) {
|
|
3528
|
-
console.log(` ${
|
|
3687
|
+
console.log(` ${pc13.red("Untracked files:")}`);
|
|
3529
3688
|
for (const file of fileStatus.untracked) {
|
|
3530
|
-
console.log(` ${
|
|
3689
|
+
console.log(` ${pc13.red("?")} ${file}`);
|
|
3531
3690
|
}
|
|
3532
3691
|
}
|
|
3533
3692
|
} else if (!dirty) {
|
|
3534
|
-
console.log(` ${
|
|
3693
|
+
console.log(` ${pc13.green("✓")} ${pc13.dim("Working tree clean")}`);
|
|
3535
3694
|
}
|
|
3536
3695
|
const tips = [];
|
|
3537
3696
|
if (fileStatus.staged.length > 0) {
|
|
3538
|
-
tips.push(`Run ${
|
|
3697
|
+
tips.push(`Run ${pc13.bold("contrib commit")} to commit staged changes`);
|
|
3539
3698
|
}
|
|
3540
3699
|
if (fileStatus.modified.length > 0 || fileStatus.untracked.length > 0) {
|
|
3541
|
-
tips.push(`Run ${
|
|
3700
|
+
tips.push(`Run ${pc13.bold("contrib commit")} to stage and commit changes`);
|
|
3542
3701
|
}
|
|
3543
3702
|
if (isFeatureBranch && branchStatus) {
|
|
3544
3703
|
if (branchStatus.merged) {
|
|
3545
|
-
tips.push(`Run ${
|
|
3704
|
+
tips.push(`Run ${pc13.bold("contrib clean")} to delete this merged branch`);
|
|
3546
3705
|
} else if (branchStatus.stale) {
|
|
3547
|
-
tips.push(`Run ${
|
|
3706
|
+
tips.push(`Run ${pc13.bold("contrib sync")} to rebase on latest changes, or ${pc13.bold("contrib clean")} if no longer needed`);
|
|
3548
3707
|
} else if (fileStatus.staged.length === 0 && fileStatus.modified.length === 0 && fileStatus.untracked.length === 0) {
|
|
3549
3708
|
const branchDiv = await getDivergence(currentBranch, `${origin}/${currentBranch}`);
|
|
3550
3709
|
if (branchDiv.ahead > 0) {
|
|
3551
|
-
tips.push(`Run ${
|
|
3710
|
+
tips.push(`Run ${pc13.bold("contrib submit")} to push and create/update your PR`);
|
|
3552
3711
|
}
|
|
3553
3712
|
}
|
|
3554
3713
|
}
|
|
3555
3714
|
if (tips.length > 0) {
|
|
3556
3715
|
console.log();
|
|
3557
|
-
console.log(` ${
|
|
3716
|
+
console.log(` ${pc13.dim("\uD83D\uDCA1 Tip:")}`);
|
|
3558
3717
|
for (const tip of tips) {
|
|
3559
|
-
console.log(` ${
|
|
3718
|
+
console.log(` ${pc13.dim(tip)}`);
|
|
3560
3719
|
}
|
|
3561
3720
|
}
|
|
3562
3721
|
console.log();
|
|
3563
3722
|
}
|
|
3564
3723
|
});
|
|
3565
3724
|
function formatStatus(branch, base, ahead, behind) {
|
|
3566
|
-
const label =
|
|
3725
|
+
const label = pc13.bold(branch.padEnd(20));
|
|
3567
3726
|
if (ahead === 0 && behind === 0) {
|
|
3568
|
-
return ` ${
|
|
3727
|
+
return ` ${pc13.green("✓")} ${label} ${pc13.dim(`in sync with ${base}`)}`;
|
|
3569
3728
|
}
|
|
3570
3729
|
if (ahead > 0 && behind === 0) {
|
|
3571
|
-
return ` ${
|
|
3730
|
+
return ` ${pc13.yellow("↑")} ${label} ${pc13.yellow(`${ahead} commit${ahead !== 1 ? "s" : ""} ahead of ${base}`)}`;
|
|
3572
3731
|
}
|
|
3573
3732
|
if (behind > 0 && ahead === 0) {
|
|
3574
|
-
return ` ${
|
|
3733
|
+
return ` ${pc13.red("↓")} ${label} ${pc13.red(`${behind} commit${behind !== 1 ? "s" : ""} behind ${base}`)}`;
|
|
3575
3734
|
}
|
|
3576
|
-
return ` ${
|
|
3735
|
+
return ` ${pc13.red("⚡")} ${label} ${pc13.yellow(`${ahead} ahead`)}${pc13.dim(", ")}${pc13.red(`${behind} behind`)} ${pc13.dim(base)}`;
|
|
3577
3736
|
}
|
|
3578
3737
|
var STALE_THRESHOLD_DAYS = 14;
|
|
3579
3738
|
async function detectBranchStatus(branch, baseBranch) {
|
|
@@ -3619,16 +3778,16 @@ async function detectBranchStatus(branch, baseBranch) {
|
|
|
3619
3778
|
}
|
|
3620
3779
|
|
|
3621
3780
|
// src/commands/submit.ts
|
|
3622
|
-
import { defineCommand as
|
|
3623
|
-
import
|
|
3781
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
3782
|
+
import pc14 from "picocolors";
|
|
3624
3783
|
async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
3625
|
-
info(`Checking out ${
|
|
3784
|
+
info(`Checking out ${pc14.bold(baseBranch)}...`);
|
|
3626
3785
|
const coResult = await checkoutBranch(baseBranch);
|
|
3627
3786
|
if (coResult.exitCode !== 0) {
|
|
3628
3787
|
error(`Failed to checkout ${baseBranch}: ${coResult.stderr}`);
|
|
3629
3788
|
process.exit(1);
|
|
3630
3789
|
}
|
|
3631
|
-
info(`Squash merging ${
|
|
3790
|
+
info(`Squash merging ${pc14.bold(featureBranch)} into ${pc14.bold(baseBranch)}...`);
|
|
3632
3791
|
const mergeResult = await mergeSquash(featureBranch);
|
|
3633
3792
|
if (mergeResult.exitCode !== 0) {
|
|
3634
3793
|
error(`Squash merge failed: ${mergeResult.stderr}`);
|
|
@@ -3645,7 +3804,7 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
3645
3804
|
message = aiMsg;
|
|
3646
3805
|
spinner.success("AI commit message generated.");
|
|
3647
3806
|
console.log(`
|
|
3648
|
-
${
|
|
3807
|
+
${pc14.dim("AI suggestion:")} ${pc14.bold(pc14.cyan(message))}`);
|
|
3649
3808
|
} else {
|
|
3650
3809
|
spinner.fail("AI did not return a commit message.");
|
|
3651
3810
|
}
|
|
@@ -3674,7 +3833,7 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
3674
3833
|
message = regen;
|
|
3675
3834
|
spinner.success("Commit message regenerated.");
|
|
3676
3835
|
console.log(`
|
|
3677
|
-
${
|
|
3836
|
+
${pc14.dim("AI suggestion:")} ${pc14.bold(pc14.cyan(regen))}`);
|
|
3678
3837
|
} else {
|
|
3679
3838
|
spinner.fail("Regeneration failed.");
|
|
3680
3839
|
finalMsg = await inputPrompt("Enter commit message");
|
|
@@ -3691,13 +3850,13 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
3691
3850
|
error(`Commit failed: ${commitResult.stderr}`);
|
|
3692
3851
|
process.exit(1);
|
|
3693
3852
|
}
|
|
3694
|
-
info(`Pushing ${
|
|
3853
|
+
info(`Pushing ${pc14.bold(baseBranch)} to ${origin}...`);
|
|
3695
3854
|
const pushResult = await pushBranch(origin, baseBranch);
|
|
3696
3855
|
if (pushResult.exitCode !== 0) {
|
|
3697
3856
|
error(`Failed to push ${baseBranch}: ${pushResult.stderr}`);
|
|
3698
3857
|
process.exit(1);
|
|
3699
3858
|
}
|
|
3700
|
-
info(`Deleting local branch ${
|
|
3859
|
+
info(`Deleting local branch ${pc14.bold(featureBranch)}...`);
|
|
3701
3860
|
const delLocal = await forceDeleteBranch(featureBranch);
|
|
3702
3861
|
if (delLocal.exitCode !== 0) {
|
|
3703
3862
|
warn(`Could not delete local branch: ${delLocal.stderr.trim()}`);
|
|
@@ -3705,16 +3864,16 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
3705
3864
|
const remoteBranchRef = `${origin}/${featureBranch}`;
|
|
3706
3865
|
const remoteExists = await branchExists(remoteBranchRef);
|
|
3707
3866
|
if (remoteExists) {
|
|
3708
|
-
info(`Deleting remote branch ${
|
|
3867
|
+
info(`Deleting remote branch ${pc14.bold(featureBranch)}...`);
|
|
3709
3868
|
const delRemote = await deleteRemoteBranch(origin, featureBranch);
|
|
3710
3869
|
if (delRemote.exitCode !== 0) {
|
|
3711
3870
|
warn(`Could not delete remote branch: ${delRemote.stderr.trim()}`);
|
|
3712
3871
|
}
|
|
3713
3872
|
}
|
|
3714
|
-
success(`✅ Squash merged ${
|
|
3715
|
-
info(`Run ${
|
|
3873
|
+
success(`✅ Squash merged ${pc14.bold(featureBranch)} into ${pc14.bold(baseBranch)} and pushed.`);
|
|
3874
|
+
info(`Run ${pc14.bold("contrib start")} to begin a new feature.`);
|
|
3716
3875
|
}
|
|
3717
|
-
var submit_default =
|
|
3876
|
+
var submit_default = defineCommand11({
|
|
3718
3877
|
meta: {
|
|
3719
3878
|
name: "submit",
|
|
3720
3879
|
description: "Push current branch and create a pull request"
|
|
@@ -3756,7 +3915,7 @@ var submit_default = defineCommand10({
|
|
|
3756
3915
|
}
|
|
3757
3916
|
if (protectedBranches.includes(currentBranch)) {
|
|
3758
3917
|
heading("\uD83D\uDE80 contrib submit");
|
|
3759
|
-
warn(`You're on ${
|
|
3918
|
+
warn(`You're on ${pc14.bold(currentBranch)}, which is a protected branch. PRs should come from feature branches.`);
|
|
3760
3919
|
await fetchAll();
|
|
3761
3920
|
const remoteRef = `${origin}/${currentBranch}`;
|
|
3762
3921
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
@@ -3765,11 +3924,11 @@ var submit_default = defineCommand10({
|
|
|
3765
3924
|
const hasAnything = hasCommits || dirty;
|
|
3766
3925
|
if (!hasAnything) {
|
|
3767
3926
|
error("No local changes or commits to move. Switch to a feature branch first.");
|
|
3768
|
-
info(` Run ${
|
|
3927
|
+
info(` Run ${pc14.bold("contrib start")} to create a new feature branch.`);
|
|
3769
3928
|
process.exit(1);
|
|
3770
3929
|
}
|
|
3771
3930
|
if (hasCommits) {
|
|
3772
|
-
info(`Found ${
|
|
3931
|
+
info(`Found ${pc14.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${pc14.bold(currentBranch)}.`);
|
|
3773
3932
|
}
|
|
3774
3933
|
if (dirty) {
|
|
3775
3934
|
info("You also have uncommitted changes in the working tree.");
|
|
@@ -3785,7 +3944,7 @@ var submit_default = defineCommand10({
|
|
|
3785
3944
|
info("No changes made. You are still on your current branch.");
|
|
3786
3945
|
return;
|
|
3787
3946
|
}
|
|
3788
|
-
info(
|
|
3947
|
+
info(pc14.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
|
|
3789
3948
|
const description = await inputPrompt("What are you going to work on?");
|
|
3790
3949
|
let newBranchName = description;
|
|
3791
3950
|
if (looksLikeNaturalLanguage(description)) {
|
|
@@ -3796,8 +3955,8 @@ var submit_default = defineCommand10({
|
|
|
3796
3955
|
if (suggested) {
|
|
3797
3956
|
spinner.success("Branch name suggestion ready.");
|
|
3798
3957
|
console.log(`
|
|
3799
|
-
${
|
|
3800
|
-
const accepted = await confirmPrompt(`Use ${
|
|
3958
|
+
${pc14.dim("AI suggestion:")} ${pc14.bold(pc14.cyan(suggested))}`);
|
|
3959
|
+
const accepted = await confirmPrompt(`Use ${pc14.bold(suggested)} as your branch name?`);
|
|
3801
3960
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
3802
3961
|
} else {
|
|
3803
3962
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -3806,7 +3965,7 @@ var submit_default = defineCommand10({
|
|
|
3806
3965
|
}
|
|
3807
3966
|
}
|
|
3808
3967
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
3809
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
3968
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc14.bold(newBranchName)}:`, config.branchPrefixes);
|
|
3810
3969
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
3811
3970
|
}
|
|
3812
3971
|
if (!isValidBranchName(newBranchName)) {
|
|
@@ -3814,7 +3973,7 @@ var submit_default = defineCommand10({
|
|
|
3814
3973
|
process.exit(1);
|
|
3815
3974
|
}
|
|
3816
3975
|
if (await branchExists(newBranchName)) {
|
|
3817
|
-
error(`Branch ${
|
|
3976
|
+
error(`Branch ${pc14.bold(newBranchName)} already exists. Choose a different name.`);
|
|
3818
3977
|
process.exit(1);
|
|
3819
3978
|
}
|
|
3820
3979
|
const branchResult = await createBranch(newBranchName);
|
|
@@ -3822,12 +3981,12 @@ var submit_default = defineCommand10({
|
|
|
3822
3981
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
3823
3982
|
process.exit(1);
|
|
3824
3983
|
}
|
|
3825
|
-
success(`Created ${
|
|
3984
|
+
success(`Created ${pc14.bold(newBranchName)} with your changes.`);
|
|
3826
3985
|
await updateLocalBranch(currentBranch, remoteRef);
|
|
3827
|
-
info(`Reset ${
|
|
3986
|
+
info(`Reset ${pc14.bold(currentBranch)} back to ${pc14.bold(remoteRef)} — no damage done.`);
|
|
3828
3987
|
console.log();
|
|
3829
|
-
success(`You're now on ${
|
|
3830
|
-
info(`Run ${
|
|
3988
|
+
success(`You're now on ${pc14.bold(newBranchName)} with all your work intact.`);
|
|
3989
|
+
info(`Run ${pc14.bold("contrib submit")} again to push and create your PR.`);
|
|
3831
3990
|
return;
|
|
3832
3991
|
}
|
|
3833
3992
|
heading("\uD83D\uDE80 contrib submit");
|
|
@@ -3836,7 +3995,7 @@ var submit_default = defineCommand10({
|
|
|
3836
3995
|
if (ghInstalled && ghAuthed) {
|
|
3837
3996
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
3838
3997
|
if (mergedPR) {
|
|
3839
|
-
warn(`PR #${mergedPR.number} (${
|
|
3998
|
+
warn(`PR #${mergedPR.number} (${pc14.bold(mergedPR.title)}) was already merged.`);
|
|
3840
3999
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
3841
4000
|
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
3842
4001
|
if (hasWork) {
|
|
@@ -3844,7 +4003,7 @@ var submit_default = defineCommand10({
|
|
|
3844
4003
|
warn("You have uncommitted changes in your working tree.");
|
|
3845
4004
|
}
|
|
3846
4005
|
if (localWork.unpushedCommits > 0) {
|
|
3847
|
-
warn(`You have ${
|
|
4006
|
+
warn(`You have ${pc14.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not in the merged PR.`);
|
|
3848
4007
|
}
|
|
3849
4008
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
3850
4009
|
const DISCARD = "Discard all changes and clean up";
|
|
@@ -3855,7 +4014,7 @@ var submit_default = defineCommand10({
|
|
|
3855
4014
|
return;
|
|
3856
4015
|
}
|
|
3857
4016
|
if (action === SAVE_NEW_BRANCH) {
|
|
3858
|
-
info(
|
|
4017
|
+
info(pc14.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
|
|
3859
4018
|
const description = await inputPrompt("What are you going to work on?");
|
|
3860
4019
|
let newBranchName = description;
|
|
3861
4020
|
if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
|
|
@@ -3864,8 +4023,8 @@ var submit_default = defineCommand10({
|
|
|
3864
4023
|
if (suggested) {
|
|
3865
4024
|
spinner.success("Branch name suggestion ready.");
|
|
3866
4025
|
console.log(`
|
|
3867
|
-
${
|
|
3868
|
-
const accepted = await confirmPrompt(`Use ${
|
|
4026
|
+
${pc14.dim("AI suggestion:")} ${pc14.bold(pc14.cyan(suggested))}`);
|
|
4027
|
+
const accepted = await confirmPrompt(`Use ${pc14.bold(suggested)} as your branch name?`);
|
|
3869
4028
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
3870
4029
|
} else {
|
|
3871
4030
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -3873,7 +4032,7 @@ var submit_default = defineCommand10({
|
|
|
3873
4032
|
}
|
|
3874
4033
|
}
|
|
3875
4034
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
3876
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
4035
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc14.bold(newBranchName)}:`, config.branchPrefixes);
|
|
3877
4036
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
3878
4037
|
}
|
|
3879
4038
|
if (!isValidBranchName(newBranchName)) {
|
|
@@ -3883,7 +4042,7 @@ var submit_default = defineCommand10({
|
|
|
3883
4042
|
const staleUpstream = await getUpstreamRef();
|
|
3884
4043
|
const staleUpstreamHash = staleUpstream ? await getCommitHash(staleUpstream) : null;
|
|
3885
4044
|
if (await branchExists(newBranchName)) {
|
|
3886
|
-
error(`Branch ${
|
|
4045
|
+
error(`Branch ${pc14.bold(newBranchName)} already exists. Choose a different name.`);
|
|
3887
4046
|
process.exit(1);
|
|
3888
4047
|
}
|
|
3889
4048
|
const renameResult = await renameBranch(currentBranch, newBranchName);
|
|
@@ -3891,10 +4050,10 @@ var submit_default = defineCommand10({
|
|
|
3891
4050
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
3892
4051
|
process.exit(1);
|
|
3893
4052
|
}
|
|
3894
|
-
success(`Renamed ${
|
|
4053
|
+
success(`Renamed ${pc14.bold(currentBranch)} → ${pc14.bold(newBranchName)}`);
|
|
3895
4054
|
await unsetUpstream();
|
|
3896
4055
|
const syncSource2 = getSyncSource(config);
|
|
3897
|
-
info(`Syncing ${
|
|
4056
|
+
info(`Syncing ${pc14.bold(newBranchName)} with latest ${pc14.bold(baseBranch)}...`);
|
|
3898
4057
|
await fetchRemote(syncSource2.remote);
|
|
3899
4058
|
let rebaseResult;
|
|
3900
4059
|
if (staleUpstreamHash) {
|
|
@@ -3905,17 +4064,17 @@ var submit_default = defineCommand10({
|
|
|
3905
4064
|
}
|
|
3906
4065
|
if (rebaseResult.exitCode !== 0) {
|
|
3907
4066
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
3908
|
-
info(` ${
|
|
4067
|
+
info(` ${pc14.bold("git rebase --continue")}`);
|
|
3909
4068
|
} else {
|
|
3910
|
-
success(`Rebased ${
|
|
4069
|
+
success(`Rebased ${pc14.bold(newBranchName)} onto ${pc14.bold(syncSource2.ref)}.`);
|
|
3911
4070
|
}
|
|
3912
|
-
info(`All your changes are preserved. Run ${
|
|
4071
|
+
info(`All your changes are preserved. Run ${pc14.bold("contrib submit")} when ready to create a new PR.`);
|
|
3913
4072
|
return;
|
|
3914
4073
|
}
|
|
3915
4074
|
warn("Discarding local changes...");
|
|
3916
4075
|
}
|
|
3917
4076
|
const syncSource = getSyncSource(config);
|
|
3918
|
-
info(`Switching to ${
|
|
4077
|
+
info(`Switching to ${pc14.bold(baseBranch)} and syncing...`);
|
|
3919
4078
|
await fetchRemote(syncSource.remote);
|
|
3920
4079
|
await resetHard("HEAD");
|
|
3921
4080
|
const coResult = await checkoutBranch(baseBranch);
|
|
@@ -3924,23 +4083,23 @@ var submit_default = defineCommand10({
|
|
|
3924
4083
|
process.exit(1);
|
|
3925
4084
|
}
|
|
3926
4085
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
3927
|
-
success(`Synced ${
|
|
3928
|
-
info(`Deleting stale branch ${
|
|
4086
|
+
success(`Synced ${pc14.bold(baseBranch)} with ${pc14.bold(syncSource.ref)}.`);
|
|
4087
|
+
info(`Deleting stale branch ${pc14.bold(currentBranch)}...`);
|
|
3929
4088
|
const delResult = await forceDeleteBranch(currentBranch);
|
|
3930
4089
|
if (delResult.exitCode === 0) {
|
|
3931
|
-
success(`Deleted ${
|
|
4090
|
+
success(`Deleted ${pc14.bold(currentBranch)}.`);
|
|
3932
4091
|
} else {
|
|
3933
4092
|
warn(`Could not delete branch: ${delResult.stderr.trim()}`);
|
|
3934
4093
|
}
|
|
3935
4094
|
console.log();
|
|
3936
|
-
info(`You're now on ${
|
|
4095
|
+
info(`You're now on ${pc14.bold(baseBranch)}. Run ${pc14.bold("contrib start")} to begin a new feature.`);
|
|
3937
4096
|
return;
|
|
3938
4097
|
}
|
|
3939
4098
|
}
|
|
3940
4099
|
if (ghInstalled && ghAuthed) {
|
|
3941
4100
|
const existingPR = await getPRForBranch(currentBranch);
|
|
3942
4101
|
if (existingPR) {
|
|
3943
|
-
info(`Pushing ${
|
|
4102
|
+
info(`Pushing ${pc14.bold(currentBranch)} to ${origin}...`);
|
|
3944
4103
|
const pushResult2 = await pushSetUpstream(origin, currentBranch);
|
|
3945
4104
|
if (pushResult2.exitCode !== 0) {
|
|
3946
4105
|
error(`Failed to push: ${pushResult2.stderr}`);
|
|
@@ -3951,8 +4110,8 @@ var submit_default = defineCommand10({
|
|
|
3951
4110
|
}
|
|
3952
4111
|
process.exit(1);
|
|
3953
4112
|
}
|
|
3954
|
-
success(`Pushed changes to existing PR #${existingPR.number}: ${
|
|
3955
|
-
console.log(` ${
|
|
4113
|
+
success(`Pushed changes to existing PR #${existingPR.number}: ${pc14.bold(existingPR.title)}`);
|
|
4114
|
+
console.log(` ${pc14.cyan(existingPR.url)}`);
|
|
3956
4115
|
return;
|
|
3957
4116
|
}
|
|
3958
4117
|
}
|
|
@@ -3972,10 +4131,10 @@ var submit_default = defineCommand10({
|
|
|
3972
4131
|
prBody = result.body;
|
|
3973
4132
|
spinner.success("PR description generated.");
|
|
3974
4133
|
console.log(`
|
|
3975
|
-
${
|
|
4134
|
+
${pc14.dim("AI title:")} ${pc14.bold(pc14.cyan(prTitle))}`);
|
|
3976
4135
|
console.log(`
|
|
3977
|
-
${
|
|
3978
|
-
console.log(
|
|
4136
|
+
${pc14.dim("AI body preview:")}`);
|
|
4137
|
+
console.log(pc14.dim(prBody.slice(0, 300) + (prBody.length > 300 ? "..." : "")));
|
|
3979
4138
|
} else {
|
|
3980
4139
|
spinner.fail("AI did not return a PR description.");
|
|
3981
4140
|
}
|
|
@@ -3983,22 +4142,39 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
3983
4142
|
warn(`AI unavailable: ${copilotError}`);
|
|
3984
4143
|
}
|
|
3985
4144
|
}
|
|
3986
|
-
if (!args["no-ai"]) {
|
|
3987
|
-
await tryGenerateAI();
|
|
3988
|
-
}
|
|
3989
4145
|
const CANCEL = "Cancel";
|
|
3990
4146
|
const SQUASH_LOCAL = `Squash merge to ${baseBranch} locally (no PR)`;
|
|
3991
4147
|
const REGENERATE = "Regenerate AI description";
|
|
3992
4148
|
let submitAction = "cancel";
|
|
3993
4149
|
const isMaintainer = config.role === "maintainer";
|
|
4150
|
+
if (isMaintainer) {
|
|
4151
|
+
const maintainerChoice = await selectPrompt("How would you like to submit your changes?", ["Create a PR", SQUASH_LOCAL, CANCEL]);
|
|
4152
|
+
if (maintainerChoice === CANCEL) {
|
|
4153
|
+
warn("Submit cancelled.");
|
|
4154
|
+
return;
|
|
4155
|
+
}
|
|
4156
|
+
if (maintainerChoice === SQUASH_LOCAL) {
|
|
4157
|
+
await performSquashMerge(origin, baseBranch, currentBranch, {
|
|
4158
|
+
model: args.model,
|
|
4159
|
+
convention: config.commitConvention
|
|
4160
|
+
});
|
|
4161
|
+
return;
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
if (!args["no-ai"]) {
|
|
4165
|
+
await tryGenerateAI();
|
|
4166
|
+
}
|
|
3994
4167
|
let actionResolved = false;
|
|
3995
4168
|
while (!actionResolved) {
|
|
3996
4169
|
if (prTitle && prBody) {
|
|
3997
|
-
const
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4170
|
+
const action = await selectPrompt("What would you like to do with the PR description?", [
|
|
4171
|
+
"Use AI description",
|
|
4172
|
+
"Edit title",
|
|
4173
|
+
"Write manually",
|
|
4174
|
+
"Use gh --fill (auto-fill from commits)",
|
|
4175
|
+
REGENERATE,
|
|
4176
|
+
CANCEL
|
|
4177
|
+
]);
|
|
4002
4178
|
if (action === CANCEL) {
|
|
4003
4179
|
submitAction = "cancel";
|
|
4004
4180
|
actionResolved = true;
|
|
@@ -4006,9 +4182,6 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
4006
4182
|
prTitle = null;
|
|
4007
4183
|
prBody = null;
|
|
4008
4184
|
await tryGenerateAI();
|
|
4009
|
-
} else if (action === SQUASH_LOCAL) {
|
|
4010
|
-
submitAction = "squash";
|
|
4011
|
-
actionResolved = true;
|
|
4012
4185
|
} else if (action === "Use AI description") {
|
|
4013
4186
|
submitAction = "create-pr";
|
|
4014
4187
|
actionResolved = true;
|
|
@@ -4027,8 +4200,6 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
4027
4200
|
}
|
|
4028
4201
|
} else {
|
|
4029
4202
|
const choices = [];
|
|
4030
|
-
if (isMaintainer)
|
|
4031
|
-
choices.push(SQUASH_LOCAL);
|
|
4032
4203
|
if (!args["no-ai"])
|
|
4033
4204
|
choices.push(REGENERATE);
|
|
4034
4205
|
choices.push("Write title & body manually", "Use gh --fill (auto-fill from commits)", CANCEL);
|
|
@@ -4038,9 +4209,6 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
4038
4209
|
actionResolved = true;
|
|
4039
4210
|
} else if (action === REGENERATE) {
|
|
4040
4211
|
await tryGenerateAI();
|
|
4041
|
-
} else if (action === SQUASH_LOCAL) {
|
|
4042
|
-
submitAction = "squash";
|
|
4043
|
-
actionResolved = true;
|
|
4044
4212
|
} else if (action === "Write title & body manually") {
|
|
4045
4213
|
prTitle = await inputPrompt("PR title");
|
|
4046
4214
|
prBody = await inputPrompt("PR body (markdown)");
|
|
@@ -4056,14 +4224,7 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
4056
4224
|
warn("Submit cancelled.");
|
|
4057
4225
|
return;
|
|
4058
4226
|
}
|
|
4059
|
-
|
|
4060
|
-
await performSquashMerge(origin, baseBranch, currentBranch, {
|
|
4061
|
-
model: args.model,
|
|
4062
|
-
convention: config.commitConvention
|
|
4063
|
-
});
|
|
4064
|
-
return;
|
|
4065
|
-
}
|
|
4066
|
-
info(`Pushing ${pc13.bold(currentBranch)} to ${origin}...`);
|
|
4227
|
+
info(`Pushing ${pc14.bold(currentBranch)} to ${origin}...`);
|
|
4067
4228
|
const pushResult = await pushSetUpstream(origin, currentBranch);
|
|
4068
4229
|
if (pushResult.exitCode !== 0) {
|
|
4069
4230
|
error(`Failed to push: ${pushResult.stderr}`);
|
|
@@ -4082,7 +4243,7 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
4082
4243
|
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/compare/${baseBranch}...${currentBranch}?expand=1`;
|
|
4083
4244
|
console.log();
|
|
4084
4245
|
info("Create your PR manually:");
|
|
4085
|
-
console.log(` ${
|
|
4246
|
+
console.log(` ${pc14.cyan(prUrl)}`);
|
|
4086
4247
|
} else {
|
|
4087
4248
|
info("gh CLI not available. Create your PR manually on GitHub.");
|
|
4088
4249
|
}
|
|
@@ -4115,10 +4276,109 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
4115
4276
|
}
|
|
4116
4277
|
});
|
|
4117
4278
|
|
|
4279
|
+
// src/commands/switch.ts
|
|
4280
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
4281
|
+
import pc15 from "picocolors";
|
|
4282
|
+
var switch_default = defineCommand12({
|
|
4283
|
+
meta: {
|
|
4284
|
+
name: "switch",
|
|
4285
|
+
description: "Switch to another branch with stash protection for uncommitted changes"
|
|
4286
|
+
},
|
|
4287
|
+
args: {
|
|
4288
|
+
name: {
|
|
4289
|
+
type: "positional",
|
|
4290
|
+
description: "Branch name to switch to (interactive picker if omitted)",
|
|
4291
|
+
required: false
|
|
4292
|
+
}
|
|
4293
|
+
},
|
|
4294
|
+
async run({ args }) {
|
|
4295
|
+
if (!await isGitRepo()) {
|
|
4296
|
+
error("Not inside a git repository.");
|
|
4297
|
+
process.exit(1);
|
|
4298
|
+
}
|
|
4299
|
+
const config = readConfig();
|
|
4300
|
+
const protectedBranches = config ? getProtectedBranches(config) : ["main", "master"];
|
|
4301
|
+
const currentBranch = await getCurrentBranch();
|
|
4302
|
+
heading("\uD83D\uDD00 contrib switch");
|
|
4303
|
+
let targetBranch = args.name;
|
|
4304
|
+
if (!targetBranch) {
|
|
4305
|
+
const localBranches = await getLocalBranches();
|
|
4306
|
+
if (localBranches.length === 0) {
|
|
4307
|
+
error("No local branches found.");
|
|
4308
|
+
process.exit(1);
|
|
4309
|
+
}
|
|
4310
|
+
const choices = localBranches.filter((b) => b.name !== currentBranch).map((b) => {
|
|
4311
|
+
const labels = [];
|
|
4312
|
+
if (protectedBranches.includes(b.name))
|
|
4313
|
+
labels.push(pc15.red("protected"));
|
|
4314
|
+
if (b.upstream)
|
|
4315
|
+
labels.push(pc15.dim(`→ ${b.upstream}`));
|
|
4316
|
+
if (b.gone)
|
|
4317
|
+
labels.push(pc15.red("remote gone"));
|
|
4318
|
+
const suffix = labels.length > 0 ? ` ${labels.join(" · ")}` : "";
|
|
4319
|
+
return `${b.name}${suffix}`;
|
|
4320
|
+
});
|
|
4321
|
+
if (choices.length === 0) {
|
|
4322
|
+
info("You are already on the only local branch.");
|
|
4323
|
+
process.exit(0);
|
|
4324
|
+
}
|
|
4325
|
+
const selected = await selectPrompt("Switch to which branch?", choices);
|
|
4326
|
+
targetBranch = selected.split(/\s{2,}/)[0].trim();
|
|
4327
|
+
}
|
|
4328
|
+
if (targetBranch === currentBranch) {
|
|
4329
|
+
info(`Already on ${pc15.bold(targetBranch)}.`);
|
|
4330
|
+
return;
|
|
4331
|
+
}
|
|
4332
|
+
if (await hasUncommittedChanges()) {
|
|
4333
|
+
warn("You have uncommitted changes.");
|
|
4334
|
+
const action = await selectPrompt("How would you like to handle them?", [
|
|
4335
|
+
"Save changes and switch",
|
|
4336
|
+
"Cancel"
|
|
4337
|
+
]);
|
|
4338
|
+
if (action === "Cancel") {
|
|
4339
|
+
info("Switch cancelled.");
|
|
4340
|
+
return;
|
|
4341
|
+
}
|
|
4342
|
+
const { execFile } = await import("node:child_process");
|
|
4343
|
+
const { promisify } = await import("node:util");
|
|
4344
|
+
const exec = promisify(execFile);
|
|
4345
|
+
const stashMsg = `contrib-save: auto-save from ${currentBranch}`;
|
|
4346
|
+
try {
|
|
4347
|
+
await exec("git", ["stash", "push", "-m", stashMsg]);
|
|
4348
|
+
info(`Saved changes: ${pc15.dim(stashMsg)}`);
|
|
4349
|
+
} catch {
|
|
4350
|
+
error("Failed to save changes. Please commit or save manually.");
|
|
4351
|
+
process.exit(1);
|
|
4352
|
+
}
|
|
4353
|
+
const result2 = await checkoutBranch(targetBranch);
|
|
4354
|
+
if (result2.exitCode !== 0) {
|
|
4355
|
+
error(`Failed to switch to ${targetBranch}: ${result2.stderr}`);
|
|
4356
|
+
try {
|
|
4357
|
+
await exec("git", ["stash", "pop"]);
|
|
4358
|
+
info("Restored saved changes.");
|
|
4359
|
+
} catch {
|
|
4360
|
+
warn("Could not restore save automatically. Use `contrib save --restore` to recover.");
|
|
4361
|
+
}
|
|
4362
|
+
process.exit(1);
|
|
4363
|
+
}
|
|
4364
|
+
success(`Switched to ${pc15.bold(targetBranch)}`);
|
|
4365
|
+
info(`Your changes from ${pc15.bold(currentBranch ?? "previous branch")} are saved.`);
|
|
4366
|
+
info(`Use ${pc15.bold("contrib save --restore")} to bring them back.`);
|
|
4367
|
+
return;
|
|
4368
|
+
}
|
|
4369
|
+
const result = await checkoutBranch(targetBranch);
|
|
4370
|
+
if (result.exitCode !== 0) {
|
|
4371
|
+
error(`Failed to switch to ${targetBranch}: ${result.stderr}`);
|
|
4372
|
+
process.exit(1);
|
|
4373
|
+
}
|
|
4374
|
+
success(`Switched to ${pc15.bold(targetBranch)}`);
|
|
4375
|
+
}
|
|
4376
|
+
});
|
|
4377
|
+
|
|
4118
4378
|
// src/commands/sync.ts
|
|
4119
|
-
import { defineCommand as
|
|
4120
|
-
import
|
|
4121
|
-
var sync_default =
|
|
4379
|
+
import { defineCommand as defineCommand13 } from "citty";
|
|
4380
|
+
import pc16 from "picocolors";
|
|
4381
|
+
var sync_default = defineCommand13({
|
|
4122
4382
|
meta: {
|
|
4123
4383
|
name: "sync",
|
|
4124
4384
|
description: "Sync your local branches with the remote"
|
|
@@ -4169,24 +4429,24 @@ var sync_default = defineCommand11({
|
|
|
4169
4429
|
await fetchRemote(origin);
|
|
4170
4430
|
}
|
|
4171
4431
|
if (!await refExists(syncSource.ref)) {
|
|
4172
|
-
error(`Remote ref ${
|
|
4432
|
+
error(`Remote ref ${pc16.bold(syncSource.ref)} does not exist.`);
|
|
4173
4433
|
info("This can happen if the branch was renamed or deleted on the remote.");
|
|
4174
|
-
info(`Check your config: the base branch may need updating via ${
|
|
4434
|
+
info(`Check your config: the base branch may need updating via ${pc16.bold("contrib setup")}.`);
|
|
4175
4435
|
process.exit(1);
|
|
4176
4436
|
}
|
|
4177
4437
|
let allowMergeCommit = false;
|
|
4178
4438
|
const div = await getDivergence(baseBranch, syncSource.ref);
|
|
4179
4439
|
if (div.ahead > 0 || div.behind > 0) {
|
|
4180
|
-
info(`${
|
|
4440
|
+
info(`${pc16.bold(baseBranch)} is ${pc16.yellow(`${div.ahead} ahead`)} and ${pc16.red(`${div.behind} behind`)} ${syncSource.ref}`);
|
|
4181
4441
|
} else {
|
|
4182
|
-
info(`${
|
|
4442
|
+
info(`${pc16.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
|
|
4183
4443
|
}
|
|
4184
4444
|
if (div.ahead > 0) {
|
|
4185
4445
|
const currentBranch = await getCurrentBranch();
|
|
4186
4446
|
const protectedBranches = getProtectedBranches(config);
|
|
4187
4447
|
const isOnProtected = currentBranch && protectedBranches.includes(currentBranch);
|
|
4188
4448
|
if (isOnProtected) {
|
|
4189
|
-
warn(`You have ${
|
|
4449
|
+
warn(`You have ${pc16.bold(String(div.ahead))} local commit${div.ahead !== 1 ? "s" : ""} on ${pc16.bold(baseBranch)} that aren't on the remote.`);
|
|
4190
4450
|
info("Pulling now could create a merge commit, which breaks clean history.");
|
|
4191
4451
|
console.log();
|
|
4192
4452
|
const MOVE_BRANCH = "Move my commits to a new feature branch, then sync";
|
|
@@ -4202,7 +4462,7 @@ var sync_default = defineCommand11({
|
|
|
4202
4462
|
return;
|
|
4203
4463
|
}
|
|
4204
4464
|
if (action === MOVE_BRANCH) {
|
|
4205
|
-
info(
|
|
4465
|
+
info(pc16.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
|
|
4206
4466
|
const description = await inputPrompt("What are you going to work on?");
|
|
4207
4467
|
let newBranchName = description;
|
|
4208
4468
|
if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
|
|
@@ -4213,8 +4473,8 @@ var sync_default = defineCommand11({
|
|
|
4213
4473
|
if (suggested) {
|
|
4214
4474
|
spinner.success("Branch name suggestion ready.");
|
|
4215
4475
|
console.log(`
|
|
4216
|
-
${
|
|
4217
|
-
const accepted = await confirmPrompt(`Use ${
|
|
4476
|
+
${pc16.dim("AI suggestion:")} ${pc16.bold(pc16.cyan(suggested))}`);
|
|
4477
|
+
const accepted = await confirmPrompt(`Use ${pc16.bold(suggested)} as your branch name?`);
|
|
4218
4478
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
4219
4479
|
} else {
|
|
4220
4480
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -4223,7 +4483,7 @@ var sync_default = defineCommand11({
|
|
|
4223
4483
|
}
|
|
4224
4484
|
}
|
|
4225
4485
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
4226
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
4486
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc16.bold(newBranchName)}:`, config.branchPrefixes);
|
|
4227
4487
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
4228
4488
|
}
|
|
4229
4489
|
if (!isValidBranchName(newBranchName)) {
|
|
@@ -4231,7 +4491,7 @@ var sync_default = defineCommand11({
|
|
|
4231
4491
|
process.exit(1);
|
|
4232
4492
|
}
|
|
4233
4493
|
if (await branchExists(newBranchName)) {
|
|
4234
|
-
error(`Branch ${
|
|
4494
|
+
error(`Branch ${pc16.bold(newBranchName)} already exists. Choose a different name.`);
|
|
4235
4495
|
process.exit(1);
|
|
4236
4496
|
}
|
|
4237
4497
|
const branchResult = await createBranch(newBranchName);
|
|
@@ -4239,7 +4499,7 @@ var sync_default = defineCommand11({
|
|
|
4239
4499
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
4240
4500
|
process.exit(1);
|
|
4241
4501
|
}
|
|
4242
|
-
success(`Created ${
|
|
4502
|
+
success(`Created ${pc16.bold(newBranchName)} with your commits.`);
|
|
4243
4503
|
const coResult2 = await checkoutBranch(baseBranch);
|
|
4244
4504
|
if (coResult2.exitCode !== 0) {
|
|
4245
4505
|
error(`Failed to checkout ${baseBranch}: ${coResult2.stderr}`);
|
|
@@ -4247,11 +4507,11 @@ var sync_default = defineCommand11({
|
|
|
4247
4507
|
}
|
|
4248
4508
|
const remoteRef = syncSource.ref;
|
|
4249
4509
|
await updateLocalBranch(baseBranch, remoteRef);
|
|
4250
|
-
success(`Reset ${
|
|
4251
|
-
success(`✅ ${
|
|
4510
|
+
success(`Reset ${pc16.bold(baseBranch)} to ${pc16.bold(remoteRef)}.`);
|
|
4511
|
+
success(`✅ ${pc16.bold(baseBranch)} is now in sync with ${syncSource.ref}`);
|
|
4252
4512
|
console.log();
|
|
4253
|
-
info(`Your commits are safe on ${
|
|
4254
|
-
info(`Run ${
|
|
4513
|
+
info(`Your commits are safe on ${pc16.bold(newBranchName)}.`);
|
|
4514
|
+
info(`Run ${pc16.bold(`git checkout ${newBranchName}`)} then ${pc16.bold("contrib update")} to rebase onto the synced ${pc16.bold(baseBranch)}.`);
|
|
4255
4515
|
return;
|
|
4256
4516
|
}
|
|
4257
4517
|
allowMergeCommit = true;
|
|
@@ -4259,7 +4519,7 @@ var sync_default = defineCommand11({
|
|
|
4259
4519
|
}
|
|
4260
4520
|
}
|
|
4261
4521
|
if (!args.yes) {
|
|
4262
|
-
const ok = await confirmPrompt(`This will pull ${
|
|
4522
|
+
const ok = await confirmPrompt(`This will pull ${pc16.bold(syncSource.ref)} into local ${pc16.bold(baseBranch)}.`);
|
|
4263
4523
|
if (!ok)
|
|
4264
4524
|
process.exit(0);
|
|
4265
4525
|
}
|
|
@@ -4273,8 +4533,8 @@ var sync_default = defineCommand11({
|
|
|
4273
4533
|
if (allowMergeCommit) {
|
|
4274
4534
|
error(`Pull failed: ${pullResult.stderr.trim()}`);
|
|
4275
4535
|
} else {
|
|
4276
|
-
error(`Fast-forward pull failed. Your local ${
|
|
4277
|
-
info(`Use ${
|
|
4536
|
+
error(`Fast-forward pull failed. Your local ${pc16.bold(baseBranch)} may have diverged.`);
|
|
4537
|
+
info(`Use ${pc16.bold("contrib sync")} again and choose "Move my commits to a new feature branch" to fix this.`);
|
|
4278
4538
|
}
|
|
4279
4539
|
process.exit(1);
|
|
4280
4540
|
}
|
|
@@ -4282,7 +4542,7 @@ var sync_default = defineCommand11({
|
|
|
4282
4542
|
if (hasDevBranch(workflow) && role === "maintainer") {
|
|
4283
4543
|
const mainDiv = await getDivergence(config.mainBranch, `${origin}/${config.mainBranch}`);
|
|
4284
4544
|
if (mainDiv.behind > 0) {
|
|
4285
|
-
info(`Also syncing ${
|
|
4545
|
+
info(`Also syncing ${pc16.bold(config.mainBranch)}...`);
|
|
4286
4546
|
const mainCoResult = await checkoutBranch(config.mainBranch);
|
|
4287
4547
|
if (mainCoResult.exitCode === 0) {
|
|
4288
4548
|
const mainPullResult = await pullFastForwardOnly(origin, config.mainBranch);
|
|
@@ -4298,9 +4558,9 @@ var sync_default = defineCommand11({
|
|
|
4298
4558
|
|
|
4299
4559
|
// src/commands/update.ts
|
|
4300
4560
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
4301
|
-
import { defineCommand as
|
|
4302
|
-
import
|
|
4303
|
-
var update_default =
|
|
4561
|
+
import { defineCommand as defineCommand14 } from "citty";
|
|
4562
|
+
import pc17 from "picocolors";
|
|
4563
|
+
var update_default = defineCommand14({
|
|
4304
4564
|
meta: {
|
|
4305
4565
|
name: "update",
|
|
4306
4566
|
description: "Rebase current branch onto the latest base branch"
|
|
@@ -4337,7 +4597,7 @@ var update_default = defineCommand12({
|
|
|
4337
4597
|
}
|
|
4338
4598
|
if (protectedBranches.includes(currentBranch)) {
|
|
4339
4599
|
heading("\uD83D\uDD03 contrib update");
|
|
4340
|
-
warn(`You're on ${
|
|
4600
|
+
warn(`You're on ${pc17.bold(currentBranch)}, which is a protected branch. Updates (rebase) apply to feature branches.`);
|
|
4341
4601
|
await fetchAll();
|
|
4342
4602
|
const { origin } = config;
|
|
4343
4603
|
const remoteRef = `${origin}/${currentBranch}`;
|
|
@@ -4346,12 +4606,12 @@ var update_default = defineCommand12({
|
|
|
4346
4606
|
const hasCommits = localWork.unpushedCommits > 0;
|
|
4347
4607
|
const hasAnything = hasCommits || dirty;
|
|
4348
4608
|
if (!hasAnything) {
|
|
4349
|
-
info(`No local changes found on ${
|
|
4350
|
-
info(`Use ${
|
|
4609
|
+
info(`No local changes found on ${pc17.bold(currentBranch)}.`);
|
|
4610
|
+
info(`Use ${pc17.bold("contrib sync")} to sync protected branches, or ${pc17.bold("contrib start")} to create a feature branch.`);
|
|
4351
4611
|
process.exit(1);
|
|
4352
4612
|
}
|
|
4353
4613
|
if (hasCommits) {
|
|
4354
|
-
info(`Found ${
|
|
4614
|
+
info(`Found ${pc17.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${pc17.bold(currentBranch)}.`);
|
|
4355
4615
|
}
|
|
4356
4616
|
if (dirty) {
|
|
4357
4617
|
info("You also have uncommitted changes in the working tree.");
|
|
@@ -4367,7 +4627,7 @@ var update_default = defineCommand12({
|
|
|
4367
4627
|
info("No changes made. You are still on your current branch.");
|
|
4368
4628
|
return;
|
|
4369
4629
|
}
|
|
4370
|
-
info(
|
|
4630
|
+
info(pc17.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
|
|
4371
4631
|
const description = await inputPrompt("What are you going to work on?");
|
|
4372
4632
|
let newBranchName = description;
|
|
4373
4633
|
if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
|
|
@@ -4378,8 +4638,8 @@ var update_default = defineCommand12({
|
|
|
4378
4638
|
if (suggested) {
|
|
4379
4639
|
spinner.success("Branch name suggestion ready.");
|
|
4380
4640
|
console.log(`
|
|
4381
|
-
${
|
|
4382
|
-
const accepted = await confirmPrompt(`Use ${
|
|
4641
|
+
${pc17.dim("AI suggestion:")} ${pc17.bold(pc17.cyan(suggested))}`);
|
|
4642
|
+
const accepted = await confirmPrompt(`Use ${pc17.bold(suggested)} as your branch name?`);
|
|
4383
4643
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
4384
4644
|
} else {
|
|
4385
4645
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -4388,7 +4648,7 @@ var update_default = defineCommand12({
|
|
|
4388
4648
|
}
|
|
4389
4649
|
}
|
|
4390
4650
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
4391
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
4651
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc17.bold(newBranchName)}:`, config.branchPrefixes);
|
|
4392
4652
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
4393
4653
|
}
|
|
4394
4654
|
if (!isValidBranchName(newBranchName)) {
|
|
@@ -4400,12 +4660,12 @@ var update_default = defineCommand12({
|
|
|
4400
4660
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
4401
4661
|
process.exit(1);
|
|
4402
4662
|
}
|
|
4403
|
-
success(`Created ${
|
|
4663
|
+
success(`Created ${pc17.bold(newBranchName)} with your changes.`);
|
|
4404
4664
|
await updateLocalBranch(currentBranch, remoteRef);
|
|
4405
|
-
info(`Reset ${
|
|
4665
|
+
info(`Reset ${pc17.bold(currentBranch)} back to ${pc17.bold(remoteRef)} — no damage done.`);
|
|
4406
4666
|
console.log();
|
|
4407
|
-
success(`You're now on ${
|
|
4408
|
-
info(`Run ${
|
|
4667
|
+
success(`You're now on ${pc17.bold(newBranchName)} with all your work intact.`);
|
|
4668
|
+
info(`Run ${pc17.bold("contrib update")} again to rebase onto latest ${pc17.bold(baseBranch)}.`);
|
|
4409
4669
|
return;
|
|
4410
4670
|
}
|
|
4411
4671
|
if (await hasUncommittedChanges()) {
|
|
@@ -4415,8 +4675,8 @@ var update_default = defineCommand12({
|
|
|
4415
4675
|
heading("\uD83D\uDD03 contrib update");
|
|
4416
4676
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
4417
4677
|
if (mergedPR) {
|
|
4418
|
-
warn(`PR #${mergedPR.number} (${
|
|
4419
|
-
info(`Link: ${
|
|
4678
|
+
warn(`PR #${mergedPR.number} (${pc17.bold(mergedPR.title)}) has already been merged.`);
|
|
4679
|
+
info(`Link: ${pc17.underline(mergedPR.url)}`);
|
|
4420
4680
|
const localWork = await hasLocalWork(syncSource.remote, currentBranch);
|
|
4421
4681
|
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
4422
4682
|
if (hasWork) {
|
|
@@ -4429,13 +4689,13 @@ var update_default = defineCommand12({
|
|
|
4429
4689
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
4430
4690
|
const DISCARD = "Discard all changes and clean up";
|
|
4431
4691
|
const CANCEL = "Cancel";
|
|
4432
|
-
const action = await selectPrompt(`${
|
|
4692
|
+
const action = await selectPrompt(`${pc17.bold(currentBranch)} is stale but has local work. What would you like to do?`, [SAVE_NEW_BRANCH, DISCARD, CANCEL]);
|
|
4433
4693
|
if (action === CANCEL) {
|
|
4434
4694
|
info("No changes made. You are still on your current branch.");
|
|
4435
4695
|
return;
|
|
4436
4696
|
}
|
|
4437
4697
|
if (action === SAVE_NEW_BRANCH) {
|
|
4438
|
-
info(
|
|
4698
|
+
info(pc17.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
|
|
4439
4699
|
const description = await inputPrompt("What are you going to work on?");
|
|
4440
4700
|
let newBranchName = description;
|
|
4441
4701
|
if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
|
|
@@ -4444,8 +4704,8 @@ var update_default = defineCommand12({
|
|
|
4444
4704
|
if (suggested) {
|
|
4445
4705
|
spinner.success("Branch name suggestion ready.");
|
|
4446
4706
|
console.log(`
|
|
4447
|
-
${
|
|
4448
|
-
const accepted = await confirmPrompt(`Use ${
|
|
4707
|
+
${pc17.dim("AI suggestion:")} ${pc17.bold(pc17.cyan(suggested))}`);
|
|
4708
|
+
const accepted = await confirmPrompt(`Use ${pc17.bold(suggested)} as your branch name?`);
|
|
4449
4709
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
4450
4710
|
} else {
|
|
4451
4711
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -4453,7 +4713,7 @@ var update_default = defineCommand12({
|
|
|
4453
4713
|
}
|
|
4454
4714
|
}
|
|
4455
4715
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
4456
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
4716
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc17.bold(newBranchName)}:`, config.branchPrefixes);
|
|
4457
4717
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
4458
4718
|
}
|
|
4459
4719
|
if (!isValidBranchName(newBranchName)) {
|
|
@@ -4463,7 +4723,7 @@ var update_default = defineCommand12({
|
|
|
4463
4723
|
const staleUpstream = await getUpstreamRef();
|
|
4464
4724
|
const staleUpstreamHash = staleUpstream ? await getCommitHash(staleUpstream) : null;
|
|
4465
4725
|
if (await branchExists(newBranchName)) {
|
|
4466
|
-
error(`Branch ${
|
|
4726
|
+
error(`Branch ${pc17.bold(newBranchName)} already exists. Choose a different name.`);
|
|
4467
4727
|
process.exit(1);
|
|
4468
4728
|
}
|
|
4469
4729
|
const renameResult = await renameBranch(currentBranch, newBranchName);
|
|
@@ -4471,7 +4731,7 @@ var update_default = defineCommand12({
|
|
|
4471
4731
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
4472
4732
|
process.exit(1);
|
|
4473
4733
|
}
|
|
4474
|
-
success(`Renamed ${
|
|
4734
|
+
success(`Renamed ${pc17.bold(currentBranch)} → ${pc17.bold(newBranchName)}`);
|
|
4475
4735
|
await unsetUpstream();
|
|
4476
4736
|
await fetchRemote(syncSource.remote);
|
|
4477
4737
|
let rebaseResult2;
|
|
@@ -4483,11 +4743,11 @@ var update_default = defineCommand12({
|
|
|
4483
4743
|
}
|
|
4484
4744
|
if (rebaseResult2.exitCode !== 0) {
|
|
4485
4745
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
4486
|
-
info(` ${
|
|
4746
|
+
info(` ${pc17.bold("git rebase --continue")}`);
|
|
4487
4747
|
} else {
|
|
4488
|
-
success(`Rebased ${
|
|
4748
|
+
success(`Rebased ${pc17.bold(newBranchName)} onto ${pc17.bold(syncSource.ref)}.`);
|
|
4489
4749
|
}
|
|
4490
|
-
info(`All your changes are preserved. Run ${
|
|
4750
|
+
info(`All your changes are preserved. Run ${pc17.bold("contrib submit")} when ready to create a new PR.`);
|
|
4491
4751
|
return;
|
|
4492
4752
|
}
|
|
4493
4753
|
warn("Discarding local changes...");
|
|
@@ -4500,24 +4760,24 @@ var update_default = defineCommand12({
|
|
|
4500
4760
|
process.exit(1);
|
|
4501
4761
|
}
|
|
4502
4762
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
4503
|
-
success(`Synced ${
|
|
4504
|
-
info(`Deleting stale branch ${
|
|
4763
|
+
success(`Synced ${pc17.bold(baseBranch)} with ${pc17.bold(syncSource.ref)}.`);
|
|
4764
|
+
info(`Deleting stale branch ${pc17.bold(currentBranch)}...`);
|
|
4505
4765
|
await forceDeleteBranch(currentBranch);
|
|
4506
|
-
success(`Deleted ${
|
|
4507
|
-
info(`Run ${
|
|
4766
|
+
success(`Deleted ${pc17.bold(currentBranch)}.`);
|
|
4767
|
+
info(`Run ${pc17.bold("contrib start")} to begin a new feature branch.`);
|
|
4508
4768
|
return;
|
|
4509
4769
|
}
|
|
4510
|
-
info(`Updating ${
|
|
4770
|
+
info(`Updating ${pc17.bold(currentBranch)} with latest ${pc17.bold(baseBranch)}...`);
|
|
4511
4771
|
await fetchRemote(syncSource.remote);
|
|
4512
4772
|
if (!await refExists(syncSource.ref)) {
|
|
4513
|
-
error(`Remote ref ${
|
|
4773
|
+
error(`Remote ref ${pc17.bold(syncSource.ref)} does not exist.`);
|
|
4514
4774
|
error("Run `git fetch --all` and verify your remote configuration.");
|
|
4515
4775
|
process.exit(1);
|
|
4516
4776
|
}
|
|
4517
4777
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
4518
4778
|
const rebaseStrategy = await determineRebaseStrategy(currentBranch, syncSource.ref);
|
|
4519
4779
|
if (rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase) {
|
|
4520
|
-
info(
|
|
4780
|
+
info(pc17.dim(`Using --onto rebase (branch was based on a different ref)`));
|
|
4521
4781
|
}
|
|
4522
4782
|
const rebaseResult = rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase ? await rebaseOnto(syncSource.ref, rebaseStrategy.ontoOldBase) : await rebase(syncSource.ref);
|
|
4523
4783
|
if (rebaseResult.exitCode !== 0) {
|
|
@@ -4546,10 +4806,10 @@ ${content.slice(0, 2000)}
|
|
|
4546
4806
|
if (suggestion) {
|
|
4547
4807
|
spinner.success("AI conflict guidance ready.");
|
|
4548
4808
|
console.log(`
|
|
4549
|
-
${
|
|
4550
|
-
console.log(
|
|
4809
|
+
${pc17.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
4810
|
+
console.log(pc17.dim("─".repeat(60)));
|
|
4551
4811
|
console.log(suggestion);
|
|
4552
|
-
console.log(
|
|
4812
|
+
console.log(pc17.dim("─".repeat(60)));
|
|
4553
4813
|
console.log();
|
|
4554
4814
|
} else {
|
|
4555
4815
|
spinner.fail("AI could not analyze the conflicts.");
|
|
@@ -4557,22 +4817,22 @@ ${pc15.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
|
4557
4817
|
}
|
|
4558
4818
|
}
|
|
4559
4819
|
}
|
|
4560
|
-
console.log(
|
|
4820
|
+
console.log(pc17.bold("To resolve:"));
|
|
4561
4821
|
console.log(` 1. Fix conflicts in the affected files`);
|
|
4562
|
-
console.log(` 2. ${
|
|
4563
|
-
console.log(` 3. ${
|
|
4822
|
+
console.log(` 2. ${pc17.cyan("git add <resolved-files>")}`);
|
|
4823
|
+
console.log(` 3. ${pc17.cyan("git rebase --continue")}`);
|
|
4564
4824
|
console.log();
|
|
4565
|
-
console.log(` Or abort: ${
|
|
4825
|
+
console.log(` Or abort: ${pc17.cyan("git rebase --abort")}`);
|
|
4566
4826
|
process.exit(1);
|
|
4567
4827
|
}
|
|
4568
|
-
success(`✅ ${
|
|
4828
|
+
success(`✅ ${pc17.bold(currentBranch)} has been rebased onto latest ${pc17.bold(baseBranch)}`);
|
|
4569
4829
|
}
|
|
4570
4830
|
});
|
|
4571
4831
|
|
|
4572
4832
|
// src/commands/validate.ts
|
|
4573
|
-
import { defineCommand as
|
|
4574
|
-
import
|
|
4575
|
-
var validate_default =
|
|
4833
|
+
import { defineCommand as defineCommand15 } from "citty";
|
|
4834
|
+
import pc18 from "picocolors";
|
|
4835
|
+
var validate_default = defineCommand15({
|
|
4576
4836
|
meta: {
|
|
4577
4837
|
name: "validate",
|
|
4578
4838
|
description: "Validate a commit message against the configured convention"
|
|
@@ -4602,7 +4862,7 @@ var validate_default = defineCommand13({
|
|
|
4602
4862
|
}
|
|
4603
4863
|
const errors = getValidationError(convention);
|
|
4604
4864
|
for (const line of errors) {
|
|
4605
|
-
console.error(
|
|
4865
|
+
console.error(pc18.red(` ✗ ${line}`));
|
|
4606
4866
|
}
|
|
4607
4867
|
process.exit(1);
|
|
4608
4868
|
}
|
|
@@ -4610,7 +4870,7 @@ var validate_default = defineCommand13({
|
|
|
4610
4870
|
|
|
4611
4871
|
// src/ui/banner.ts
|
|
4612
4872
|
import figlet from "figlet";
|
|
4613
|
-
import
|
|
4873
|
+
import pc19 from "picocolors";
|
|
4614
4874
|
var LOGO_BIG;
|
|
4615
4875
|
try {
|
|
4616
4876
|
LOGO_BIG = figlet.textSync(`Contribute
|
|
@@ -4632,14 +4892,14 @@ function getAuthor() {
|
|
|
4632
4892
|
}
|
|
4633
4893
|
function showBanner(variant = "small") {
|
|
4634
4894
|
const logo = variant === "big" ? LOGO_BIG : LOGO_SMALL;
|
|
4635
|
-
console.log(
|
|
4895
|
+
console.log(pc19.cyan(`
|
|
4636
4896
|
${logo}`));
|
|
4637
|
-
console.log(` ${
|
|
4897
|
+
console.log(` ${pc19.dim(`v${getVersion()}`)} ${pc19.dim("—")} ${pc19.dim(`Built by ${getAuthor()}`)}`);
|
|
4638
4898
|
if (variant === "big") {
|
|
4639
4899
|
console.log();
|
|
4640
|
-
console.log(` ${
|
|
4641
|
-
console.log(` ${
|
|
4642
|
-
console.log(` ${
|
|
4900
|
+
console.log(` ${pc19.yellow("Star")} ${pc19.cyan("https://github.com/warengonzaga/contribute-now")}`);
|
|
4901
|
+
console.log(` ${pc19.green("Contribute")} ${pc19.cyan("https://github.com/warengonzaga/contribute-now/blob/main/CONTRIBUTING.md")}`);
|
|
4902
|
+
console.log(` ${pc19.magenta("Sponsor")} ${pc19.cyan("https://warengonzaga.com/sponsor")}`);
|
|
4643
4903
|
}
|
|
4644
4904
|
console.log();
|
|
4645
4905
|
}
|
|
@@ -4654,6 +4914,8 @@ if (!isVersion) {
|
|
|
4654
4914
|
"commit",
|
|
4655
4915
|
"update",
|
|
4656
4916
|
"submit",
|
|
4917
|
+
"switch",
|
|
4918
|
+
"save",
|
|
4657
4919
|
"clean",
|
|
4658
4920
|
"status",
|
|
4659
4921
|
"log",
|
|
@@ -4667,7 +4929,7 @@ if (!isVersion) {
|
|
|
4667
4929
|
const useBigBanner = isHelp || !hasSubCommand;
|
|
4668
4930
|
showBanner(useBigBanner ? "big" : "small");
|
|
4669
4931
|
}
|
|
4670
|
-
var main =
|
|
4932
|
+
var main = defineCommand16({
|
|
4671
4933
|
meta: {
|
|
4672
4934
|
name: "contrib",
|
|
4673
4935
|
version: getVersion(),
|
|
@@ -4687,6 +4949,8 @@ var main = defineCommand14({
|
|
|
4687
4949
|
commit: commit_default,
|
|
4688
4950
|
update: update_default,
|
|
4689
4951
|
submit: submit_default,
|
|
4952
|
+
switch: switch_default,
|
|
4953
|
+
save: save_default,
|
|
4690
4954
|
branch: branch_default,
|
|
4691
4955
|
clean: clean_default,
|
|
4692
4956
|
status: status_default,
|