contribute-now 0.5.0 → 0.6.0-dev.ec5e617
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 +454 -192
- 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.ec5e617",
|
|
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
|
}
|
|
@@ -4063,7 +4222,7 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
4063
4222
|
});
|
|
4064
4223
|
return;
|
|
4065
4224
|
}
|
|
4066
|
-
info(`Pushing ${
|
|
4225
|
+
info(`Pushing ${pc14.bold(currentBranch)} to ${origin}...`);
|
|
4067
4226
|
const pushResult = await pushSetUpstream(origin, currentBranch);
|
|
4068
4227
|
if (pushResult.exitCode !== 0) {
|
|
4069
4228
|
error(`Failed to push: ${pushResult.stderr}`);
|
|
@@ -4082,7 +4241,7 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
4082
4241
|
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/compare/${baseBranch}...${currentBranch}?expand=1`;
|
|
4083
4242
|
console.log();
|
|
4084
4243
|
info("Create your PR manually:");
|
|
4085
|
-
console.log(` ${
|
|
4244
|
+
console.log(` ${pc14.cyan(prUrl)}`);
|
|
4086
4245
|
} else {
|
|
4087
4246
|
info("gh CLI not available. Create your PR manually on GitHub.");
|
|
4088
4247
|
}
|
|
@@ -4115,10 +4274,109 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
4115
4274
|
}
|
|
4116
4275
|
});
|
|
4117
4276
|
|
|
4277
|
+
// src/commands/switch.ts
|
|
4278
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
4279
|
+
import pc15 from "picocolors";
|
|
4280
|
+
var switch_default = defineCommand12({
|
|
4281
|
+
meta: {
|
|
4282
|
+
name: "switch",
|
|
4283
|
+
description: "Switch to another branch with stash protection for uncommitted changes"
|
|
4284
|
+
},
|
|
4285
|
+
args: {
|
|
4286
|
+
name: {
|
|
4287
|
+
type: "positional",
|
|
4288
|
+
description: "Branch name to switch to (interactive picker if omitted)",
|
|
4289
|
+
required: false
|
|
4290
|
+
}
|
|
4291
|
+
},
|
|
4292
|
+
async run({ args }) {
|
|
4293
|
+
if (!await isGitRepo()) {
|
|
4294
|
+
error("Not inside a git repository.");
|
|
4295
|
+
process.exit(1);
|
|
4296
|
+
}
|
|
4297
|
+
const config = readConfig();
|
|
4298
|
+
const protectedBranches = config ? getProtectedBranches(config) : ["main", "master"];
|
|
4299
|
+
const currentBranch = await getCurrentBranch();
|
|
4300
|
+
heading("\uD83D\uDD00 contrib switch");
|
|
4301
|
+
let targetBranch = args.name;
|
|
4302
|
+
if (!targetBranch) {
|
|
4303
|
+
const localBranches = await getLocalBranches();
|
|
4304
|
+
if (localBranches.length === 0) {
|
|
4305
|
+
error("No local branches found.");
|
|
4306
|
+
process.exit(1);
|
|
4307
|
+
}
|
|
4308
|
+
const choices = localBranches.filter((b) => b.name !== currentBranch).map((b) => {
|
|
4309
|
+
const labels = [];
|
|
4310
|
+
if (protectedBranches.includes(b.name))
|
|
4311
|
+
labels.push(pc15.red("protected"));
|
|
4312
|
+
if (b.upstream)
|
|
4313
|
+
labels.push(pc15.dim(`→ ${b.upstream}`));
|
|
4314
|
+
if (b.gone)
|
|
4315
|
+
labels.push(pc15.red("remote gone"));
|
|
4316
|
+
const suffix = labels.length > 0 ? ` ${labels.join(" · ")}` : "";
|
|
4317
|
+
return `${b.name}${suffix}`;
|
|
4318
|
+
});
|
|
4319
|
+
if (choices.length === 0) {
|
|
4320
|
+
info("You are already on the only local branch.");
|
|
4321
|
+
process.exit(0);
|
|
4322
|
+
}
|
|
4323
|
+
const selected = await selectPrompt("Switch to which branch?", choices);
|
|
4324
|
+
targetBranch = selected.split(/\s{2,}/)[0].trim();
|
|
4325
|
+
}
|
|
4326
|
+
if (targetBranch === currentBranch) {
|
|
4327
|
+
info(`Already on ${pc15.bold(targetBranch)}.`);
|
|
4328
|
+
return;
|
|
4329
|
+
}
|
|
4330
|
+
if (await hasUncommittedChanges()) {
|
|
4331
|
+
warn("You have uncommitted changes.");
|
|
4332
|
+
const action = await selectPrompt("How would you like to handle them?", [
|
|
4333
|
+
"Save changes and switch",
|
|
4334
|
+
"Cancel"
|
|
4335
|
+
]);
|
|
4336
|
+
if (action === "Cancel") {
|
|
4337
|
+
info("Switch cancelled.");
|
|
4338
|
+
return;
|
|
4339
|
+
}
|
|
4340
|
+
const { execFile } = await import("node:child_process");
|
|
4341
|
+
const { promisify } = await import("node:util");
|
|
4342
|
+
const exec = promisify(execFile);
|
|
4343
|
+
const stashMsg = `contrib-save: auto-save from ${currentBranch}`;
|
|
4344
|
+
try {
|
|
4345
|
+
await exec("git", ["stash", "push", "-m", stashMsg]);
|
|
4346
|
+
info(`Saved changes: ${pc15.dim(stashMsg)}`);
|
|
4347
|
+
} catch {
|
|
4348
|
+
error("Failed to save changes. Please commit or save manually.");
|
|
4349
|
+
process.exit(1);
|
|
4350
|
+
}
|
|
4351
|
+
const result2 = await checkoutBranch(targetBranch);
|
|
4352
|
+
if (result2.exitCode !== 0) {
|
|
4353
|
+
error(`Failed to switch to ${targetBranch}: ${result2.stderr}`);
|
|
4354
|
+
try {
|
|
4355
|
+
await exec("git", ["stash", "pop"]);
|
|
4356
|
+
info("Restored saved changes.");
|
|
4357
|
+
} catch {
|
|
4358
|
+
warn("Could not restore save automatically. Use `contrib save --restore` to recover.");
|
|
4359
|
+
}
|
|
4360
|
+
process.exit(1);
|
|
4361
|
+
}
|
|
4362
|
+
success(`Switched to ${pc15.bold(targetBranch)}`);
|
|
4363
|
+
info(`Your changes from ${pc15.bold(currentBranch ?? "previous branch")} are saved.`);
|
|
4364
|
+
info(`Use ${pc15.bold("contrib save --restore")} to bring them back.`);
|
|
4365
|
+
return;
|
|
4366
|
+
}
|
|
4367
|
+
const result = await checkoutBranch(targetBranch);
|
|
4368
|
+
if (result.exitCode !== 0) {
|
|
4369
|
+
error(`Failed to switch to ${targetBranch}: ${result.stderr}`);
|
|
4370
|
+
process.exit(1);
|
|
4371
|
+
}
|
|
4372
|
+
success(`Switched to ${pc15.bold(targetBranch)}`);
|
|
4373
|
+
}
|
|
4374
|
+
});
|
|
4375
|
+
|
|
4118
4376
|
// src/commands/sync.ts
|
|
4119
|
-
import { defineCommand as
|
|
4120
|
-
import
|
|
4121
|
-
var sync_default =
|
|
4377
|
+
import { defineCommand as defineCommand13 } from "citty";
|
|
4378
|
+
import pc16 from "picocolors";
|
|
4379
|
+
var sync_default = defineCommand13({
|
|
4122
4380
|
meta: {
|
|
4123
4381
|
name: "sync",
|
|
4124
4382
|
description: "Sync your local branches with the remote"
|
|
@@ -4169,24 +4427,24 @@ var sync_default = defineCommand11({
|
|
|
4169
4427
|
await fetchRemote(origin);
|
|
4170
4428
|
}
|
|
4171
4429
|
if (!await refExists(syncSource.ref)) {
|
|
4172
|
-
error(`Remote ref ${
|
|
4430
|
+
error(`Remote ref ${pc16.bold(syncSource.ref)} does not exist.`);
|
|
4173
4431
|
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 ${
|
|
4432
|
+
info(`Check your config: the base branch may need updating via ${pc16.bold("contrib setup")}.`);
|
|
4175
4433
|
process.exit(1);
|
|
4176
4434
|
}
|
|
4177
4435
|
let allowMergeCommit = false;
|
|
4178
4436
|
const div = await getDivergence(baseBranch, syncSource.ref);
|
|
4179
4437
|
if (div.ahead > 0 || div.behind > 0) {
|
|
4180
|
-
info(`${
|
|
4438
|
+
info(`${pc16.bold(baseBranch)} is ${pc16.yellow(`${div.ahead} ahead`)} and ${pc16.red(`${div.behind} behind`)} ${syncSource.ref}`);
|
|
4181
4439
|
} else {
|
|
4182
|
-
info(`${
|
|
4440
|
+
info(`${pc16.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
|
|
4183
4441
|
}
|
|
4184
4442
|
if (div.ahead > 0) {
|
|
4185
4443
|
const currentBranch = await getCurrentBranch();
|
|
4186
4444
|
const protectedBranches = getProtectedBranches(config);
|
|
4187
4445
|
const isOnProtected = currentBranch && protectedBranches.includes(currentBranch);
|
|
4188
4446
|
if (isOnProtected) {
|
|
4189
|
-
warn(`You have ${
|
|
4447
|
+
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
4448
|
info("Pulling now could create a merge commit, which breaks clean history.");
|
|
4191
4449
|
console.log();
|
|
4192
4450
|
const MOVE_BRANCH = "Move my commits to a new feature branch, then sync";
|
|
@@ -4202,7 +4460,7 @@ var sync_default = defineCommand11({
|
|
|
4202
4460
|
return;
|
|
4203
4461
|
}
|
|
4204
4462
|
if (action === MOVE_BRANCH) {
|
|
4205
|
-
info(
|
|
4463
|
+
info(pc16.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
|
|
4206
4464
|
const description = await inputPrompt("What are you going to work on?");
|
|
4207
4465
|
let newBranchName = description;
|
|
4208
4466
|
if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
|
|
@@ -4213,8 +4471,8 @@ var sync_default = defineCommand11({
|
|
|
4213
4471
|
if (suggested) {
|
|
4214
4472
|
spinner.success("Branch name suggestion ready.");
|
|
4215
4473
|
console.log(`
|
|
4216
|
-
${
|
|
4217
|
-
const accepted = await confirmPrompt(`Use ${
|
|
4474
|
+
${pc16.dim("AI suggestion:")} ${pc16.bold(pc16.cyan(suggested))}`);
|
|
4475
|
+
const accepted = await confirmPrompt(`Use ${pc16.bold(suggested)} as your branch name?`);
|
|
4218
4476
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
4219
4477
|
} else {
|
|
4220
4478
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -4223,7 +4481,7 @@ var sync_default = defineCommand11({
|
|
|
4223
4481
|
}
|
|
4224
4482
|
}
|
|
4225
4483
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
4226
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
4484
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc16.bold(newBranchName)}:`, config.branchPrefixes);
|
|
4227
4485
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
4228
4486
|
}
|
|
4229
4487
|
if (!isValidBranchName(newBranchName)) {
|
|
@@ -4231,7 +4489,7 @@ var sync_default = defineCommand11({
|
|
|
4231
4489
|
process.exit(1);
|
|
4232
4490
|
}
|
|
4233
4491
|
if (await branchExists(newBranchName)) {
|
|
4234
|
-
error(`Branch ${
|
|
4492
|
+
error(`Branch ${pc16.bold(newBranchName)} already exists. Choose a different name.`);
|
|
4235
4493
|
process.exit(1);
|
|
4236
4494
|
}
|
|
4237
4495
|
const branchResult = await createBranch(newBranchName);
|
|
@@ -4239,7 +4497,7 @@ var sync_default = defineCommand11({
|
|
|
4239
4497
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
4240
4498
|
process.exit(1);
|
|
4241
4499
|
}
|
|
4242
|
-
success(`Created ${
|
|
4500
|
+
success(`Created ${pc16.bold(newBranchName)} with your commits.`);
|
|
4243
4501
|
const coResult2 = await checkoutBranch(baseBranch);
|
|
4244
4502
|
if (coResult2.exitCode !== 0) {
|
|
4245
4503
|
error(`Failed to checkout ${baseBranch}: ${coResult2.stderr}`);
|
|
@@ -4247,11 +4505,11 @@ var sync_default = defineCommand11({
|
|
|
4247
4505
|
}
|
|
4248
4506
|
const remoteRef = syncSource.ref;
|
|
4249
4507
|
await updateLocalBranch(baseBranch, remoteRef);
|
|
4250
|
-
success(`Reset ${
|
|
4251
|
-
success(`✅ ${
|
|
4508
|
+
success(`Reset ${pc16.bold(baseBranch)} to ${pc16.bold(remoteRef)}.`);
|
|
4509
|
+
success(`✅ ${pc16.bold(baseBranch)} is now in sync with ${syncSource.ref}`);
|
|
4252
4510
|
console.log();
|
|
4253
|
-
info(`Your commits are safe on ${
|
|
4254
|
-
info(`Run ${
|
|
4511
|
+
info(`Your commits are safe on ${pc16.bold(newBranchName)}.`);
|
|
4512
|
+
info(`Run ${pc16.bold(`git checkout ${newBranchName}`)} then ${pc16.bold("contrib update")} to rebase onto the synced ${pc16.bold(baseBranch)}.`);
|
|
4255
4513
|
return;
|
|
4256
4514
|
}
|
|
4257
4515
|
allowMergeCommit = true;
|
|
@@ -4259,7 +4517,7 @@ var sync_default = defineCommand11({
|
|
|
4259
4517
|
}
|
|
4260
4518
|
}
|
|
4261
4519
|
if (!args.yes) {
|
|
4262
|
-
const ok = await confirmPrompt(`This will pull ${
|
|
4520
|
+
const ok = await confirmPrompt(`This will pull ${pc16.bold(syncSource.ref)} into local ${pc16.bold(baseBranch)}.`);
|
|
4263
4521
|
if (!ok)
|
|
4264
4522
|
process.exit(0);
|
|
4265
4523
|
}
|
|
@@ -4273,8 +4531,8 @@ var sync_default = defineCommand11({
|
|
|
4273
4531
|
if (allowMergeCommit) {
|
|
4274
4532
|
error(`Pull failed: ${pullResult.stderr.trim()}`);
|
|
4275
4533
|
} else {
|
|
4276
|
-
error(`Fast-forward pull failed. Your local ${
|
|
4277
|
-
info(`Use ${
|
|
4534
|
+
error(`Fast-forward pull failed. Your local ${pc16.bold(baseBranch)} may have diverged.`);
|
|
4535
|
+
info(`Use ${pc16.bold("contrib sync")} again and choose "Move my commits to a new feature branch" to fix this.`);
|
|
4278
4536
|
}
|
|
4279
4537
|
process.exit(1);
|
|
4280
4538
|
}
|
|
@@ -4282,7 +4540,7 @@ var sync_default = defineCommand11({
|
|
|
4282
4540
|
if (hasDevBranch(workflow) && role === "maintainer") {
|
|
4283
4541
|
const mainDiv = await getDivergence(config.mainBranch, `${origin}/${config.mainBranch}`);
|
|
4284
4542
|
if (mainDiv.behind > 0) {
|
|
4285
|
-
info(`Also syncing ${
|
|
4543
|
+
info(`Also syncing ${pc16.bold(config.mainBranch)}...`);
|
|
4286
4544
|
const mainCoResult = await checkoutBranch(config.mainBranch);
|
|
4287
4545
|
if (mainCoResult.exitCode === 0) {
|
|
4288
4546
|
const mainPullResult = await pullFastForwardOnly(origin, config.mainBranch);
|
|
@@ -4298,9 +4556,9 @@ var sync_default = defineCommand11({
|
|
|
4298
4556
|
|
|
4299
4557
|
// src/commands/update.ts
|
|
4300
4558
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
4301
|
-
import { defineCommand as
|
|
4302
|
-
import
|
|
4303
|
-
var update_default =
|
|
4559
|
+
import { defineCommand as defineCommand14 } from "citty";
|
|
4560
|
+
import pc17 from "picocolors";
|
|
4561
|
+
var update_default = defineCommand14({
|
|
4304
4562
|
meta: {
|
|
4305
4563
|
name: "update",
|
|
4306
4564
|
description: "Rebase current branch onto the latest base branch"
|
|
@@ -4337,7 +4595,7 @@ var update_default = defineCommand12({
|
|
|
4337
4595
|
}
|
|
4338
4596
|
if (protectedBranches.includes(currentBranch)) {
|
|
4339
4597
|
heading("\uD83D\uDD03 contrib update");
|
|
4340
|
-
warn(`You're on ${
|
|
4598
|
+
warn(`You're on ${pc17.bold(currentBranch)}, which is a protected branch. Updates (rebase) apply to feature branches.`);
|
|
4341
4599
|
await fetchAll();
|
|
4342
4600
|
const { origin } = config;
|
|
4343
4601
|
const remoteRef = `${origin}/${currentBranch}`;
|
|
@@ -4346,12 +4604,12 @@ var update_default = defineCommand12({
|
|
|
4346
4604
|
const hasCommits = localWork.unpushedCommits > 0;
|
|
4347
4605
|
const hasAnything = hasCommits || dirty;
|
|
4348
4606
|
if (!hasAnything) {
|
|
4349
|
-
info(`No local changes found on ${
|
|
4350
|
-
info(`Use ${
|
|
4607
|
+
info(`No local changes found on ${pc17.bold(currentBranch)}.`);
|
|
4608
|
+
info(`Use ${pc17.bold("contrib sync")} to sync protected branches, or ${pc17.bold("contrib start")} to create a feature branch.`);
|
|
4351
4609
|
process.exit(1);
|
|
4352
4610
|
}
|
|
4353
4611
|
if (hasCommits) {
|
|
4354
|
-
info(`Found ${
|
|
4612
|
+
info(`Found ${pc17.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${pc17.bold(currentBranch)}.`);
|
|
4355
4613
|
}
|
|
4356
4614
|
if (dirty) {
|
|
4357
4615
|
info("You also have uncommitted changes in the working tree.");
|
|
@@ -4367,7 +4625,7 @@ var update_default = defineCommand12({
|
|
|
4367
4625
|
info("No changes made. You are still on your current branch.");
|
|
4368
4626
|
return;
|
|
4369
4627
|
}
|
|
4370
|
-
info(
|
|
4628
|
+
info(pc17.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
|
|
4371
4629
|
const description = await inputPrompt("What are you going to work on?");
|
|
4372
4630
|
let newBranchName = description;
|
|
4373
4631
|
if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
|
|
@@ -4378,8 +4636,8 @@ var update_default = defineCommand12({
|
|
|
4378
4636
|
if (suggested) {
|
|
4379
4637
|
spinner.success("Branch name suggestion ready.");
|
|
4380
4638
|
console.log(`
|
|
4381
|
-
${
|
|
4382
|
-
const accepted = await confirmPrompt(`Use ${
|
|
4639
|
+
${pc17.dim("AI suggestion:")} ${pc17.bold(pc17.cyan(suggested))}`);
|
|
4640
|
+
const accepted = await confirmPrompt(`Use ${pc17.bold(suggested)} as your branch name?`);
|
|
4383
4641
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
4384
4642
|
} else {
|
|
4385
4643
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -4388,7 +4646,7 @@ var update_default = defineCommand12({
|
|
|
4388
4646
|
}
|
|
4389
4647
|
}
|
|
4390
4648
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
4391
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
4649
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc17.bold(newBranchName)}:`, config.branchPrefixes);
|
|
4392
4650
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
4393
4651
|
}
|
|
4394
4652
|
if (!isValidBranchName(newBranchName)) {
|
|
@@ -4400,12 +4658,12 @@ var update_default = defineCommand12({
|
|
|
4400
4658
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
4401
4659
|
process.exit(1);
|
|
4402
4660
|
}
|
|
4403
|
-
success(`Created ${
|
|
4661
|
+
success(`Created ${pc17.bold(newBranchName)} with your changes.`);
|
|
4404
4662
|
await updateLocalBranch(currentBranch, remoteRef);
|
|
4405
|
-
info(`Reset ${
|
|
4663
|
+
info(`Reset ${pc17.bold(currentBranch)} back to ${pc17.bold(remoteRef)} — no damage done.`);
|
|
4406
4664
|
console.log();
|
|
4407
|
-
success(`You're now on ${
|
|
4408
|
-
info(`Run ${
|
|
4665
|
+
success(`You're now on ${pc17.bold(newBranchName)} with all your work intact.`);
|
|
4666
|
+
info(`Run ${pc17.bold("contrib update")} again to rebase onto latest ${pc17.bold(baseBranch)}.`);
|
|
4409
4667
|
return;
|
|
4410
4668
|
}
|
|
4411
4669
|
if (await hasUncommittedChanges()) {
|
|
@@ -4415,8 +4673,8 @@ var update_default = defineCommand12({
|
|
|
4415
4673
|
heading("\uD83D\uDD03 contrib update");
|
|
4416
4674
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
4417
4675
|
if (mergedPR) {
|
|
4418
|
-
warn(`PR #${mergedPR.number} (${
|
|
4419
|
-
info(`Link: ${
|
|
4676
|
+
warn(`PR #${mergedPR.number} (${pc17.bold(mergedPR.title)}) has already been merged.`);
|
|
4677
|
+
info(`Link: ${pc17.underline(mergedPR.url)}`);
|
|
4420
4678
|
const localWork = await hasLocalWork(syncSource.remote, currentBranch);
|
|
4421
4679
|
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
4422
4680
|
if (hasWork) {
|
|
@@ -4429,13 +4687,13 @@ var update_default = defineCommand12({
|
|
|
4429
4687
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
4430
4688
|
const DISCARD = "Discard all changes and clean up";
|
|
4431
4689
|
const CANCEL = "Cancel";
|
|
4432
|
-
const action = await selectPrompt(`${
|
|
4690
|
+
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
4691
|
if (action === CANCEL) {
|
|
4434
4692
|
info("No changes made. You are still on your current branch.");
|
|
4435
4693
|
return;
|
|
4436
4694
|
}
|
|
4437
4695
|
if (action === SAVE_NEW_BRANCH) {
|
|
4438
|
-
info(
|
|
4696
|
+
info(pc17.dim("Tip: Describe what you're going to work on in plain English and we'll generate a branch name."));
|
|
4439
4697
|
const description = await inputPrompt("What are you going to work on?");
|
|
4440
4698
|
let newBranchName = description;
|
|
4441
4699
|
if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
|
|
@@ -4444,8 +4702,8 @@ var update_default = defineCommand12({
|
|
|
4444
4702
|
if (suggested) {
|
|
4445
4703
|
spinner.success("Branch name suggestion ready.");
|
|
4446
4704
|
console.log(`
|
|
4447
|
-
${
|
|
4448
|
-
const accepted = await confirmPrompt(`Use ${
|
|
4705
|
+
${pc17.dim("AI suggestion:")} ${pc17.bold(pc17.cyan(suggested))}`);
|
|
4706
|
+
const accepted = await confirmPrompt(`Use ${pc17.bold(suggested)} as your branch name?`);
|
|
4449
4707
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
4450
4708
|
} else {
|
|
4451
4709
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -4453,7 +4711,7 @@ var update_default = defineCommand12({
|
|
|
4453
4711
|
}
|
|
4454
4712
|
}
|
|
4455
4713
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
4456
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
4714
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc17.bold(newBranchName)}:`, config.branchPrefixes);
|
|
4457
4715
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
4458
4716
|
}
|
|
4459
4717
|
if (!isValidBranchName(newBranchName)) {
|
|
@@ -4463,7 +4721,7 @@ var update_default = defineCommand12({
|
|
|
4463
4721
|
const staleUpstream = await getUpstreamRef();
|
|
4464
4722
|
const staleUpstreamHash = staleUpstream ? await getCommitHash(staleUpstream) : null;
|
|
4465
4723
|
if (await branchExists(newBranchName)) {
|
|
4466
|
-
error(`Branch ${
|
|
4724
|
+
error(`Branch ${pc17.bold(newBranchName)} already exists. Choose a different name.`);
|
|
4467
4725
|
process.exit(1);
|
|
4468
4726
|
}
|
|
4469
4727
|
const renameResult = await renameBranch(currentBranch, newBranchName);
|
|
@@ -4471,7 +4729,7 @@ var update_default = defineCommand12({
|
|
|
4471
4729
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
4472
4730
|
process.exit(1);
|
|
4473
4731
|
}
|
|
4474
|
-
success(`Renamed ${
|
|
4732
|
+
success(`Renamed ${pc17.bold(currentBranch)} → ${pc17.bold(newBranchName)}`);
|
|
4475
4733
|
await unsetUpstream();
|
|
4476
4734
|
await fetchRemote(syncSource.remote);
|
|
4477
4735
|
let rebaseResult2;
|
|
@@ -4483,11 +4741,11 @@ var update_default = defineCommand12({
|
|
|
4483
4741
|
}
|
|
4484
4742
|
if (rebaseResult2.exitCode !== 0) {
|
|
4485
4743
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
4486
|
-
info(` ${
|
|
4744
|
+
info(` ${pc17.bold("git rebase --continue")}`);
|
|
4487
4745
|
} else {
|
|
4488
|
-
success(`Rebased ${
|
|
4746
|
+
success(`Rebased ${pc17.bold(newBranchName)} onto ${pc17.bold(syncSource.ref)}.`);
|
|
4489
4747
|
}
|
|
4490
|
-
info(`All your changes are preserved. Run ${
|
|
4748
|
+
info(`All your changes are preserved. Run ${pc17.bold("contrib submit")} when ready to create a new PR.`);
|
|
4491
4749
|
return;
|
|
4492
4750
|
}
|
|
4493
4751
|
warn("Discarding local changes...");
|
|
@@ -4500,24 +4758,24 @@ var update_default = defineCommand12({
|
|
|
4500
4758
|
process.exit(1);
|
|
4501
4759
|
}
|
|
4502
4760
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
4503
|
-
success(`Synced ${
|
|
4504
|
-
info(`Deleting stale branch ${
|
|
4761
|
+
success(`Synced ${pc17.bold(baseBranch)} with ${pc17.bold(syncSource.ref)}.`);
|
|
4762
|
+
info(`Deleting stale branch ${pc17.bold(currentBranch)}...`);
|
|
4505
4763
|
await forceDeleteBranch(currentBranch);
|
|
4506
|
-
success(`Deleted ${
|
|
4507
|
-
info(`Run ${
|
|
4764
|
+
success(`Deleted ${pc17.bold(currentBranch)}.`);
|
|
4765
|
+
info(`Run ${pc17.bold("contrib start")} to begin a new feature branch.`);
|
|
4508
4766
|
return;
|
|
4509
4767
|
}
|
|
4510
|
-
info(`Updating ${
|
|
4768
|
+
info(`Updating ${pc17.bold(currentBranch)} with latest ${pc17.bold(baseBranch)}...`);
|
|
4511
4769
|
await fetchRemote(syncSource.remote);
|
|
4512
4770
|
if (!await refExists(syncSource.ref)) {
|
|
4513
|
-
error(`Remote ref ${
|
|
4771
|
+
error(`Remote ref ${pc17.bold(syncSource.ref)} does not exist.`);
|
|
4514
4772
|
error("Run `git fetch --all` and verify your remote configuration.");
|
|
4515
4773
|
process.exit(1);
|
|
4516
4774
|
}
|
|
4517
4775
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
4518
4776
|
const rebaseStrategy = await determineRebaseStrategy(currentBranch, syncSource.ref);
|
|
4519
4777
|
if (rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase) {
|
|
4520
|
-
info(
|
|
4778
|
+
info(pc17.dim(`Using --onto rebase (branch was based on a different ref)`));
|
|
4521
4779
|
}
|
|
4522
4780
|
const rebaseResult = rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase ? await rebaseOnto(syncSource.ref, rebaseStrategy.ontoOldBase) : await rebase(syncSource.ref);
|
|
4523
4781
|
if (rebaseResult.exitCode !== 0) {
|
|
@@ -4546,10 +4804,10 @@ ${content.slice(0, 2000)}
|
|
|
4546
4804
|
if (suggestion) {
|
|
4547
4805
|
spinner.success("AI conflict guidance ready.");
|
|
4548
4806
|
console.log(`
|
|
4549
|
-
${
|
|
4550
|
-
console.log(
|
|
4807
|
+
${pc17.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
4808
|
+
console.log(pc17.dim("─".repeat(60)));
|
|
4551
4809
|
console.log(suggestion);
|
|
4552
|
-
console.log(
|
|
4810
|
+
console.log(pc17.dim("─".repeat(60)));
|
|
4553
4811
|
console.log();
|
|
4554
4812
|
} else {
|
|
4555
4813
|
spinner.fail("AI could not analyze the conflicts.");
|
|
@@ -4557,22 +4815,22 @@ ${pc15.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
|
4557
4815
|
}
|
|
4558
4816
|
}
|
|
4559
4817
|
}
|
|
4560
|
-
console.log(
|
|
4818
|
+
console.log(pc17.bold("To resolve:"));
|
|
4561
4819
|
console.log(` 1. Fix conflicts in the affected files`);
|
|
4562
|
-
console.log(` 2. ${
|
|
4563
|
-
console.log(` 3. ${
|
|
4820
|
+
console.log(` 2. ${pc17.cyan("git add <resolved-files>")}`);
|
|
4821
|
+
console.log(` 3. ${pc17.cyan("git rebase --continue")}`);
|
|
4564
4822
|
console.log();
|
|
4565
|
-
console.log(` Or abort: ${
|
|
4823
|
+
console.log(` Or abort: ${pc17.cyan("git rebase --abort")}`);
|
|
4566
4824
|
process.exit(1);
|
|
4567
4825
|
}
|
|
4568
|
-
success(`✅ ${
|
|
4826
|
+
success(`✅ ${pc17.bold(currentBranch)} has been rebased onto latest ${pc17.bold(baseBranch)}`);
|
|
4569
4827
|
}
|
|
4570
4828
|
});
|
|
4571
4829
|
|
|
4572
4830
|
// src/commands/validate.ts
|
|
4573
|
-
import { defineCommand as
|
|
4574
|
-
import
|
|
4575
|
-
var validate_default =
|
|
4831
|
+
import { defineCommand as defineCommand15 } from "citty";
|
|
4832
|
+
import pc18 from "picocolors";
|
|
4833
|
+
var validate_default = defineCommand15({
|
|
4576
4834
|
meta: {
|
|
4577
4835
|
name: "validate",
|
|
4578
4836
|
description: "Validate a commit message against the configured convention"
|
|
@@ -4602,7 +4860,7 @@ var validate_default = defineCommand13({
|
|
|
4602
4860
|
}
|
|
4603
4861
|
const errors = getValidationError(convention);
|
|
4604
4862
|
for (const line of errors) {
|
|
4605
|
-
console.error(
|
|
4863
|
+
console.error(pc18.red(` ✗ ${line}`));
|
|
4606
4864
|
}
|
|
4607
4865
|
process.exit(1);
|
|
4608
4866
|
}
|
|
@@ -4610,7 +4868,7 @@ var validate_default = defineCommand13({
|
|
|
4610
4868
|
|
|
4611
4869
|
// src/ui/banner.ts
|
|
4612
4870
|
import figlet from "figlet";
|
|
4613
|
-
import
|
|
4871
|
+
import pc19 from "picocolors";
|
|
4614
4872
|
var LOGO_BIG;
|
|
4615
4873
|
try {
|
|
4616
4874
|
LOGO_BIG = figlet.textSync(`Contribute
|
|
@@ -4632,14 +4890,14 @@ function getAuthor() {
|
|
|
4632
4890
|
}
|
|
4633
4891
|
function showBanner(variant = "small") {
|
|
4634
4892
|
const logo = variant === "big" ? LOGO_BIG : LOGO_SMALL;
|
|
4635
|
-
console.log(
|
|
4893
|
+
console.log(pc19.cyan(`
|
|
4636
4894
|
${logo}`));
|
|
4637
|
-
console.log(` ${
|
|
4895
|
+
console.log(` ${pc19.dim(`v${getVersion()}`)} ${pc19.dim("—")} ${pc19.dim(`Built by ${getAuthor()}`)}`);
|
|
4638
4896
|
if (variant === "big") {
|
|
4639
4897
|
console.log();
|
|
4640
|
-
console.log(` ${
|
|
4641
|
-
console.log(` ${
|
|
4642
|
-
console.log(` ${
|
|
4898
|
+
console.log(` ${pc19.yellow("Star")} ${pc19.cyan("https://github.com/warengonzaga/contribute-now")}`);
|
|
4899
|
+
console.log(` ${pc19.green("Contribute")} ${pc19.cyan("https://github.com/warengonzaga/contribute-now/blob/main/CONTRIBUTING.md")}`);
|
|
4900
|
+
console.log(` ${pc19.magenta("Sponsor")} ${pc19.cyan("https://warengonzaga.com/sponsor")}`);
|
|
4643
4901
|
}
|
|
4644
4902
|
console.log();
|
|
4645
4903
|
}
|
|
@@ -4654,6 +4912,8 @@ if (!isVersion) {
|
|
|
4654
4912
|
"commit",
|
|
4655
4913
|
"update",
|
|
4656
4914
|
"submit",
|
|
4915
|
+
"switch",
|
|
4916
|
+
"save",
|
|
4657
4917
|
"clean",
|
|
4658
4918
|
"status",
|
|
4659
4919
|
"log",
|
|
@@ -4667,7 +4927,7 @@ if (!isVersion) {
|
|
|
4667
4927
|
const useBigBanner = isHelp || !hasSubCommand;
|
|
4668
4928
|
showBanner(useBigBanner ? "big" : "small");
|
|
4669
4929
|
}
|
|
4670
|
-
var main =
|
|
4930
|
+
var main = defineCommand16({
|
|
4671
4931
|
meta: {
|
|
4672
4932
|
name: "contrib",
|
|
4673
4933
|
version: getVersion(),
|
|
@@ -4687,6 +4947,8 @@ var main = defineCommand14({
|
|
|
4687
4947
|
commit: commit_default,
|
|
4688
4948
|
update: update_default,
|
|
4689
4949
|
submit: submit_default,
|
|
4950
|
+
switch: switch_default,
|
|
4951
|
+
save: save_default,
|
|
4690
4952
|
branch: branch_default,
|
|
4691
4953
|
clean: clean_default,
|
|
4692
4954
|
status: status_default,
|