@tertium/gith 0.1.1 → 0.3.0
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 +12 -1
- package/bin/gith.js +409 -87
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -35,10 +35,16 @@ gith create -r 1.2.0 # release/1.2.0 from develop
|
|
|
35
35
|
gith create -h 1.2.1 # hotfix/1.2.1 from main
|
|
36
36
|
gith create --release # release, version = minor after the latest tag
|
|
37
37
|
gith create --hotfix # hotfix, version = patch after the latest tag
|
|
38
|
+
gith switch # list local and origin branches, and switch to the one you pick
|
|
38
39
|
gith switch -b login-redirect # switch to the branch, creating it from develop if it is missing
|
|
39
40
|
gith switch -commit 3f2a1b9 # detach HEAD at that commit, to look at old code
|
|
41
|
+
gith switch --release # switch to the newest release/* (local or origin); -r 1.9.0 for one version
|
|
42
|
+
gith switch --hotfix # switch to the newest hotfix/* (local or origin); -h 1.2.1 for one version
|
|
40
43
|
gith update # merge origin/develop into this work branch
|
|
41
44
|
gith merge # this branch into develop
|
|
45
|
+
gith finish --current-release # release the newest release/*: version, changelog, merge to main, tag, merge main to develop, then ask before deleting the branch
|
|
46
|
+
gith finish --current-hotfix # the same for the newest hotfix/*
|
|
47
|
+
gith merge # pick the branch to merge, then the target from the list
|
|
42
48
|
gith merge -r 1.2.0 # this branch into release/1.2.0
|
|
43
49
|
gith merge -h 1.2.1 # this branch into hotfix/1.2.1
|
|
44
50
|
gith merge --current-release # this branch into the newest release/* (local or origin)
|
|
@@ -59,6 +65,7 @@ gith reset # discard uncommitted changes, then clean
|
|
|
59
65
|
gith clean # remove untracked files and directories
|
|
60
66
|
|
|
61
67
|
# Clean up branches (every removal shows what it will remove first)
|
|
68
|
+
gith remove # pick branches to delete from the list (here and on origin)
|
|
62
69
|
gith remove -b login-redirect # delete the branch here and on origin (rm is an alias)
|
|
63
70
|
gith remove -b login-redirect --local # delete only the local branch
|
|
64
71
|
gith remove -b login-redirect --remote # delete only the origin branch
|
|
@@ -93,6 +100,8 @@ gith stat --last-week -limit 50 # the same for last week, looking at no more
|
|
|
93
100
|
gith sync # fetch all remotes and tags, prune stale refs
|
|
94
101
|
gith open # open origin in the browser
|
|
95
102
|
gith doctor # check the repo is ready for the branch flow
|
|
103
|
+
gith changelog --dry-run # preview the CHANGELOG.md entry for package.json's version
|
|
104
|
+
gith changelog # write it as its own commit (docs(changelog): <version>); --no-commit only writes
|
|
96
105
|
|
|
97
106
|
# Help
|
|
98
107
|
gith help # full usage
|
|
@@ -139,7 +148,7 @@ Commands that choose between flags (for example `create -b`, `-r` and `-h`, or `
|
|
|
139
148
|
- **`switch -b`** switches to a branch. If the branch exists neither locally nor on origin, it is created first, the same way as `create -b`, and then checked out.
|
|
140
149
|
- **`switch -commit <sha>`** detaches HEAD at that commit so you can look at old code. It refuses a commit that does not exist, and it refuses while tracked files have changes (untracked files do not block it). It lists the files that block it, and points to `gith stash`. On a detached HEAD, `gith switch -b <name>` creates the branch at the current commit, so commits made there stay on it. It does not create or switch branches for other commits, so commits made on a detached HEAD are easy to lose: name a branch before you leave.
|
|
141
150
|
- **`remove -b <branch>`** removes the branch on both sides: the local branch first (with `git branch -d`, so git refuses to delete an unmerged branch), then the origin branch. A side that does not exist is skipped, so a branch that lives only here is still removed. A name that exists in neither place is an error. `--local` touches only this clone; `--remote` touches only origin. Containers (`develop`, `main`, `release/*`, `hotfix/*`) are refused.
|
|
142
|
-
- **`remove
|
|
151
|
+
- **`remove`** with no name opens the list of branches and asks which to remove, the same as `remove --branch`. By default it lists branch pairs, meaning branches that exist both here and on origin. `--local` lists local branches and `--remote` lists origin branches. Each choice is removed on the same sides as above. `-limit <n>` shows only the first `n` (all by default), and the picker numbers only those. Pick with the arrow keys: up and down move, space checks an item, enter confirms the checked items, and esc cancels. `switch` takes one item with enter. The list needs a terminal, and that is checked before anything is listed. The checked-out branch and containers are never listed. Choosing items is the confirmation, so nothing else is asked. If a delete fails (for example, an unmerged branch), the removal stops there and earlier picks stay deleted.
|
|
143
152
|
- **`status`** prints the branch and how far it is ahead of or behind its origin branch, then counts staged, unstaged, untracked and conflicting files. It changes nothing.
|
|
144
153
|
- **`update`** fetches `origin/develop` and merges it into the current work branch. It needs a clean working tree and refuses containers. If the merge conflicts, it aborts the merge and says so. It does not push.
|
|
145
154
|
- **`undo`** runs `git reset --soft HEAD~1`: the last commit is removed and its changes stay staged. It refuses when HEAD is already on origin, because undoing a pushed commit needs a force-push.
|
|
@@ -174,6 +183,8 @@ Commands that choose between flags (for example `create -b`, `-r` and `-h`, or `
|
|
|
174
183
|
- **`create -r` / `create -h`** creates a container from its base (`develop` for release, `main` for hotfix), refusing names that already exist on origin. Like `create -b`, it does not switch.
|
|
175
184
|
- **With `--release` or `--hotfix` (no value)**, the version is worked out from the highest `vX.Y.Z` tag across local tags and origin: `-r` takes the next minor (`1.2.1` → `1.3.0`), `-h` takes the next patch (`1.2.1` → `1.2.2`). With no tags yet, the first release is `0.1.0` and the first hotfix `0.0.1`. The version is printed before the branch is created.
|
|
176
185
|
- **`merge --current-release`** and **`--current-hotfix`** pick the newest `release/*` or `hotfix/*` branch by version (numbers compare as numbers, so `1.10.0` beats `1.9.0`), checking both local branches and origin, and merge into it. The target must exist on origin, because the merge pushes it; a newest branch that only exists here stops the command with a message.
|
|
186
|
+
- **`finish --current-release`** (and `--current-hotfix`) releases the newest `release/*` (or `hotfix/*`), named by its version X.Y.Z. It makes `package.json`'s version match the name (committed as `X.Y.Z`, only when they differ), adds the changelog entry as its own commit (when it is missing), merges the branch into `main` with `--no-ff`, tags `vX.Y.Z` and pushes, merges `main` into `develop` when `develop` exists on origin, and then asks before deleting the branch. A conflict aborts the merge and stops. Projects without a `package.json` keep their version by hand.
|
|
187
|
+
- **`merge`** with no flags asks which branch to merge and which target to merge it into (develop, release/*, hotfix/*), with the arrow keys. After a merge into a target, the work branch is deleted here and on origin, after a Y/n question. Without a terminal, or on a no, it is kept. Release and hotfix branches are never deleted this way.
|
|
177
188
|
- **`merge`** pushes the work branch, merges the target into it first (so conflicts happen on the work branch and are aborted cleanly), then merges it into the target with `--no-ff`, pushes, and checks that both branches match origin.
|
|
178
189
|
|
|
179
190
|
Containers (`develop`, `main`, `release/*`, `hotfix/*`) can't be merged from, and only `develop`, `release/*` and `hotfix/*` can be merged into.
|
package/bin/gith.js
CHANGED
|
@@ -28,9 +28,11 @@ var SPECS = new Map([
|
|
|
28
28
|
["stashes", "switch"],
|
|
29
29
|
["current-release", "switch"],
|
|
30
30
|
["current-hotfix", "switch"],
|
|
31
|
+
["develop", "switch"],
|
|
31
32
|
["pockets", "switch"],
|
|
32
33
|
["detached", "switch"],
|
|
33
34
|
["all", "switch"],
|
|
35
|
+
["no-commit", "switch"],
|
|
34
36
|
["overall", "switch"],
|
|
35
37
|
["current-year", "switch"],
|
|
36
38
|
["current-month", "switch"],
|
|
@@ -119,6 +121,10 @@ function onlyOne(flags, names) {
|
|
|
119
121
|
return present[0];
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
// src/components/changelog/changelog.ts
|
|
125
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
126
|
+
import path from "node:path";
|
|
127
|
+
|
|
122
128
|
// src/components/git/git.ts
|
|
123
129
|
import { execFileSync } from "node:child_process";
|
|
124
130
|
var verbose = false;
|
|
@@ -216,6 +222,92 @@ function act(args, options = {}) {
|
|
|
216
222
|
execFileSync("git", args, { stdio: "inherit", ...options });
|
|
217
223
|
}
|
|
218
224
|
|
|
225
|
+
// src/components/changelog/changelog.constants.ts
|
|
226
|
+
var CHANGELOG_FILE_NAME = "CHANGELOG.md";
|
|
227
|
+
var CHANGELOG_TITLE = "# Changelog";
|
|
228
|
+
var SEMVER_ONLY_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
229
|
+
|
|
230
|
+
// src/components/changelog/changelog.utils.ts
|
|
231
|
+
function filterCommitSubjects(rawLog) {
|
|
232
|
+
if (!rawLog.trim())
|
|
233
|
+
return [];
|
|
234
|
+
return rawLog.split(`
|
|
235
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0 && !SEMVER_ONLY_PATTERN.test(line));
|
|
236
|
+
}
|
|
237
|
+
function buildEntry(notes) {
|
|
238
|
+
const bullets = notes.subjects.length > 0 ? notes.subjects.map((subject) => `- ${subject}`).join(`
|
|
239
|
+
`) : "- (no changes recorded)";
|
|
240
|
+
return `## ${notes.version} - ${notes.date}
|
|
241
|
+
|
|
242
|
+
${bullets}
|
|
243
|
+
`;
|
|
244
|
+
}
|
|
245
|
+
function mergeChangelogContent(existing, entry) {
|
|
246
|
+
const heading = `${CHANGELOG_TITLE}
|
|
247
|
+
`;
|
|
248
|
+
if (existing === null)
|
|
249
|
+
return `${heading}
|
|
250
|
+
${entry}
|
|
251
|
+
`;
|
|
252
|
+
const body = existing.startsWith(heading) ? existing.slice(heading.length).replace(/^\n+/, "") : existing;
|
|
253
|
+
return `${heading}
|
|
254
|
+
${entry}
|
|
255
|
+
${body}`;
|
|
256
|
+
}
|
|
257
|
+
function parsePackageVersion(rawPackageJson) {
|
|
258
|
+
const pkg = JSON.parse(rawPackageJson);
|
|
259
|
+
if (!pkg.version)
|
|
260
|
+
throw new Error('No "version" field found in package.json');
|
|
261
|
+
return pkg.version;
|
|
262
|
+
}
|
|
263
|
+
function todayIso(date = new Date) {
|
|
264
|
+
return date.toISOString().slice(0, 10);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/components/changelog/changelog.ts
|
|
268
|
+
function previousTag() {
|
|
269
|
+
try {
|
|
270
|
+
return captureArgs(["describe", "--tags", "--abbrev=0"]);
|
|
271
|
+
} catch {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function commitSubjectsSince(tag) {
|
|
276
|
+
const range = tag ? `${tag}..HEAD` : "HEAD";
|
|
277
|
+
return filterCommitSubjects(captureArgs(["log", range, "--no-merges", "--pretty=format:%s"]));
|
|
278
|
+
}
|
|
279
|
+
function changelog(flags) {
|
|
280
|
+
const noCommit = flags.has("no-commit");
|
|
281
|
+
if (flags.size > (noCommit ? 1 : 0))
|
|
282
|
+
throw new Error("usage: gith changelog [--dry-run] [--no-commit]");
|
|
283
|
+
const root = process.cwd();
|
|
284
|
+
const version = parsePackageVersion(readFileSync(path.join(root, "package.json"), "utf8"));
|
|
285
|
+
const entry = buildEntry({
|
|
286
|
+
version,
|
|
287
|
+
date: todayIso(),
|
|
288
|
+
subjects: commitSubjectsSince(previousTag())
|
|
289
|
+
});
|
|
290
|
+
if (isDryRun()) {
|
|
291
|
+
console.log(entry);
|
|
292
|
+
console.log(`dry run: would update ${CHANGELOG_FILE_NAME}`);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const file = path.join(root, CHANGELOG_FILE_NAME);
|
|
296
|
+
const existing = existsSync(file) ? readFileSync(file, "utf8") : null;
|
|
297
|
+
writeFileSync(file, mergeChangelogContent(existing, entry));
|
|
298
|
+
console.log(`✅ ${CHANGELOG_FILE_NAME} updated for ${version}`);
|
|
299
|
+
if (noCommit)
|
|
300
|
+
return;
|
|
301
|
+
gitArgs(["add", "--", CHANGELOG_FILE_NAME]);
|
|
302
|
+
gitArgs([
|
|
303
|
+
"commit",
|
|
304
|
+
"-m",
|
|
305
|
+
`docs(changelog): ${version}`,
|
|
306
|
+
"--",
|
|
307
|
+
CHANGELOG_FILE_NAME
|
|
308
|
+
]);
|
|
309
|
+
}
|
|
310
|
+
|
|
219
311
|
// src/components/doctor/doctor.utils.ts
|
|
220
312
|
function check(name, test, failure = "fail") {
|
|
221
313
|
return {
|
|
@@ -336,6 +428,124 @@ function find(flags) {
|
|
|
336
428
|
`));
|
|
337
429
|
}
|
|
338
430
|
|
|
431
|
+
// src/components/flow/flow.ts
|
|
432
|
+
import { execSync } from "node:child_process";
|
|
433
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
434
|
+
|
|
435
|
+
// src/components/prompt/prompt.ts
|
|
436
|
+
import { emitKeypressEvents } from "node:readline";
|
|
437
|
+
import { createInterface } from "node:readline/promises";
|
|
438
|
+
|
|
439
|
+
// src/components/prompt/prompt.utils.ts
|
|
440
|
+
function moveIndex(index, delta, length) {
|
|
441
|
+
if (length <= 0)
|
|
442
|
+
return 0;
|
|
443
|
+
return ((index + delta) % length + length) % length;
|
|
444
|
+
}
|
|
445
|
+
function toggled(checked, index) {
|
|
446
|
+
const next = new Set(checked);
|
|
447
|
+
if (next.has(index))
|
|
448
|
+
next.delete(index);
|
|
449
|
+
else
|
|
450
|
+
next.add(index);
|
|
451
|
+
return next;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/components/prompt/prompt.ts
|
|
455
|
+
var assumeYes = false;
|
|
456
|
+
function setAssumeYes(on) {
|
|
457
|
+
assumeYes = on;
|
|
458
|
+
}
|
|
459
|
+
async function confirm(question) {
|
|
460
|
+
if (isDryRun()) {
|
|
461
|
+
console.log(`dry run: would ask: ${question}`);
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
if (assumeYes)
|
|
465
|
+
return true;
|
|
466
|
+
if (!process.stdin.isTTY)
|
|
467
|
+
throw new Error("this needs a terminal to confirm; pass --yes to skip the question.");
|
|
468
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
469
|
+
try {
|
|
470
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
471
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
472
|
+
} finally {
|
|
473
|
+
rl.close();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
async function ask(question) {
|
|
477
|
+
if (isDryRun()) {
|
|
478
|
+
console.log(`dry run: would ask: ${question}`);
|
|
479
|
+
return "<message>";
|
|
480
|
+
}
|
|
481
|
+
if (!process.stdin.isTTY)
|
|
482
|
+
throw new Error("needs a terminal to ask; pass -m <message>.");
|
|
483
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
484
|
+
try {
|
|
485
|
+
return (await rl.question(question)).trim();
|
|
486
|
+
} finally {
|
|
487
|
+
rl.close();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
async function select(question, options, multi) {
|
|
491
|
+
if (!process.stdin.isTTY)
|
|
492
|
+
throw new Error("this needs a terminal to pick.");
|
|
493
|
+
return new Promise((resolve) => {
|
|
494
|
+
const stdin = process.stdin;
|
|
495
|
+
let index = 0;
|
|
496
|
+
let checked = new Set;
|
|
497
|
+
let drawnLines = 0;
|
|
498
|
+
const draw = () => {
|
|
499
|
+
if (drawnLines > 0)
|
|
500
|
+
process.stdout.write(`\x1B[${drawnLines}A\x1B[0J`);
|
|
501
|
+
const hint = multi ? "space checks, enter confirms, esc cancels" : "enter selects, esc cancels";
|
|
502
|
+
const lines = [`${question} (${hint})`];
|
|
503
|
+
options.forEach((option, i) => {
|
|
504
|
+
const cursor = i === index ? ">" : " ";
|
|
505
|
+
const mark = multi ? checked.has(i) ? "[x]" : "[ ]" : "";
|
|
506
|
+
lines.push(`${cursor} ${mark} ${option.label}`.replace(/ +$/, ""));
|
|
507
|
+
});
|
|
508
|
+
process.stdout.write(`${lines.join(`
|
|
509
|
+
`)}
|
|
510
|
+
`);
|
|
511
|
+
drawnLines = lines.length;
|
|
512
|
+
};
|
|
513
|
+
const finish = (picked) => {
|
|
514
|
+
stdin.off("keypress", onKey);
|
|
515
|
+
stdin.setRawMode(false);
|
|
516
|
+
stdin.pause();
|
|
517
|
+
process.stdout.write(`
|
|
518
|
+
`);
|
|
519
|
+
resolve(picked);
|
|
520
|
+
};
|
|
521
|
+
const onKey = (_text, key) => {
|
|
522
|
+
if (!key)
|
|
523
|
+
return;
|
|
524
|
+
if (key.ctrl && key.name === "c" || key.name === "escape") {
|
|
525
|
+
finish([]);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (key.name === "return") {
|
|
529
|
+
const picked = multi ? [...checked].sort((a, b) => a - b).map((i) => options[i]?.name ?? "") : [options[index]?.name ?? ""];
|
|
530
|
+
finish(picked.filter(Boolean));
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
if (key.name === "up")
|
|
534
|
+
index = moveIndex(index, -1, options.length);
|
|
535
|
+
else if (key.name === "down")
|
|
536
|
+
index = moveIndex(index, 1, options.length);
|
|
537
|
+
else if (key.name === "space" && multi)
|
|
538
|
+
checked = toggled(checked, index);
|
|
539
|
+
draw();
|
|
540
|
+
};
|
|
541
|
+
emitKeypressEvents(stdin);
|
|
542
|
+
stdin.setRawMode(true);
|
|
543
|
+
stdin.resume();
|
|
544
|
+
stdin.on("keypress", onKey);
|
|
545
|
+
draw();
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
339
549
|
// src/components/rules/rules.constants.ts
|
|
340
550
|
var CONTAINER = /^(develop|main|release\/.+|hotfix\/.+)$/;
|
|
341
551
|
var TARGET = /^(develop|release\/.+|hotfix\/.+)$/;
|
|
@@ -443,15 +653,20 @@ function create(flags) {
|
|
|
443
653
|
}
|
|
444
654
|
open(kind, value || nextContainerVersion(kind));
|
|
445
655
|
}
|
|
446
|
-
function merge(flags) {
|
|
656
|
+
async function merge(flags) {
|
|
657
|
+
if (flags.size === 0)
|
|
658
|
+
return mergePick();
|
|
447
659
|
const kind = onlyOne(flags, [
|
|
660
|
+
"develop",
|
|
448
661
|
"release",
|
|
449
662
|
"hotfix",
|
|
450
663
|
"current-release",
|
|
451
664
|
"current-hotfix"
|
|
452
665
|
]);
|
|
453
666
|
let target = "develop";
|
|
454
|
-
if (kind === "
|
|
667
|
+
if (kind === "develop")
|
|
668
|
+
target = "develop";
|
|
669
|
+
else if (kind === "current-release")
|
|
455
670
|
target = newestContainer("release");
|
|
456
671
|
else if (kind === "current-hotfix")
|
|
457
672
|
target = newestContainer("hotfix");
|
|
@@ -462,7 +677,7 @@ function merge(flags) {
|
|
|
462
677
|
}
|
|
463
678
|
target = containerName(kind, version);
|
|
464
679
|
}
|
|
465
|
-
mergeInto(target);
|
|
680
|
+
await mergeInto(target);
|
|
466
681
|
}
|
|
467
682
|
function createBranch(goal, from) {
|
|
468
683
|
if (isContainer(goal))
|
|
@@ -477,10 +692,11 @@ function createBranch(goal, from) {
|
|
|
477
692
|
}
|
|
478
693
|
gitArgs(["push", "-u", "origin", goal]);
|
|
479
694
|
}
|
|
480
|
-
function mergeInto(target) {
|
|
695
|
+
async function mergeInto(target, from) {
|
|
481
696
|
if (!isTarget(target))
|
|
482
697
|
throw new Error(`'${target}' is not a valid target.`);
|
|
483
|
-
const
|
|
698
|
+
const current = capture("rev-parse --abbrev-ref HEAD");
|
|
699
|
+
const source = from ?? current;
|
|
484
700
|
if (isContainer(source))
|
|
485
701
|
throw new Error(`'${source}' is a container and cannot be merged from.`);
|
|
486
702
|
if (source === target)
|
|
@@ -488,6 +704,8 @@ function mergeInto(target) {
|
|
|
488
704
|
if (!hasOnOrigin(target))
|
|
489
705
|
throw new Error(`'${target}' does not exist on origin.`);
|
|
490
706
|
assertCleanTree();
|
|
707
|
+
if (source !== current)
|
|
708
|
+
gitArgs(["switch", "--", source]);
|
|
491
709
|
if (hasOnOrigin(source))
|
|
492
710
|
git(`pull origin ${source}`);
|
|
493
711
|
git(`push -u origin ${source}`);
|
|
@@ -507,44 +725,116 @@ function mergeInto(target) {
|
|
|
507
725
|
assertSynced([target, source]);
|
|
508
726
|
console.log(`
|
|
509
727
|
✅ '${source}' merged into '${target}', both in sync with origin.`);
|
|
728
|
+
await deleteMergedBranch(source);
|
|
510
729
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
function setAssumeYes(on) {
|
|
516
|
-
assumeYes = on;
|
|
517
|
-
}
|
|
518
|
-
async function confirm(question) {
|
|
519
|
-
if (isDryRun()) {
|
|
520
|
-
console.log(`dry run: would ask: ${question}`);
|
|
521
|
-
return true;
|
|
522
|
-
}
|
|
523
|
-
if (assumeYes)
|
|
524
|
-
return true;
|
|
525
|
-
if (!process.stdin.isTTY)
|
|
526
|
-
throw new Error("this needs a terminal to confirm; pass --yes to skip the question.");
|
|
527
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
730
|
+
async function deleteMergedBranch(source) {
|
|
731
|
+
const local = hasLocal(source);
|
|
732
|
+
const origin = hasOnOrigin(source);
|
|
733
|
+
let yes;
|
|
528
734
|
try {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
735
|
+
yes = await confirm(`Delete '${source}' here and on origin, it is merged? [Y/n] `);
|
|
736
|
+
} catch {
|
|
737
|
+
yes = false;
|
|
738
|
+
}
|
|
739
|
+
if (!yes) {
|
|
740
|
+
console.log(`'${source}' kept.`);
|
|
741
|
+
return;
|
|
533
742
|
}
|
|
743
|
+
if (local)
|
|
744
|
+
gitArgs(["branch", "-d", "--", source]);
|
|
745
|
+
if (origin)
|
|
746
|
+
gitArgs(["push", "origin", "--delete", source]);
|
|
534
747
|
}
|
|
535
|
-
async function
|
|
748
|
+
async function mergePick() {
|
|
749
|
+
if (!process.stdin.isTTY) {
|
|
750
|
+
throw new Error("pass -r <version> or -h <version> instead; the pickers need a terminal");
|
|
751
|
+
}
|
|
752
|
+
const current = capture("rev-parse --abbrev-ref HEAD");
|
|
753
|
+
const local = captureArgs([
|
|
754
|
+
"for-each-ref",
|
|
755
|
+
"--format=%(refname:short)",
|
|
756
|
+
"refs/heads"
|
|
757
|
+
]).split(`
|
|
758
|
+
`).filter(Boolean);
|
|
759
|
+
const sources = local.filter((name) => !isContainer(name)).sort((a, b) => a.localeCompare(b)).map((name) => ({
|
|
760
|
+
name,
|
|
761
|
+
label: name === current ? `${name} (current)` : name
|
|
762
|
+
}));
|
|
763
|
+
if (sources.length === 0)
|
|
764
|
+
throw new Error("no work branch to merge.");
|
|
765
|
+
const [source] = await select("Which branch to merge?", sources, false);
|
|
766
|
+
if (source === undefined)
|
|
767
|
+
return;
|
|
768
|
+
const origin = captureArgs(["ls-remote", "--heads", "origin"]).split(`
|
|
769
|
+
`).map((line) => (line.split("\t")[1] ?? "").replace(/^refs\/heads\//, "")).filter(Boolean);
|
|
770
|
+
const targets = [...new Set([...local, ...origin])].filter((name) => isTarget(name) && name !== source).sort((a, b) => a.localeCompare(b)).map((name) => ({
|
|
771
|
+
name,
|
|
772
|
+
label: local.includes(name) ? name : `${name} (origin)`
|
|
773
|
+
}));
|
|
774
|
+
if (targets.length === 0)
|
|
775
|
+
throw new Error("no develop, release or hotfix branch to merge into.");
|
|
776
|
+
const [target] = await select(`Merge '${source}' into which?`, targets, false);
|
|
777
|
+
if (target === undefined)
|
|
778
|
+
return;
|
|
779
|
+
await mergeInto(target, source);
|
|
780
|
+
}
|
|
781
|
+
function alignVersion(version) {
|
|
782
|
+
if (!existsSync2("package.json"))
|
|
783
|
+
return;
|
|
784
|
+
const current = parsePackageVersion(readFileSync2("package.json", "utf8"));
|
|
785
|
+
if (current === version)
|
|
786
|
+
return;
|
|
536
787
|
if (isDryRun()) {
|
|
537
|
-
console.log(
|
|
538
|
-
|
|
788
|
+
console.log(`> npm version ${version} --no-git-tag-version --ignore-scripts`);
|
|
789
|
+
} else {
|
|
790
|
+
execSync(`npm version ${version} --no-git-tag-version --ignore-scripts`, {
|
|
791
|
+
stdio: "inherit"
|
|
792
|
+
});
|
|
539
793
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
794
|
+
gitArgs(["add", "--", "package.json"]);
|
|
795
|
+
gitArgs(["commit", "-m", version, "--", "package.json"]);
|
|
796
|
+
}
|
|
797
|
+
function addChangelogEntry(version) {
|
|
798
|
+
if (!existsSync2("package.json"))
|
|
799
|
+
return;
|
|
800
|
+
const file = "CHANGELOG.md";
|
|
801
|
+
if (existsSync2(file) && readFileSync2(file, "utf8").includes(`## ${version} - `))
|
|
802
|
+
return;
|
|
803
|
+
changelog(new Map);
|
|
804
|
+
}
|
|
805
|
+
async function finish(flags) {
|
|
806
|
+
const kind = onlyOne(flags, ["current-release", "current-hotfix"]);
|
|
807
|
+
if (!kind || flags.size > 1)
|
|
808
|
+
throw new Error("usage: gith finish --current-release | --current-hotfix");
|
|
809
|
+
const containerKind = kind === "current-release" ? "release" : "hotfix";
|
|
810
|
+
const branch = newestContainer(containerKind);
|
|
811
|
+
const version = branch.slice(containerKind.length + 1);
|
|
812
|
+
if (parseSemver(version) === undefined)
|
|
813
|
+
throw new Error(`'${branch}' is not named with a version (X.Y.Z).`);
|
|
814
|
+
assertCleanTree();
|
|
815
|
+
gitArgs(["switch", "--", branch]);
|
|
816
|
+
gitArgs(["pull", "--ff-only", "origin", branch]);
|
|
817
|
+
alignVersion(version);
|
|
818
|
+
addChangelogEntry(version);
|
|
819
|
+
gitArgs(["switch", "--", "main"]);
|
|
820
|
+
gitArgs(["pull", "--ff-only", "origin", "main"]);
|
|
543
821
|
try {
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
|
|
822
|
+
gitArgs(["merge", "--no-ff", branch, "-m", `Merge '${branch}' into main`]);
|
|
823
|
+
} catch {
|
|
824
|
+
gitArgs(["merge", "--abort"]);
|
|
825
|
+
throw new Error(`merging '${branch}' into main conflicts; resolve it by hand, then retry.`);
|
|
826
|
+
}
|
|
827
|
+
gitArgs(["tag", "-a", `v${version}`, "-m", `v${version}`]);
|
|
828
|
+
gitArgs(["push", "origin", "main"]);
|
|
829
|
+
gitArgs(["push", "origin", `v${version}`]);
|
|
830
|
+
if (hasOnOrigin("develop")) {
|
|
831
|
+
gitArgs(["switch", "--", "develop"]);
|
|
832
|
+
gitArgs(["pull", "--ff-only", "origin", "develop"]);
|
|
833
|
+
gitArgs(["merge", "--no-edit", "main"]);
|
|
834
|
+
gitArgs(["push", "origin", "develop"]);
|
|
547
835
|
}
|
|
836
|
+
gitArgs(["switch", "--", "main"]);
|
|
837
|
+
await deleteMergedBranch(branch);
|
|
548
838
|
}
|
|
549
839
|
|
|
550
840
|
// src/components/history/history.utils.ts
|
|
@@ -771,7 +1061,7 @@ function stashes(limit, withGorlum) {
|
|
|
771
1061
|
]).split(`
|
|
772
1062
|
`).filter(Boolean);
|
|
773
1063
|
if (rows.length === 0) {
|
|
774
|
-
console.log("No stashes.");
|
|
1064
|
+
console.log(withGorlum ? "There is nothing in my pockets!" : "No stashes.");
|
|
775
1065
|
return;
|
|
776
1066
|
}
|
|
777
1067
|
const shown = rows.slice(0, limit);
|
|
@@ -802,9 +1092,6 @@ function originTags() {
|
|
|
802
1092
|
`).filter(Boolean).map((line) => (line.split("\t")[1] ?? "").replace(/^refs\/tags\//, "")).filter((ref) => ref && !ref.endsWith("^{}"));
|
|
803
1093
|
}
|
|
804
1094
|
|
|
805
|
-
// src/components/maintenance/maintenance.ts
|
|
806
|
-
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
807
|
-
|
|
808
1095
|
// src/components/maintenance/maintenance.constants.ts
|
|
809
1096
|
var DEFAULT_DAYS = 90;
|
|
810
1097
|
|
|
@@ -853,7 +1140,21 @@ function parsePositive(flags, name) {
|
|
|
853
1140
|
}
|
|
854
1141
|
|
|
855
1142
|
// src/components/maintenance/maintenance.ts
|
|
856
|
-
function switchBranch(flags) {
|
|
1143
|
+
async function switchBranch(flags) {
|
|
1144
|
+
if (flags.size === 0)
|
|
1145
|
+
return switchPick();
|
|
1146
|
+
const kind = onlyOne(flags, ["release", "hotfix"]);
|
|
1147
|
+
if (kind !== undefined) {
|
|
1148
|
+
if (flags.size > 1)
|
|
1149
|
+
throw new Error(`usage: gith switch --${kind} | -${kind === "release" ? "r" : "h"} <version>`);
|
|
1150
|
+
const version = flags.get(kind) ?? "";
|
|
1151
|
+
const name = version === "" ? newestContainer(kind) : containerName(kind, version);
|
|
1152
|
+
if (!hasLocal(name) && !hasOnOrigin(name)) {
|
|
1153
|
+
throw new Error(`'${name}' does not exist, locally or on origin.`);
|
|
1154
|
+
}
|
|
1155
|
+
gitArgs(["switch", "--", name]);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
857
1158
|
const commit = flags.get("commit");
|
|
858
1159
|
if (commit !== undefined) {
|
|
859
1160
|
if (flags.has("branch") || flags.size > 1)
|
|
@@ -878,20 +1179,6 @@ function candidates(scope, local, remote, current) {
|
|
|
878
1179
|
}
|
|
879
1180
|
return local.filter((name) => notContainer(name) && notCurrent(name) && remote.includes(name)).map((name) => ({ name, label: `${name} (local + origin)` }));
|
|
880
1181
|
}
|
|
881
|
-
function parseSelection(answer, count) {
|
|
882
|
-
const indexes = new Set;
|
|
883
|
-
for (const part of answer.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
884
|
-
const range = /^(\d+)-(\d+)$/.exec(part);
|
|
885
|
-
const from = Number(range ? range[1] : part);
|
|
886
|
-
const to = Number(range ? range[2] : part);
|
|
887
|
-
if (!Number.isInteger(from) || !Number.isInteger(to) || from < 1 || to > count || from > to) {
|
|
888
|
-
throw new Error(`'${part}' is not a choice from 1 to ${count}.`);
|
|
889
|
-
}
|
|
890
|
-
for (let n = from;n <= to; n++)
|
|
891
|
-
indexes.add(n - 1);
|
|
892
|
-
}
|
|
893
|
-
return [...indexes];
|
|
894
|
-
}
|
|
895
1182
|
function branchTimes(output) {
|
|
896
1183
|
const times = new Map;
|
|
897
1184
|
for (const line of output.split(`
|
|
@@ -937,7 +1224,7 @@ function detachedOptions(times, current) {
|
|
|
937
1224
|
return options.sort((x, y) => x.name.localeCompare(y.name));
|
|
938
1225
|
}
|
|
939
1226
|
async function remove(flags) {
|
|
940
|
-
const name = flags.get("branch");
|
|
1227
|
+
const name = flags.get("branch") ?? "";
|
|
941
1228
|
const side = onlyOne(flags, ["local", "remote"]);
|
|
942
1229
|
const scope = side ?? "pair";
|
|
943
1230
|
const obsoleteOn = flags.has("obsolete");
|
|
@@ -950,14 +1237,12 @@ async function remove(flags) {
|
|
|
950
1237
|
throw new Error("--all only applies to --detached.");
|
|
951
1238
|
if (obsoleteOn || detachedOn) {
|
|
952
1239
|
const usage = detachedOn ? "usage: gith remove --detached [--all | -l <n>]" : "usage: gith remove --obsolete [--local] [-days <n>] [-l <n>]";
|
|
953
|
-
if (
|
|
1240
|
+
if (flags.has("branch") || obsoleteOn && side === "remote" || detachedOn && side)
|
|
954
1241
|
throw new Error(usage);
|
|
955
1242
|
if (detachedOn)
|
|
956
1243
|
return detached(flags);
|
|
957
1244
|
return obsolete(flags, side === "local" ? "local" : "pair");
|
|
958
1245
|
}
|
|
959
|
-
if (name === undefined)
|
|
960
|
-
throw new Error("usage: gith remove -b <branch> [--local | --remote], or gith remove --branch to pick");
|
|
961
1246
|
if (name) {
|
|
962
1247
|
if (flags.has("limit"))
|
|
963
1248
|
throw new Error("-limit only applies to listings: gith remove --branch.");
|
|
@@ -1012,25 +1297,49 @@ async function clean(flags) {
|
|
|
1012
1297
|
}
|
|
1013
1298
|
gitArgs(["clean", "-fd"]);
|
|
1014
1299
|
}
|
|
1015
|
-
|
|
1300
|
+
var REMOVE_ACTION = {
|
|
1301
|
+
empty: "No branches to remove.",
|
|
1302
|
+
prompt: "Remove which?",
|
|
1303
|
+
multi: true
|
|
1304
|
+
};
|
|
1305
|
+
async function choose(options, limit, action = REMOVE_ACTION) {
|
|
1016
1306
|
if (options.length === 0) {
|
|
1017
|
-
console.log(
|
|
1307
|
+
console.log(action.empty);
|
|
1018
1308
|
return [];
|
|
1019
1309
|
}
|
|
1020
1310
|
const shown = limit === undefined ? options : options.slice(0, limit);
|
|
1021
|
-
for (const [i, option] of shown.entries()) {
|
|
1022
|
-
console.log(` ${i + 1}. ${option.label}`);
|
|
1023
|
-
}
|
|
1024
1311
|
if (shown.length < options.length) {
|
|
1025
1312
|
console.log(`showing ${shown.length} of ${options.length}; -limit <n> shows more`);
|
|
1026
1313
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1314
|
+
return select(action.prompt, shown, action.multi);
|
|
1315
|
+
}
|
|
1316
|
+
function switchOptions() {
|
|
1317
|
+
const local = localBranches();
|
|
1318
|
+
const origin = remoteBranches();
|
|
1319
|
+
const current = currentBranch2();
|
|
1320
|
+
const names = [...new Set([...local, ...origin])].sort((a, b) => a.localeCompare(b));
|
|
1321
|
+
return names.map((name) => {
|
|
1322
|
+
const where = local.includes(name) && origin.includes(name) ? "local + origin" : local.includes(name) ? "local" : "origin";
|
|
1323
|
+
return {
|
|
1324
|
+
name,
|
|
1325
|
+
label: `${name} (${where}${name === current ? ", current" : ""})`
|
|
1326
|
+
};
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
async function switchPick() {
|
|
1330
|
+
if (!process.stdin.isTTY) {
|
|
1331
|
+
throw new Error("pass a branch name with -b, e.g. gith switch -b <branch>; the list needs a terminal");
|
|
1033
1332
|
}
|
|
1333
|
+
const picked = await choose(switchOptions(), undefined, {
|
|
1334
|
+
empty: "No branches.",
|
|
1335
|
+
prompt: "Switch to which?",
|
|
1336
|
+
multi: false
|
|
1337
|
+
});
|
|
1338
|
+
if (picked.length === 0)
|
|
1339
|
+
return;
|
|
1340
|
+
if (picked.length > 1)
|
|
1341
|
+
throw new Error("pick exactly one branch to switch to.");
|
|
1342
|
+
gitArgs(["switch", "--", picked[0] ?? ""]);
|
|
1034
1343
|
}
|
|
1035
1344
|
function requireTerminal() {
|
|
1036
1345
|
if (!process.stdin.isTTY) {
|
|
@@ -1056,14 +1365,14 @@ async function removeBranches(scope, names, ask = true) {
|
|
|
1056
1365
|
});
|
|
1057
1366
|
if (plan.length > 0)
|
|
1058
1367
|
console.log("Will remove:");
|
|
1059
|
-
if (ask && plan.length > 0 && !await confirm(plan.length === 1 ? "Remove this branch? [Y/n] " : `Remove these ${plan.length} branches? [Y/n] `)) {
|
|
1060
|
-
console.log("Nothing removed.");
|
|
1061
|
-
return;
|
|
1062
|
-
}
|
|
1063
1368
|
for (const { name, local, origin } of plan) {
|
|
1064
1369
|
const sides = [local && "local", origin && "origin"].filter(Boolean).join(" + ");
|
|
1065
1370
|
console.log(` ${name} (${sides})`);
|
|
1066
1371
|
}
|
|
1372
|
+
if (ask && plan.length > 0 && !await confirm(plan.length === 1 ? "Remove this branch? [Y/n] " : `Remove these ${plan.length} branches? [Y/n] `)) {
|
|
1373
|
+
console.log("Nothing removed.");
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1067
1376
|
for (const { name, local, origin } of plan) {
|
|
1068
1377
|
if (local)
|
|
1069
1378
|
gitArgs(["branch", "-d", "--", name]);
|
|
@@ -1173,12 +1482,12 @@ function openerFor(platform, url) {
|
|
|
1173
1482
|
var KINDS = ["patch", "minor", "major"];
|
|
1174
1483
|
|
|
1175
1484
|
// src/components/release/release.utils.ts
|
|
1176
|
-
import { execSync } from "node:child_process";
|
|
1485
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
1177
1486
|
function npmVersion(kind) {
|
|
1178
1487
|
console.log(`> npm version ${kind}`);
|
|
1179
1488
|
if (isDryRun())
|
|
1180
1489
|
return;
|
|
1181
|
-
|
|
1490
|
+
execSync2(`npm version ${kind}`, { stdio: "inherit" });
|
|
1182
1491
|
}
|
|
1183
1492
|
|
|
1184
1493
|
// src/components/release/release.ts
|
|
@@ -1569,7 +1878,7 @@ function tree(flags) {
|
|
|
1569
1878
|
}
|
|
1570
1879
|
|
|
1571
1880
|
// src/components/version/version.ts
|
|
1572
|
-
import { readFileSync as
|
|
1881
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
1573
1882
|
|
|
1574
1883
|
// src/components/version/version.constants.ts
|
|
1575
1884
|
var MASCOT = [
|
|
@@ -1584,19 +1893,19 @@ var MASCOT = [
|
|
|
1584
1893
|
];
|
|
1585
1894
|
|
|
1586
1895
|
// src/components/version/version.utils.ts
|
|
1587
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
1588
|
-
import
|
|
1896
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
1897
|
+
import path2 from "node:path";
|
|
1589
1898
|
import { fileURLToPath } from "node:url";
|
|
1590
1899
|
function packageJsonPath() {
|
|
1591
|
-
let dir =
|
|
1900
|
+
let dir = path2.dirname(fileURLToPath(import.meta.url));
|
|
1592
1901
|
for (;; ) {
|
|
1593
|
-
const candidate =
|
|
1594
|
-
if (
|
|
1595
|
-
const pkg = JSON.parse(
|
|
1902
|
+
const candidate = path2.join(dir, "package.json");
|
|
1903
|
+
if (existsSync3(candidate)) {
|
|
1904
|
+
const pkg = JSON.parse(readFileSync3(candidate, "utf8"));
|
|
1596
1905
|
if (pkg.name === "@tertium/gith")
|
|
1597
1906
|
return candidate;
|
|
1598
1907
|
}
|
|
1599
|
-
const parent =
|
|
1908
|
+
const parent = path2.dirname(dir);
|
|
1600
1909
|
if (parent === dir)
|
|
1601
1910
|
throw new Error("gith package.json not found");
|
|
1602
1911
|
dir = parent;
|
|
@@ -1605,7 +1914,7 @@ function packageJsonPath() {
|
|
|
1605
1914
|
|
|
1606
1915
|
// src/components/version/version.ts
|
|
1607
1916
|
function readVersion() {
|
|
1608
|
-
const pkg = JSON.parse(
|
|
1917
|
+
const pkg = JSON.parse(readFileSync4(packageJsonPath(), "utf8"));
|
|
1609
1918
|
return pkg.version ?? "unknown";
|
|
1610
1919
|
}
|
|
1611
1920
|
function versionText() {
|
|
@@ -1623,9 +1932,14 @@ Start and finish work
|
|
|
1623
1932
|
gith create -r <version> release/<version> from develop
|
|
1624
1933
|
gith create -h <version> hotfix/<version> from main
|
|
1625
1934
|
gith create --release | --hotfix version from the latest tag: minor for release, patch for hotfix
|
|
1935
|
+
gith switch list local and origin branches, and switch to the one picked
|
|
1626
1936
|
gith switch -b <branch> switch to a branch, creating it from develop if missing (on a detached HEAD: from that commit)
|
|
1627
1937
|
gith switch -commit <sha> detach HEAD at a commit, to look at it (tracked files must be clean)
|
|
1938
|
+
gith switch --release | --hotfix switch to the newest release/* or hotfix/* (local or origin); -r <v> / -h <v> for one version
|
|
1628
1939
|
gith update merge origin/develop into this work branch
|
|
1940
|
+
gith finish --current-release | --current-hotfix release the newest release/* or hotfix/*: version, changelog, merge to main, tag, back to develop
|
|
1941
|
+
gith merge --develop merge this branch into develop
|
|
1942
|
+
gith merge pick the branch to merge, then the target (develop, release/*, hotfix/*)
|
|
1629
1943
|
gith merge [-r <v> | -h <v>] merge this branch into develop, release/<v> or hotfix/<v>
|
|
1630
1944
|
gith merge --current-release merge this branch into the newest release (local or origin)
|
|
1631
1945
|
gith merge --current-hotfix merge this branch into the newest hotfix (local or origin)
|
|
@@ -1646,7 +1960,8 @@ Clean up branches (every removal shows what it will remove first)
|
|
|
1646
1960
|
gith remove -b <branch> delete the branch here and on origin (either side may be missing)
|
|
1647
1961
|
gith remove -b <branch> --local delete only the local branch
|
|
1648
1962
|
gith remove -b <branch> --remote delete only the origin branch
|
|
1649
|
-
gith remove
|
|
1963
|
+
gith remove pick branches to delete from a list (here and on origin)
|
|
1964
|
+
gith remove --branch [-l <n>] same list, for branch pairs (here and on origin)
|
|
1650
1965
|
gith remove --branch --local pick from local branches
|
|
1651
1966
|
gith remove --branch --remote pick from origin branches
|
|
1652
1967
|
gith remove --obsolete [--local] [-days <n>] remove branches untouched for 90 days, after Y/n
|
|
@@ -1670,6 +1985,7 @@ Remote and health
|
|
|
1670
1985
|
gith sync fetch all remotes and tags, prune stale refs
|
|
1671
1986
|
gith open open origin in the browser
|
|
1672
1987
|
gith doctor check the repo is ready for the branch flow (exit 1 on failures)
|
|
1988
|
+
gith changelog [--dry-run] add an entry for package.json's version to CHANGELOG.md (commits since the last tag)
|
|
1673
1989
|
|
|
1674
1990
|
Help
|
|
1675
1991
|
gith help show this help
|
|
@@ -1702,11 +2018,14 @@ try {
|
|
|
1702
2018
|
case "create":
|
|
1703
2019
|
create(parseFlags(rest));
|
|
1704
2020
|
break;
|
|
2021
|
+
case "finish":
|
|
2022
|
+
await finish(parseFlags(rest));
|
|
2023
|
+
break;
|
|
1705
2024
|
case "merge":
|
|
1706
|
-
merge(parseFlags(rest));
|
|
2025
|
+
await merge(parseFlags(rest));
|
|
1707
2026
|
break;
|
|
1708
2027
|
case "switch":
|
|
1709
|
-
switchBranch(parseFlags(rest));
|
|
2028
|
+
await switchBranch(parseFlags(rest));
|
|
1710
2029
|
break;
|
|
1711
2030
|
case "remove":
|
|
1712
2031
|
case "rm":
|
|
@@ -1753,6 +2072,9 @@ try {
|
|
|
1753
2072
|
case "open":
|
|
1754
2073
|
open2(parseFlags(rest));
|
|
1755
2074
|
break;
|
|
2075
|
+
case "changelog":
|
|
2076
|
+
changelog(parseFlags(rest));
|
|
2077
|
+
break;
|
|
1756
2078
|
case "doctor":
|
|
1757
2079
|
if (!doctor(parseFlags(rest)))
|
|
1758
2080
|
exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tertium/gith",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Git helper: branch flow with goal branches, release and hotfix containers, and everyday git commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"typecheck": "tsc --noEmit",
|
|
19
19
|
"release:patch": "node bin/gith.js release --patch",
|
|
20
20
|
"release:minor": "node bin/gith.js release --minor",
|
|
21
|
-
"release:major": "node bin/gith.js release --major"
|
|
21
|
+
"release:major": "node bin/gith.js release --major",
|
|
22
|
+
"version": "bun run build && node bin/gith.js changelog"
|
|
22
23
|
},
|
|
23
24
|
"license": "ISC",
|
|
24
25
|
"author": "Vitalii Balabanov",
|