git-interactive-vlaqa 1.0.2 → 1.0.3

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 CHANGED
@@ -79,3 +79,45 @@ After a successful commit, the short hash and subject are printed:
79
79
  | Not inside a Git repository | `Make sure you are calling commands from GIT project!` |
80
80
  | No staged files | `No staged files. Stage your changes with \`git add\` first.` |
81
81
  | Prompt cancelled (`Ctrl+C`) | `👋 until next time!` |
82
+
83
+ ---
84
+
85
+ ### `gia` — Interactive Add
86
+
87
+ Interactively stage files using a checkbox UI. Shows all changed files grouped by directory, with their current Git status.
88
+
89
+ ```bash
90
+ gia
91
+ ```
92
+
93
+ #### Interactive Prompts
94
+
95
+ A checkbox list is presented with all modified, untracked, and partially staged files. Files inside subdirectories are grouped under their parent directory.
96
+
97
+ | Entry type | Example display |
98
+ |------------|-----------------|
99
+ | Root-level file | `M src/index.ts` |
100
+ | Directory group | `src/` |
101
+ | File inside directory | ` M src/add.ts` |
102
+
103
+ - Already-staged files are pre-checked.
104
+ - Selecting a directory entry (`src/`) stages all files inside it.
105
+ - Space to toggle, Enter to confirm.
106
+
107
+ #### Output
108
+
109
+ ```
110
+ ✔ Staged 3 file(s):
111
+ src/add.ts
112
+ src/index.ts
113
+ tests/add.test.ts
114
+ ```
115
+
116
+ #### Error handling
117
+
118
+ | Condition | Message |
119
+ |-----------|---------|
120
+ | Not inside a Git repository | `Make sure you are calling commands from GIT project!` |
121
+ | Working tree is clean | `No changes to stage. Working tree is clean.` |
122
+ | No files selected | `No files selected. Nothing staged.` |
123
+ | Prompt cancelled (`Ctrl+C`) | `👋 until next time!` |
package/dist/add.js CHANGED
@@ -1,8 +1,68 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.add = void 0;
4
+ const prompts_1 = require("@inquirer/prompts");
4
5
  const git_1 = require("./git");
5
6
  const gitError_1 = require("./errors/gitError");
7
+ function parseStatusLines(lines) {
8
+ const root = [];
9
+ const dirs = new Map();
10
+ for (const line of lines) {
11
+ const xy = line.slice(0, 2);
12
+ const file = line.slice(3).trim();
13
+ const staged = xy[0] !== ' ' && xy[0] !== '?';
14
+ const status = xy.trim() || '??';
15
+ const entry = { file, status, staged };
16
+ const slashIdx = file.indexOf('/');
17
+ if (slashIdx === -1 || slashIdx === file.length - 1) {
18
+ root.push(entry);
19
+ }
20
+ else {
21
+ const dir = file.slice(0, slashIdx);
22
+ if (!dirs.has(dir))
23
+ dirs.set(dir, []);
24
+ dirs.get(dir).push(entry);
25
+ }
26
+ }
27
+ return { root, dirs };
28
+ }
29
+ function buildChoices(root, dirs) {
30
+ const choices = [];
31
+ for (const entry of root) {
32
+ choices.push({ name: `${entry.status} ${entry.file}`, value: entry.file, checked: entry.staged });
33
+ }
34
+ for (const [dir, entries] of dirs) {
35
+ const allStaged = entries.every((e) => e.staged);
36
+ choices.push({ name: `${dir}/`, value: `${dir}/`, checked: allStaged });
37
+ for (const entry of entries) {
38
+ choices.push({
39
+ name: ` ${entry.status} ${entry.file}`,
40
+ value: entry.file,
41
+ checked: allStaged || entry.staged,
42
+ });
43
+ }
44
+ }
45
+ return choices;
46
+ }
47
+ function expandSelection(selected, dirs) {
48
+ const result = [];
49
+ for (const value of selected) {
50
+ if (value.endsWith('/')) {
51
+ const dirName = value.slice(0, -1);
52
+ const children = dirs.get(dirName);
53
+ if (children) {
54
+ children.forEach((e) => result.push(e.file));
55
+ }
56
+ else {
57
+ result.push(value);
58
+ }
59
+ }
60
+ else {
61
+ result.push(value);
62
+ }
63
+ }
64
+ return [...new Set(result)];
65
+ }
6
66
  class Add {
7
67
  gitBase;
8
68
  constructor(gitBase = git_1.git) {
@@ -11,8 +71,28 @@ class Add {
11
71
  async run(workdir) {
12
72
  if (!this.gitBase.isInsideGitProject(workdir).ok)
13
73
  throw new gitError_1.GitError('Make sure you are calling commands from GIT project!');
14
- // TODO: implement interactive git add
15
- throw new Error('Not implemented');
74
+ const statusResult = this.gitBase.ok(workdir, ['status', '--porcelain']);
75
+ if (!statusResult.ok)
76
+ throw new gitError_1.GitError(`Could not get git status: ${statusResult.out}`);
77
+ const lines = statusResult.out.split('\n').filter(Boolean);
78
+ if (lines.length === 0)
79
+ throw new gitError_1.GitError('No changes to stage. Working tree is clean.');
80
+ const { root, dirs } = parseStatusLines(lines);
81
+ const choices = buildChoices(root, dirs);
82
+ const selected = await (0, prompts_1.checkbox)({
83
+ message: 'Select files to stage:',
84
+ choices,
85
+ });
86
+ if (selected.length === 0) {
87
+ console.log('No files selected. Nothing staged.');
88
+ return;
89
+ }
90
+ const toStage = expandSelection(selected, dirs);
91
+ const result = this.gitBase.ok(workdir, ['add', '--', ...toStage]);
92
+ if (!result.ok)
93
+ throw new gitError_1.GitError(`Failed to stage files: ${result.out}`);
94
+ console.log(`\n\x1b[32m✔ Staged ${toStage.length} file(s):\x1b[0m`);
95
+ toStage.forEach((f) => console.log(` ${f}`));
16
96
  }
17
97
  }
18
98
  exports.add = new Add();
package/dist/commit.js CHANGED
@@ -17,7 +17,7 @@ class Commit {
17
17
  });
18
18
  const scopeAnswer = await (0, prompts_1.rawlist)({
19
19
  message: 'Select commit scope:',
20
- choices: ['branch', 'e2e', 'api', 'omit', 'custom'],
20
+ choices: ['branch', 'e2e', 'testcases', 'api', 'omit', 'custom'],
21
21
  });
22
22
  let scope = '';
23
23
  if (scopeAnswer === 'branch') {
@@ -30,6 +30,9 @@ class Commit {
30
30
  else if (scopeAnswer === 'custom') {
31
31
  scope = wrapScope(await (0, prompts_1.input)({ message: 'Provide custom scope:' }));
32
32
  }
33
+ else if (scopeAnswer !== 'omit') {
34
+ scope = wrapScope(scopeAnswer);
35
+ }
33
36
  const description = await (0, prompts_1.input)({
34
37
  message: 'Provide commit description:',
35
38
  transformer: (v) => `\x1b[32m[${v.length}/75]\x1b[0m ${v}`,
package/dist/git.js CHANGED
@@ -4,7 +4,7 @@ exports.git = void 0;
4
4
  const child_process_1 = require("child_process");
5
5
  class Git {
6
6
  call(workdir, args) {
7
- return (0, child_process_1.execFileSync)('git', args, { cwd: workdir, encoding: 'utf8', stdio: 'pipe' }).trim();
7
+ return (0, child_process_1.execFileSync)('git', args, { cwd: workdir, encoding: 'utf8', stdio: 'pipe' }).replace(/\r?\n$/, '');
8
8
  }
9
9
  ok(workdir, args) {
10
10
  try {
package/dist/remov.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "git-interactive-vlaqa",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Interactive Git CLI",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
- "build": "tsc",
7
+ "build": "tsc -p tsconfig.build.json",
8
8
  "prepublishOnly": "npm run build",
9
- "test": "echo \"Error: no test specified\" && exit 1"
9
+ "test": "jest"
10
10
  },
11
11
  "files": [
12
12
  "dist",
13
13
  "bin"
14
14
  ],
15
15
  "bin": {
16
- "gicm": "./bin/gicm",
17
- "gia": "./bin/gia"
16
+ "gicm": "bin/gicm",
17
+ "gia": "bin/gia"
18
18
  },
19
19
  "repository": {
20
20
  "type": "git",
@@ -32,10 +32,13 @@
32
32
  "@eslint/json": "^2.0.0",
33
33
  "@eslint/markdown": "^8.0.2",
34
34
  "@stylistic/eslint-plugin": "^5.10.0",
35
- "@types/node": "^25.9.3",
35
+ "@types/jest": "^30.0.0",
36
+ "@types/node": "^25.9.4",
36
37
  "eslint": "^10.5.0",
37
38
  "globals": "^17.6.0",
39
+ "jest": "^30.4.2",
38
40
  "jiti": "^2.7.0",
41
+ "ts-jest": "^29.4.11",
39
42
  "typescript": "^6.0.3",
40
43
  "typescript-eslint": "^8.61.0"
41
44
  },