@romenkova/cli 1.0.0 → 1.1.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/dist/commands/git/branch.js +42 -0
- package/dist/commands/git/commit.js +35 -0
- package/dist/commands/git/git.js +30 -0
- package/dist/commands/git/index.js +14 -0
- package/dist/commands/git/rebase.js +39 -0
- package/dist/commands/rebase.js +37 -0
- package/dist/git.js +14 -0
- package/dist/index.js +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { prompt, ui } from "../../ui.js";
|
|
3
|
+
import { slugify } from "./git.js";
|
|
4
|
+
const branchCommand = {
|
|
5
|
+
command: "branch",
|
|
6
|
+
describe: "Create a branch from a ticket ID",
|
|
7
|
+
builder: {
|
|
8
|
+
name: {
|
|
9
|
+
alias: "n",
|
|
10
|
+
type: "string",
|
|
11
|
+
demandOption: true,
|
|
12
|
+
describe: "Your name (used as branch prefix)",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
handler: async (argv) => {
|
|
16
|
+
const author = argv.name;
|
|
17
|
+
ui.header("branch");
|
|
18
|
+
ui.info("author", author);
|
|
19
|
+
ui.separator();
|
|
20
|
+
const ticket = (await prompt("Ticket (e.g. XXX-000): ")).toUpperCase();
|
|
21
|
+
if (!/^[A-Z]+-\d+$/.test(ticket)) {
|
|
22
|
+
ui.fail(`Invalid ticket format: ${ticket}`, "Expected: XXX-000");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const description = await prompt("Description: ");
|
|
26
|
+
if (!description) {
|
|
27
|
+
ui.fail("Description cannot be empty.");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const branchName = `${author}/${ticket}-${slugify(description)}`;
|
|
31
|
+
ui.result("branch", branchName);
|
|
32
|
+
try {
|
|
33
|
+
execSync(`git checkout -b ${branchName}`, { stdio: "inherit" });
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
ui.fail("Failed to create branch.");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
ui.done();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
export default branchCommand;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { ui } from "../../ui.js";
|
|
3
|
+
import { extractTicket, getBranchName } from "./git.js";
|
|
4
|
+
const commitCommand = {
|
|
5
|
+
command: "commit",
|
|
6
|
+
describe: "Create a commit prefixed with the ticket from the branch name",
|
|
7
|
+
handler: async () => {
|
|
8
|
+
ui.header("commit");
|
|
9
|
+
const branch = getBranchName();
|
|
10
|
+
const ticket = extractTicket(branch);
|
|
11
|
+
if (!ticket) {
|
|
12
|
+
ui.fail(`Could not extract ticket from branch: ${branch}`, "Expected: name/XXX-000-some-slug");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
ui.info("branch", branch);
|
|
16
|
+
ui.highlight("ticket", ticket);
|
|
17
|
+
ui.separator();
|
|
18
|
+
const message = await prompt("Message: ");
|
|
19
|
+
if (!message) {
|
|
20
|
+
ui.fail("Commit message cannot be empty.");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const commitMessage = `${ticket}: ${message}`;
|
|
24
|
+
ui.result("commit", `"${commitMessage}"`);
|
|
25
|
+
try {
|
|
26
|
+
execSync(`git commit -m "${commitMessage}"`, { stdio: "inherit" });
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
ui.fail("Git commit failed.");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
ui.done();
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export default commitCommand;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
export function getBranchName() {
|
|
3
|
+
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
4
|
+
encoding: "utf-8",
|
|
5
|
+
}).trim();
|
|
6
|
+
}
|
|
7
|
+
export function extractTicket(branch) {
|
|
8
|
+
const match = branch.match(/\/([A-Z]+-\d+)/);
|
|
9
|
+
return match ? match[1] : null;
|
|
10
|
+
}
|
|
11
|
+
export function slugify(text) {
|
|
12
|
+
return text
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
15
|
+
.replace(/^-+|-+$/g, "");
|
|
16
|
+
}
|
|
17
|
+
export function getLog(count) {
|
|
18
|
+
const raw = execSync(`git log --oneline -n ${count} --format="%h %s"`, {
|
|
19
|
+
encoding: "utf-8",
|
|
20
|
+
}).trim();
|
|
21
|
+
if (!raw)
|
|
22
|
+
return [];
|
|
23
|
+
return raw.split("\n").map((line) => {
|
|
24
|
+
const spaceIndex = line.indexOf(" ");
|
|
25
|
+
return {
|
|
26
|
+
hash: line.substring(0, spaceIndex),
|
|
27
|
+
message: line.substring(spaceIndex + 1),
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import branchCommand from "./branch.js";
|
|
2
|
+
import commitCommand from "./commit.js";
|
|
3
|
+
import rebaseCommand from "./rebase.js";
|
|
4
|
+
const gitCommand = {
|
|
5
|
+
command: "git",
|
|
6
|
+
describe: "Git workflow helpers",
|
|
7
|
+
builder: (yargs) => yargs
|
|
8
|
+
.command(commitCommand)
|
|
9
|
+
.command(branchCommand)
|
|
10
|
+
.command(rebaseCommand)
|
|
11
|
+
.demandCommand(1, ""),
|
|
12
|
+
handler: () => { },
|
|
13
|
+
};
|
|
14
|
+
export default gitCommand;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { select } from "@inquirer/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { getLog } from "./git.js";
|
|
5
|
+
import { ui } from "../../ui.js";
|
|
6
|
+
const COMMIT_COUNT = 30;
|
|
7
|
+
const rebaseCommand = {
|
|
8
|
+
command: "rebase",
|
|
9
|
+
describe: "Interactive rebase \u2014 pick a commit from the log",
|
|
10
|
+
handler: async () => {
|
|
11
|
+
ui.header("rebase");
|
|
12
|
+
const commits = getLog(COMMIT_COUNT);
|
|
13
|
+
if (commits.length === 0) {
|
|
14
|
+
ui.fail("No commits found.");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const index = await select({
|
|
18
|
+
message: "Rebase from which commit?",
|
|
19
|
+
loop: false,
|
|
20
|
+
pageSize: 20,
|
|
21
|
+
choices: commits.map((c, i) => ({
|
|
22
|
+
name: `${chalk.yellow(c.hash)} ${c.message}`,
|
|
23
|
+
value: i + 1,
|
|
24
|
+
})),
|
|
25
|
+
});
|
|
26
|
+
const isRoot = index === commits.length;
|
|
27
|
+
const rebaseArg = isRoot ? "--root" : `HEAD~${index}`;
|
|
28
|
+
ui.result("rebase", `git rebase -i ${rebaseArg}`);
|
|
29
|
+
try {
|
|
30
|
+
execSync(`git rebase -i ${rebaseArg}`, { stdio: "inherit" });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
ui.fail("Rebase failed or was aborted.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
ui.done();
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export default rebaseCommand;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { select } from "@inquirer/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { getLog } from "../git.js";
|
|
5
|
+
import { ui } from "../ui.js";
|
|
6
|
+
const COMMIT_COUNT = 30;
|
|
7
|
+
const rebaseCommand = {
|
|
8
|
+
command: "rebase",
|
|
9
|
+
describe: "Interactive rebase \u2014 pick a commit from the log",
|
|
10
|
+
handler: async () => {
|
|
11
|
+
ui.header("rebase");
|
|
12
|
+
const commits = getLog(COMMIT_COUNT);
|
|
13
|
+
if (commits.length === 0) {
|
|
14
|
+
ui.fail("No commits found.");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const index = await select({
|
|
18
|
+
message: "Rebase from which commit?",
|
|
19
|
+
loop: false,
|
|
20
|
+
pageSize: 20,
|
|
21
|
+
choices: commits.map((c, i) => ({
|
|
22
|
+
name: `${chalk.yellow(c.hash)} ${c.message}`,
|
|
23
|
+
value: i + 1,
|
|
24
|
+
})),
|
|
25
|
+
});
|
|
26
|
+
ui.result("rebase", `git rebase -i HEAD~${index}`);
|
|
27
|
+
try {
|
|
28
|
+
execSync(`git rebase -i HEAD~${index}`, { stdio: "inherit" });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
ui.fail("Rebase failed or was aborted.");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
ui.done();
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export default rebaseCommand;
|
package/dist/git.js
CHANGED
|
@@ -14,3 +14,17 @@ export function slugify(text) {
|
|
|
14
14
|
.replace(/[^a-z0-9]+/g, "-")
|
|
15
15
|
.replace(/^-+|-+$/g, "");
|
|
16
16
|
}
|
|
17
|
+
export function getLog(count) {
|
|
18
|
+
const raw = execSync(`git log --oneline -n ${count} --format="%h %s"`, {
|
|
19
|
+
encoding: "utf-8",
|
|
20
|
+
}).trim();
|
|
21
|
+
if (!raw)
|
|
22
|
+
return [];
|
|
23
|
+
return raw.split("\n").map((line) => {
|
|
24
|
+
const spaceIndex = line.indexOf(" ");
|
|
25
|
+
return {
|
|
26
|
+
hash: line.substring(0, spaceIndex),
|
|
27
|
+
message: line.substring(spaceIndex + 1),
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@romenkova/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Personal dev CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"tsx": "^4.21.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@inquirer/prompts": "^8.2.0",
|
|
30
31
|
"chalk": "^5.6.2",
|
|
31
32
|
"yargs": "^18.0.0"
|
|
32
33
|
}
|