@samaksh/git-worker 0.2.0 → 0.2.2

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/cli/index.js CHANGED
@@ -7,7 +7,7 @@ const program = new Command();
7
7
  program
8
8
  .name("git-worker")
9
9
  .description("Safe, composable CLI/TUI for applying reviewed file changes across many Git branches")
10
- .version("0.2.0")
10
+ .version("0.2.2")
11
11
  .option("-r, --repo <path>", "Repository path or remote URL (default: current directory)")
12
12
  .option("-c, --cached", "Select from previously cloned and cached repositories")
13
13
  .option("-b, --branch <pattern>", "Branch pattern — glob (uat/*) or regex (/uat.*/)")
@@ -6,7 +6,7 @@ import { join, resolve, normalize } from "path";
6
6
  import { simpleGit, CheckRepoActions } from "simple-git";
7
7
  import { homedir } from "os";
8
8
  import { createHash } from "crypto";
9
- const GIT_WORKER_VERSION = "0.2.0";
9
+ const GIT_WORKER_VERSION = "0.2.2";
10
10
  const LOCK_FILE = ".git-worker-lock";
11
11
  /**
12
12
  * Extract a human-readable repository name from a remote URL or file path.
@@ -1,6 +1,7 @@
1
1
  import { ok, err } from "../core/types.js";
2
2
  import { workspaceError, inputCancelledError } from "../core/errors.js";
3
- import { clack, sectionHeader, statusLine, sym } from "../utils/ui.js";
3
+ import { clack, sectionHeader, statusLine } from "../utils/ui.js";
4
+ import inquirer from "inquirer";
4
5
  import chalk from "chalk";
5
6
  /**
6
7
  * Matches a branch name against a pattern.
@@ -99,45 +100,44 @@ export const selectTargetsStage = async (context, deps) => {
99
100
  label: `➜ Confirm and proceed with ${selectedCount} branch(es)`,
100
101
  hint: "proceed to target file selection",
101
102
  });
102
- options.push({
103
- value: "list",
104
- label: "📋 Search & select branches from list",
105
- hint: "type to filter, Space/Tab to toggle",
106
- });
107
103
  options.push({
108
104
  value: "pattern",
109
105
  label: "🔍 Filter by pattern or regex",
110
106
  hint: "glob (uat/*) or regex (/uat.*/)",
111
107
  });
112
108
  options.push({
113
- value: "invert",
114
- label: `🔄 Invert selection (select the other ${totalCount - selectedCount} branches)`,
115
- hint: "flip current selection",
109
+ value: "list",
110
+ label: "📋 Select from branch list",
111
+ hint: "Space: toggle • a: select all • i: invert",
116
112
  });
117
113
  options.push({
118
114
  value: "all",
119
- label: `🌟 Select ALL branches (${totalCount} branches)`,
115
+ label: `🌟 Select all (a) — all ${totalCount} branches`,
120
116
  });
121
- }
122
- else {
123
117
  options.push({
124
- value: "list",
125
- label: "📋 Search & select branches from list",
126
- hint: "type to filter, Space/Tab to toggle",
118
+ value: "invert",
119
+ label: `🔄 Invert selection (i) — select other ${totalCount - selectedCount} branch(es)`,
127
120
  });
121
+ }
122
+ else {
128
123
  options.push({
129
124
  value: "pattern",
130
125
  label: "🔍 Filter by pattern or regex",
131
- hint: "glob (uat/*) or regex (/uat.*/)",
126
+ hint: "glob (uat/*) or regex (/uat.*/) — then select/review",
127
+ });
128
+ options.push({
129
+ value: "list",
130
+ label: "📋 Select from branch list",
131
+ hint: "Space: toggle • a: select all • i: invert",
132
132
  });
133
133
  options.push({
134
134
  value: "all",
135
- label: `🌟 Select ALL branches (${totalCount} branches)`,
135
+ label: `🌟 Select all (a) — all ${totalCount} branches`,
136
136
  hint: "select every branch in repository",
137
137
  });
138
138
  options.push({
139
139
  value: "invert",
140
- label: `🔄 Invert selection (select all ${totalCount} branches)`,
140
+ label: `🔄 Invert selection (i) — select all ${totalCount} branches`,
141
141
  });
142
142
  }
143
143
  const previewText = selectedCount > 0
@@ -188,7 +188,6 @@ export const selectTargetsStage = async (context, deps) => {
188
188
  clack.log.warn(`Pattern '${pattern}' matched 0 of ${totalCount} branches.`);
189
189
  continue;
190
190
  }
191
- selectedNames = new Set(matched.map((b) => b.name));
192
191
  statusLine("ok", `Pattern '${pattern}' matched ${matched.length} branch(es):`);
193
192
  for (const b of matched.slice(0, 5)) {
194
193
  console.log(chalk.gray(` • ${b.name}`));
@@ -196,42 +195,128 @@ export const selectTargetsStage = async (context, deps) => {
196
195
  if (matched.length > 5) {
197
196
  console.log(chalk.gray(` ... and ${matched.length - 5} more`));
198
197
  }
199
- continue;
198
+ // Open interactive checkbox selection with the filtered branches
199
+ try {
200
+ const { picked } = await inquirer.prompt([
201
+ {
202
+ type: "checkbox",
203
+ name: "picked",
204
+ message: `Select from matched branches (${pattern}):`,
205
+ pageSize: 15,
206
+ choices: matched.map((b) => ({
207
+ name: `${b.name} (${b.isRemote ? `remote/${b.remote}` : "local"} ${b.sha.slice(0, 8)})`,
208
+ value: b.name,
209
+ checked: true,
210
+ })),
211
+ },
212
+ ]);
213
+ selectedNames = new Set(picked);
214
+ statusLine("ok", `Selection updated: ${selectedNames.size} branch(es) selected`);
215
+ if (selectedNames.size > 0) {
216
+ const proceed = await clack.confirm({
217
+ message: `Proceed with ${selectedNames.size} selected branch(es)?`,
218
+ initialValue: true,
219
+ });
220
+ if (!clack.isCancel(proceed) && proceed) {
221
+ break;
222
+ }
223
+ }
224
+ continue;
225
+ }
226
+ catch (e) {
227
+ if (e.isTtyError ||
228
+ e.name === "ExitPromptError" ||
229
+ e.message?.includes("force closed") ||
230
+ e.message?.includes("User force closed")) {
231
+ return err(inputCancelledError("Branch selection cancelled"));
232
+ }
233
+ continue;
234
+ }
200
235
  }
201
236
  if (choice === "list") {
202
- const promptFn = typeof clack.autocompleteMultiselect === "function"
203
- ? clack.autocompleteMultiselect
204
- : clack.multiselect;
205
- const picked = await promptFn({
206
- message: "Select branches (type to filter, Space/Tab to toggle, Enter to confirm):",
207
- placeholder: "Type branch name or pattern…",
208
- options: allBranches.map((b) => ({
209
- value: b.name,
210
- label: `${sym.branch} ${b.name}`,
211
- hint: b.isRemote
212
- ? `remote/${b.remote} ${b.sha.slice(0, 8)}`
213
- : `local ${b.sha.slice(0, 8)}`,
214
- })),
215
- filter: (input, option) => {
216
- if (!input || !input.trim())
217
- return true;
218
- const query = input.trim().toLowerCase();
219
- const val = String(option.value).toLowerCase();
220
- if (query.startsWith("/") || query.includes("*") || query.includes("?")) {
221
- return matchesBranchPattern(val, query);
237
+ try {
238
+ const FILTER_ACTION = "__ACTION_FILTER_PATTERN__";
239
+ const choices = [
240
+ {
241
+ name: chalk.cyan("🔍 [Filter this list by pattern / regex...]"),
242
+ value: FILTER_ACTION,
243
+ checked: false,
244
+ },
245
+ new inquirer.Separator(),
246
+ ...allBranches.map((b) => ({
247
+ name: `${b.name} (${b.isRemote ? `remote/${b.remote}` : "local"} ${b.sha.slice(0, 8)})`,
248
+ value: b.name,
249
+ checked: selectedNames.has(b.name),
250
+ })),
251
+ ];
252
+ const { picked } = await inquirer.prompt([
253
+ {
254
+ type: "checkbox",
255
+ name: "picked",
256
+ message: "Select branches to process:",
257
+ pageSize: 15,
258
+ choices,
259
+ },
260
+ ]);
261
+ if (picked.includes(FILTER_ACTION)) {
262
+ const patternInput = await clack.text({
263
+ message: "Enter glob pattern (e.g. uat/*) or regex (e.g. /uat.*/):",
264
+ placeholder: "/uat.*/ or uat/*",
265
+ validate: (v) => {
266
+ if (!v || v.trim().length === 0) {
267
+ return "Please enter a pattern or regex";
268
+ }
269
+ },
270
+ });
271
+ if (!clack.isCancel(patternInput)) {
272
+ const pattern = patternInput.trim();
273
+ const matched = allBranches.filter((b) => matchesBranchPattern(b.name, pattern));
274
+ if (matched.length === 0) {
275
+ clack.log.warn(`Pattern '${pattern}' matched 0 of ${totalCount} branches.`);
276
+ }
277
+ else {
278
+ const { subPicked } = await inquirer.prompt([
279
+ {
280
+ type: "checkbox",
281
+ name: "subPicked",
282
+ message: `Select from matched branches (${pattern}):`,
283
+ pageSize: 15,
284
+ choices: matched.map((b) => ({
285
+ name: `${b.name} (${b.isRemote ? `remote/${b.remote}` : "local"} ${b.sha.slice(0, 8)})`,
286
+ value: b.name,
287
+ checked: true,
288
+ })),
289
+ },
290
+ ]);
291
+ selectedNames = new Set(subPicked);
292
+ statusLine("ok", `Selection updated: ${selectedNames.size} branch(es) selected`);
293
+ if (selectedNames.size > 0) {
294
+ const proceed = await clack.confirm({
295
+ message: `Proceed with ${selectedNames.size} selected branch(es)?`,
296
+ initialValue: true,
297
+ });
298
+ if (!clack.isCancel(proceed) && proceed) {
299
+ break;
300
+ }
301
+ }
302
+ continue;
303
+ }
222
304
  }
223
- return val.includes(query);
224
- },
225
- initialValues: Array.from(selectedNames),
226
- required: false,
227
- maxItems: 12,
228
- });
229
- if (clack.isCancel(picked)) {
305
+ }
306
+ const actualPicked = picked.filter((p) => p !== FILTER_ACTION);
307
+ selectedNames = new Set(actualPicked);
308
+ statusLine("ok", `Selection updated: ${selectedNames.size} branch(es) selected`);
309
+ continue;
310
+ }
311
+ catch (e) {
312
+ if (e.isTtyError ||
313
+ e.name === "ExitPromptError" ||
314
+ e.message?.includes("force closed") ||
315
+ e.message?.includes("User force closed")) {
316
+ return err(inputCancelledError("Branch selection cancelled"));
317
+ }
230
318
  continue;
231
319
  }
232
- selectedNames = new Set(picked);
233
- statusLine("ok", `Selection updated: ${selectedNames.size} branch(es) selected`);
234
- continue;
235
320
  }
236
321
  }
237
322
  branches = allBranches.filter((b) => selectedNames.has(b.name));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samaksh/git-worker",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Safe, composable TypeScript CLI/TUI for applying reviewed file changes across many Git branches",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,6 +17,9 @@
17
17
  "README.md",
18
18
  "docs"
19
19
  ],
20
+ "licenses": [
21
+ "MIT"
22
+ ],
20
23
  "scripts": {
21
24
  "build": "tsc",
22
25
  "prepublishOnly": "npm run build",