@hallaxius/forge 0.1.3 → 0.1.4

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.
Files changed (45) hide show
  1. package/README.md +160 -158
  2. package/bin/forge.js +2 -2
  3. package/dist/cli.js +12764 -13800
  4. package/package.json +75 -75
  5. package/src/cli.ts +80 -78
  6. package/src/commands/account.ts +80 -0
  7. package/src/commands/alias.ts +66 -66
  8. package/src/commands/branch.ts +46 -46
  9. package/src/commands/ci.ts +28 -28
  10. package/src/commands/clone.ts +100 -100
  11. package/src/commands/commit.ts +88 -88
  12. package/src/commands/config.ts +47 -48
  13. package/src/commands/diff.ts +26 -26
  14. package/src/commands/fetch.ts +20 -20
  15. package/src/commands/help.ts +58 -58
  16. package/src/commands/init.ts +32 -33
  17. package/src/commands/issue.ts +63 -63
  18. package/src/commands/log.ts +29 -29
  19. package/src/commands/merge.ts +37 -37
  20. package/src/commands/pr.ts +65 -65
  21. package/src/commands/push.ts +35 -35
  22. package/src/commands/release.ts +26 -26
  23. package/src/commands/remote.ts +107 -107
  24. package/src/commands/reset.ts +30 -30
  25. package/src/commands/setup.ts +93 -94
  26. package/src/commands/stash.ts +44 -44
  27. package/src/commands/status.ts +74 -74
  28. package/src/commands/sync.ts +20 -20
  29. package/src/commands/tag.ts +41 -41
  30. package/src/commands/undo.ts +27 -27
  31. package/src/commands/version.ts +12 -12
  32. package/src/constants/colors.ts +7 -7
  33. package/src/constants/commit-types.ts +24 -24
  34. package/src/constants/messages.ts +13 -23
  35. package/src/lib/auth.ts +172 -172
  36. package/src/lib/config.ts +108 -108
  37. package/src/lib/git.ts +543 -543
  38. package/src/lib/github.ts +202 -160
  39. package/src/lib/logger.ts +18 -31
  40. package/src/lib/ui.ts +122 -156
  41. package/src/lib/validators.ts +16 -16
  42. package/src/templates/commit-types.json +9 -9
  43. package/src/utils/files.ts +21 -21
  44. package/src/utils/strings.ts +19 -19
  45. package/src/version.const.ts +1 -1
@@ -1,28 +1,28 @@
1
- import type { Command } from "commander";
2
- import { getCIStatus } from "../lib/github.js";
3
- import { error, info } from "../lib/logger.js";
4
- import { createTable, withSpinner } from "../lib/ui.js";
5
-
6
- export default function register(program: Command): void {
7
- program
8
- .command("ci")
9
- .description("Check CI status")
10
- .action(async () => {
11
- try {
12
- const checks = await withSpinner("Fetching CI status...", () =>
13
- getCIStatus(),
14
- );
15
- if (checks.length === 0) {
16
- info("No CI checks found.");
17
- return;
18
- }
19
- const rows = checks.map((c) => [c.name, c.conclusion, c.branch]);
20
- info("CI Checks:");
21
- console.log(createTable(["Name", "Status", "Branch"], rows));
22
- } catch (err) {
23
- error(
24
- `CI check failed: ${err instanceof Error ? err.message : String(err)}`,
25
- );
26
- }
27
- });
28
- }
1
+ import type { Command } from "commander";
2
+ import { getCIStatus } from "../lib/github.js";
3
+ import { error, text } from "../lib/logger.js";
4
+ import { createTable, withSpinner } from "../lib/ui.js";
5
+
6
+ export default function register(program: Command): void {
7
+ program
8
+ .command("ci")
9
+ .description("Check CI status")
10
+ .action(async () => {
11
+ try {
12
+ const checks = await withSpinner("Fetching CI status...", () =>
13
+ getCIStatus(),
14
+ );
15
+ if (checks.length === 0) {
16
+ text("No CI checks found.");
17
+ return;
18
+ }
19
+ const rows = checks.map((c) => [c.name, c.conclusion, c.branch]);
20
+ text("CI Checks:");
21
+ text(createTable(["Name", "Status", "Branch"], rows));
22
+ } catch (err) {
23
+ error(
24
+ `CI check failed: ${err instanceof Error ? err.message : String(err)}`,
25
+ );
26
+ }
27
+ });
28
+ }
@@ -1,100 +1,100 @@
1
- import type { Command } from "commander";
2
- import { ConfigManager } from "../lib/config.js";
3
- import * as git from "../lib/git.js";
4
- import { error, highlight, info } from "../lib/logger.js";
5
- import { createTable, showBox, withSpinner } from "../lib/ui.js";
6
-
7
- export default function register(program: Command): void {
8
- program
9
- .command("clone")
10
- .description("Clone a repository")
11
- .argument("[url]", "Repository URL or org/repo shorthand")
12
- .argument("[dir]", "Target directory")
13
- .option("--ssh", "Use SSH URL")
14
- .option(
15
- "--depth <n>",
16
- "Create a shallow clone with history truncated to n commits",
17
- )
18
- .option("--branch <name>", "Clone a specific branch")
19
- .option(
20
- "--recurse-submodules",
21
- "Initialize and clone submodules recursively",
22
- )
23
- .option("--cd", "Print cd command for eval")
24
- .option("--list", "List recent clones")
25
- .action(async (url, dir, options) => {
26
- try {
27
- if (options.list) {
28
- const config = new ConfigManager();
29
- const clones = config.getClones();
30
-
31
- if (clones.length === 0) {
32
- info("No recent clones found.");
33
- return;
34
- }
35
-
36
- const rows = clones.map((c) => {
37
- const [u, d] = c.split("|");
38
- return [u || "", d || ""];
39
- });
40
-
41
- info("Recent clones:");
42
- console.log(createTable(["URL", "Directory"], rows));
43
- return;
44
- }
45
-
46
- if (!url) {
47
- error("Repository URL or org/repo shorthand is required.");
48
- return;
49
- }
50
-
51
- let resolvedUrl = url;
52
-
53
- if (url.includes("/") && !url.includes(".") && !url.includes("://")) {
54
- resolvedUrl = `https://github.com/${url}.git`;
55
- }
56
-
57
- if (options.ssh) {
58
- const match = resolvedUrl.match(/https:\/\/github\.com\/(.+)\.git$/);
59
- if (match) {
60
- resolvedUrl = `git@github.com:${match[1]}.git`;
61
- } else if (resolvedUrl.startsWith("https://")) {
62
- const rest = resolvedUrl.replace("https://", "");
63
- const slashIndex = rest.indexOf("/");
64
- if (slashIndex !== -1) {
65
- const host = rest.substring(0, slashIndex);
66
- const path = rest.substring(slashIndex + 1).replace(/\.git$/, "");
67
- resolvedUrl = `git@${host}:${path}.git`;
68
- }
69
- }
70
- }
71
-
72
- const cloneDir = await withSpinner("Cloning repository...", () =>
73
- git.clone(resolvedUrl, dir, {
74
- depth: options.depth ? Number(options.depth) : undefined,
75
- branch: options.branch,
76
- recurseSubmodules: options.recurseSubmodules,
77
- }),
78
- );
79
-
80
- const content = [
81
- `URL: ${resolvedUrl}`,
82
- `Directory: ${cloneDir}`,
83
- `Branch: ${options.branch || "default"}`,
84
- ].join("\n");
85
-
86
- showBox("Clone Complete", content);
87
-
88
- if (options.cd) {
89
- highlight(`cd ${cloneDir}`);
90
- }
91
-
92
- const config = new ConfigManager();
93
- config.addClone(`${resolvedUrl}|${cloneDir}`);
94
- } catch (err) {
95
- error(
96
- `Clone failed: ${err instanceof Error ? err.message : String(err)}`,
97
- );
98
- }
99
- });
100
- }
1
+ import type { Command } from "commander";
2
+ import { ConfigManager } from "../lib/config.js";
3
+ import * as git from "../lib/git.js";
4
+ import { error, text } from "../lib/logger.js";
5
+ import { createTable, withSpinner } from "../lib/ui.js";
6
+
7
+ export default function register(program: Command): void {
8
+ program
9
+ .command("clone")
10
+ .description("Clone a repository")
11
+ .argument("[url]", "Repository URL or org/repo shorthand")
12
+ .argument("[dir]", "Target directory")
13
+ .option("--ssh", "Use SSH URL")
14
+ .option(
15
+ "--depth <n>",
16
+ "Create a shallow clone with history truncated to n commits",
17
+ )
18
+ .option("--branch <name>", "Clone a specific branch")
19
+ .option(
20
+ "--recurse-submodules",
21
+ "Initialize and clone submodules recursively",
22
+ )
23
+ .option("--cd", "Print cd command for eval")
24
+ .option("--list", "List recent clones")
25
+ .action(async (url, dir, options) => {
26
+ try {
27
+ if (options.list) {
28
+ const config = new ConfigManager();
29
+ const clones = config.getClones();
30
+
31
+ if (clones.length === 0) {
32
+ text("No recent clones found.");
33
+ return;
34
+ }
35
+
36
+ const rows = clones.map((c) => {
37
+ const [u, d] = c.split("|");
38
+ return [u || "", d || ""];
39
+ });
40
+
41
+ text("Recent clones:");
42
+ text(createTable(["URL", "Directory"], rows));
43
+ return;
44
+ }
45
+
46
+ if (!url) {
47
+ error("Repository URL or org/repo shorthand is required.");
48
+ return;
49
+ }
50
+
51
+ let resolvedUrl = url;
52
+
53
+ if (url.includes("/") && !url.includes(".") && !url.includes("://")) {
54
+ resolvedUrl = `https://github.com/${url}.git`;
55
+ }
56
+
57
+ if (options.ssh) {
58
+ const match = resolvedUrl.match(/https:\/\/github\.com\/(.+)\.git$/);
59
+ if (match) {
60
+ resolvedUrl = `git@github.com:${match[1]}.git`;
61
+ } else if (resolvedUrl.startsWith("https://")) {
62
+ const rest = resolvedUrl.replace("https://", "");
63
+ const slashIndex = rest.indexOf("/");
64
+ if (slashIndex !== -1) {
65
+ const host = rest.substring(0, slashIndex);
66
+ const path = rest.substring(slashIndex + 1).replace(/\.git$/, "");
67
+ resolvedUrl = `git@${host}:${path}.git`;
68
+ }
69
+ }
70
+ }
71
+
72
+ const cloneDir = await withSpinner("Cloning repository...", () =>
73
+ git.clone(resolvedUrl, dir, {
74
+ depth: options.depth ? Number(options.depth) : undefined,
75
+ branch: options.branch,
76
+ recurseSubmodules: options.recurseSubmodules,
77
+ }),
78
+ );
79
+
80
+ const content = [
81
+ `URL: ${resolvedUrl}`,
82
+ `Directory: ${cloneDir}`,
83
+ `Branch: ${options.branch || "default"}`,
84
+ ].join("\n");
85
+
86
+ text(content);
87
+
88
+ if (options.cd) {
89
+ text(`cd ${cloneDir}`);
90
+ }
91
+
92
+ const config = new ConfigManager();
93
+ config.addClone(`${resolvedUrl}|${cloneDir}`);
94
+ } catch (err) {
95
+ error(
96
+ `Clone failed: ${err instanceof Error ? err.message : String(err)}`,
97
+ );
98
+ }
99
+ });
100
+ }
@@ -1,88 +1,88 @@
1
- import type { Command } from "commander";
2
- import { commitTypes } from "../constants/messages.js";
3
- import * as git from "../lib/git.js";
4
- import { error, info, newline, success, text, warning } from "../lib/logger.js";
5
- import { checkbox, confirm, input, select, withSpinner } from "../lib/ui.js";
6
-
7
- export default function register(program: Command): void {
8
- program
9
- .command("commit")
10
- .description("Create a commit (interactive or quick)")
11
- .option("-m, --message <message>", "Quick commit message")
12
- .option("--amend", "Amend last commit")
13
- .action(async (options) => {
14
- try {
15
- if (options.amend) {
16
- await git.amendCommit();
17
- success("Last commit amended.");
18
- return;
19
- }
20
-
21
- if (options.message) {
22
- const hash = await git.commit(options.message);
23
- success(`Committed: ${hash}`);
24
- return;
25
- }
26
-
27
- const status = await git.getStatus();
28
-
29
- if (status.files.length === 0) {
30
- warning(
31
- "No files to commit. Stage files first or use -m for quick commit.",
32
- );
33
- return;
34
- }
35
-
36
- const fileChoices = status.files.map((f) => ({
37
- name: `${f.path} (${f.working_dir || f.index})`,
38
- value: f.path,
39
- checked: f.index !== " " || f.working_dir !== " ",
40
- }));
41
-
42
- const selectedFiles = await checkbox(
43
- "Select files to stage",
44
- fileChoices,
45
- );
46
-
47
- if (selectedFiles.length === 0) {
48
- warning("No files selected. Commit cancelled.");
49
- return;
50
- }
51
-
52
- await withSpinner("Staging files...", () => git.add(selectedFiles));
53
-
54
- newline();
55
- const typeChoices = commitTypes.map((t) => ({
56
- name: `${t.value.padEnd(10)} ${t.description}`,
57
- value: t.value,
58
- }));
59
- const type = await select("Commit type", typeChoices);
60
-
61
- const scope = await input("Scope (optional)");
62
-
63
- const description = await input("Description");
64
-
65
- const fullMessage = scope
66
- ? `${type}(${scope}): ${description}`
67
- : `${type}: ${description}`;
68
-
69
- newline();
70
- info("Commit preview:");
71
- text(` ${fullMessage}`);
72
- newline();
73
-
74
- const proceed = await confirm("Proceed with commit?");
75
- if (!proceed) {
76
- info("Commit cancelled.");
77
- return;
78
- }
79
-
80
- const hash = await git.commit(fullMessage);
81
- success(`Committed: ${hash}`);
82
- } catch (err) {
83
- error(
84
- `Commit failed: ${err instanceof Error ? err.message : String(err)}`,
85
- );
86
- }
87
- });
88
- }
1
+ import type { Command } from "commander";
2
+ import { commitTypes } from "../constants/messages.js";
3
+ import * as git from "../lib/git.js";
4
+ import { error, newline, text, warning } from "../lib/logger.js";
5
+ import { checkbox, confirm, input, select, withSpinner } from "../lib/ui.js";
6
+
7
+ export default function register(program: Command): void {
8
+ program
9
+ .command("commit")
10
+ .description("Create a commit (interactive or quick)")
11
+ .option("-m, --message <message>", "Quick commit message")
12
+ .option("--amend", "Amend last commit")
13
+ .action(async (options) => {
14
+ try {
15
+ if (options.amend) {
16
+ await git.amendCommit();
17
+ text("Last commit amended.");
18
+ return;
19
+ }
20
+
21
+ if (options.message) {
22
+ const hash = await git.commit(options.message);
23
+ text(`Committed: ${hash}`);
24
+ return;
25
+ }
26
+
27
+ const status = await git.getStatus();
28
+
29
+ if (status.files.length === 0) {
30
+ warning(
31
+ "No files to commit. Stage files first or use -m for quick commit.",
32
+ );
33
+ return;
34
+ }
35
+
36
+ const fileChoices = status.files.map((f) => ({
37
+ name: `${f.path} (${f.working_dir || f.index})`,
38
+ value: f.path,
39
+ checked: f.index !== " " || f.working_dir !== " ",
40
+ }));
41
+
42
+ const selectedFiles = await checkbox(
43
+ "Select files to stage",
44
+ fileChoices,
45
+ );
46
+
47
+ if (selectedFiles.length === 0) {
48
+ warning("No files selected. Commit cancelled.");
49
+ return;
50
+ }
51
+
52
+ await withSpinner("Staging files...", () => git.add(selectedFiles));
53
+
54
+ newline();
55
+ const typeChoices = commitTypes.map((t) => ({
56
+ name: `${t.value.padEnd(10)} ${t.description}`,
57
+ value: t.value,
58
+ }));
59
+ const type = await select("Commit type", typeChoices);
60
+
61
+ const scope = await input("Scope (optional)");
62
+
63
+ const description = await input("Description");
64
+
65
+ const fullMessage = scope
66
+ ? `${type}(${scope}): ${description}`
67
+ : `${type}: ${description}`;
68
+
69
+ newline();
70
+ text("Commit preview:");
71
+ text(` ${fullMessage}`);
72
+ newline();
73
+
74
+ const proceed = await confirm("Proceed with commit?");
75
+ if (!proceed) {
76
+ text("Commit cancelled.");
77
+ return;
78
+ }
79
+
80
+ const hash = await git.commit(fullMessage);
81
+ text(`Committed: ${hash}`);
82
+ } catch (err) {
83
+ error(
84
+ `Commit failed: ${err instanceof Error ? err.message : String(err)}`,
85
+ );
86
+ }
87
+ });
88
+ }
@@ -1,48 +1,47 @@
1
- import { spawnSync } from "node:child_process";
2
- import type { Command } from "commander";
3
- import { ConfigManager } from "../lib/config.js";
4
- import { info } from "../lib/logger.js";
5
- import { showBox } from "../lib/ui.js";
6
-
7
- export default function register(program: Command): void {
8
- program
9
- .command("config")
10
- .description("Manage configuration")
11
- .option("--show", "Show config")
12
- .option("--edit", "Edit config in editor")
13
- .action(async (options) => {
14
- try {
15
- const config = new ConfigManager();
16
-
17
- if (options.edit) {
18
- const editor = process.env.EDITOR || "vim";
19
- const configPath = config.getPath();
20
- spawnSync(editor, [configPath], { stdio: "inherit" });
21
- info("Config edited.");
22
- return;
23
- }
24
-
25
- const user = config.get("user");
26
- const github = config.get("github");
27
- const prefs = config.get("preferences");
28
-
29
- const content = [
30
- `User:`,
31
- ` Name: ${user.name || "(not set)"}`,
32
- ` Email: ${user.email || "(not set)"}`,
33
- ``,
34
- `GitHub:`,
35
- ` Token: ${github.token ? (github.token.includes(":") ? "*** (encrypted)" : "***") : "(not set)"}`,
36
- ``,
37
- `Preferences:`,
38
- ` Auto Push: ${prefs.autoPush}`,
39
- ` Commit Template: ${prefs.commitTemplate || "(default)"}`,
40
- ` Editor: ${prefs.editor}`,
41
- ].join("\n");
42
-
43
- showBox("Configuration", content);
44
- } catch (err) {
45
- info(`Config: ${err instanceof Error ? err.message : String(err)}`);
46
- }
47
- });
48
- }
1
+ import { spawnSync } from "node:child_process";
2
+ import type { Command } from "commander";
3
+ import { ConfigManager } from "../lib/config.js";
4
+ import { text } from "../lib/logger.js";
5
+
6
+ export default function register(program: Command): void {
7
+ program
8
+ .command("config")
9
+ .description("Manage configuration")
10
+ .option("--show", "Show config")
11
+ .option("--edit", "Edit config in editor")
12
+ .action(async (options) => {
13
+ try {
14
+ const config = new ConfigManager();
15
+
16
+ if (options.edit) {
17
+ const editor = process.env.EDITOR || "vim";
18
+ const configPath = config.getPath();
19
+ spawnSync(editor, [configPath], { stdio: "inherit" });
20
+ text("Config edited.");
21
+ return;
22
+ }
23
+
24
+ const user = config.get("user");
25
+ const github = config.get("github");
26
+ const prefs = config.get("preferences");
27
+
28
+ const content = [
29
+ `User:`,
30
+ ` Name: ${user.name || "(not set)"}`,
31
+ ` Email: ${user.email || "(not set)"}`,
32
+ ``,
33
+ `GitHub:`,
34
+ ` Token: ${github.token ? (github.token.includes(":") ? "*** (encrypted)" : "***") : "(not set)"}`,
35
+ ``,
36
+ `Preferences:`,
37
+ ` Auto Push: ${prefs.autoPush}`,
38
+ ` Commit Template: ${prefs.commitTemplate || "(default)"}`,
39
+ ` Editor: ${prefs.editor}`,
40
+ ].join("\n");
41
+
42
+ text(content);
43
+ } catch (err) {
44
+ text(`Config: ${err instanceof Error ? err.message : String(err)}`);
45
+ }
46
+ });
47
+ }
@@ -1,26 +1,26 @@
1
- import type { Command } from "commander";
2
- import * as git from "../lib/git.js";
3
- import { info, text } from "../lib/logger.js";
4
-
5
- export default function register(program: Command): void {
6
- program
7
- .command("diff")
8
- .description("Show changes")
9
- .option("--staged", "Show staged changes")
10
- .action(async (options) => {
11
- try {
12
- const output = options.staged
13
- ? await git.diffStaged()
14
- : await git.diff();
15
-
16
- if (!output) {
17
- info("No changes.");
18
- return;
19
- }
20
-
21
- text(output);
22
- } catch (err) {
23
- info(`Diff: ${err instanceof Error ? err.message : String(err)}`);
24
- }
25
- });
26
- }
1
+ import type { Command } from "commander";
2
+ import * as git from "../lib/git.js";
3
+ import { text } from "../lib/logger.js";
4
+
5
+ export default function register(program: Command): void {
6
+ program
7
+ .command("diff")
8
+ .description("Show changes")
9
+ .option("--staged", "Show staged changes")
10
+ .action(async (options) => {
11
+ try {
12
+ const output = options.staged
13
+ ? await git.diffStaged()
14
+ : await git.diff();
15
+
16
+ if (!output) {
17
+ text("No changes.");
18
+ return;
19
+ }
20
+
21
+ text(output);
22
+ } catch (err) {
23
+ text(`Diff: ${err instanceof Error ? err.message : String(err)}`);
24
+ }
25
+ });
26
+ }
@@ -1,20 +1,20 @@
1
- import type { Command } from "commander";
2
- import * as git from "../lib/git.js";
3
- import { error, success } from "../lib/logger.js";
4
- import { withSpinner } from "../lib/ui.js";
5
-
6
- export default function register(program: Command): void {
7
- program
8
- .command("fetch")
9
- .description("Fetch from remote")
10
- .action(async () => {
11
- try {
12
- const result = await withSpinner("Fetching...", () => git.fetch());
13
- success(`Fetch complete: ${result}`);
14
- } catch (err) {
15
- error(
16
- `Fetch failed: ${err instanceof Error ? err.message : String(err)}`,
17
- );
18
- }
19
- });
20
- }
1
+ import type { Command } from "commander";
2
+ import * as git from "../lib/git.js";
3
+ import { error, text } from "../lib/logger.js";
4
+ import { withSpinner } from "../lib/ui.js";
5
+
6
+ export default function register(program: Command): void {
7
+ program
8
+ .command("fetch")
9
+ .description("Fetch from remote")
10
+ .action(async () => {
11
+ try {
12
+ const result = await withSpinner("Fetching...", () => git.fetch());
13
+ text(`Fetch complete: ${result}`);
14
+ } catch (err) {
15
+ error(
16
+ `Fetch failed: ${err instanceof Error ? err.message : String(err)}`,
17
+ );
18
+ }
19
+ });
20
+ }