@junheep/gwt 0.1.0 → 0.1.1

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 (3) hide show
  1. package/README.md +7 -4
  2. package/bin/gwt.mjs +54 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -141,7 +141,8 @@ pnpm install --frozen-lockfile
141
141
 
142
142
  Hook paths in user config are resolved relative to the directory containing
143
143
  `config.json`; hook paths in `.gwt.json` are resolved relative to the target
144
- worktree. Both run with the target worktree as their working directory.
144
+ worktree. Both run with the target worktree as their working directory, and
145
+ their standard output and errors are streamed directly to the terminal.
145
146
 
146
147
  Hooks in user config are trusted because the user added them directly. Hooks
147
148
  from a committed `.gwt.json` require explicit trust because they execute
@@ -184,6 +185,8 @@ arrow keys, `j`/`k`, or Ctrl-n/Ctrl-p to move; press 1–9 to select a numbered
184
185
  row immediately; or press `/` to filter by branch, ID, or path. Enter switches
185
186
  to the selected worktree. Escape leaves filter mode or cancels the picker.
186
187
 
187
- `gwt remove` refuses dirty worktrees. It removes the branch only when
188
- `git branch -d` considers the deletion safe. `--discard --yes` explicitly
189
- allows dirty worktree removal and forced branch deletion.
188
+ `gwt remove` refuses dirty worktrees and first tries to delete the branch with
189
+ `git branch -d`. If Git rejects safe deletion, an interactive terminal asks
190
+ whether to force-delete the branch; non-interactive use keeps it and prints a
191
+ command for deleting it later. `--discard --yes` explicitly allows dirty
192
+ worktree removal and forced branch deletion.
package/bin/gwt.mjs CHANGED
@@ -496,14 +496,14 @@ function runHook(name, repository, configDocument, worktree, metadata) {
496
496
  GWT_BRANCH: context.branch,
497
497
  ...Object.fromEntries(Object.entries(context.ports).map(([key, value]) => [key, String(value)])),
498
498
  }
499
+ console.log(`Running ${name}...`)
499
500
  const result = run(hook.path, [], {
500
501
  cwd: context.path,
501
502
  env,
502
503
  input: `${JSON.stringify(context)}\n`,
503
504
  allowFailure: true,
505
+ stdio: ["pipe", "inherit", "inherit"],
504
506
  })
505
- if (result.stdout) process.stdout.write(result.stdout)
506
- if (result.stderr) process.stderr.write(result.stderr)
507
507
  if (result.status !== 0) throw new CliError(`${name} failed with status ${result.status}`)
508
508
  }
509
509
 
@@ -817,18 +817,46 @@ function worktreeRows(repository) {
817
817
  return rows
818
818
  }
819
819
 
820
+ function displayWidth(value) {
821
+ let width = 0
822
+ for (const character of value.normalize("NFC")) {
823
+ const codePoint = character.codePointAt(0)
824
+ if (/\p{Mark}/u.test(character) || codePoint === 0x200d) continue
825
+ const fullWidth = codePoint >= 0x1100 && (
826
+ codePoint <= 0x115f
827
+ || codePoint === 0x2329
828
+ || codePoint === 0x232a
829
+ || (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f)
830
+ || (codePoint >= 0xac00 && codePoint <= 0xd7a3)
831
+ || (codePoint >= 0xf900 && codePoint <= 0xfaff)
832
+ || (codePoint >= 0xfe10 && codePoint <= 0xfe19)
833
+ || (codePoint >= 0xfe30 && codePoint <= 0xfe6f)
834
+ || (codePoint >= 0xff00 && codePoint <= 0xff60)
835
+ || (codePoint >= 0xffe0 && codePoint <= 0xffe6)
836
+ || (codePoint >= 0x1f300 && codePoint <= 0x1faff)
837
+ || (codePoint >= 0x20000 && codePoint <= 0x3fffd)
838
+ )
839
+ width += fullWidth ? 2 : 1
840
+ }
841
+ return width
842
+ }
843
+
844
+ function padDisplay(value, width) {
845
+ return `${value}${" ".repeat(Math.max(0, width - displayWidth(value)))}`
846
+ }
847
+
820
848
  function commandList(args) {
821
849
  if (args.length > 0) throw new CliError("Usage: gwt list")
822
850
  const repository = discoverRepository()
823
851
  const rows = worktreeRows(repository)
824
852
  const widths = {
825
- id: Math.max(2, ...rows.map((row) => row.id.length)),
826
- branch: Math.max(6, ...rows.map((row) => row.branch.length)),
827
- setup: Math.max(5, ...rows.map((row) => row.setup.length)),
853
+ id: Math.max(2, ...rows.map((row) => displayWidth(row.id))),
854
+ branch: Math.max(6, ...rows.map((row) => displayWidth(row.branch))),
855
+ setup: Math.max(5, ...rows.map((row) => displayWidth(row.setup))),
828
856
  }
829
- console.log(` ${"ID".padEnd(widths.id)} ${"BRANCH".padEnd(widths.branch)} ${"SETUP".padEnd(widths.setup)} PATH`)
857
+ console.log(` ${padDisplay("ID", widths.id)} ${padDisplay("BRANCH", widths.branch)} ${padDisplay("SETUP", widths.setup)} PATH`)
830
858
  for (const row of rows) {
831
- console.log(`${row.current ? "*" : " "} ${row.id.padEnd(widths.id)} ${row.branch.padEnd(widths.branch)} ${row.setup.padEnd(widths.setup)} ${row.path}`)
859
+ console.log(`${row.current ? "*" : " "} ${padDisplay(row.id, widths.id)} ${padDisplay(row.branch, widths.branch)} ${padDisplay(row.setup, widths.setup)} ${row.path}`)
832
860
  }
833
861
  }
834
862
 
@@ -890,6 +918,7 @@ async function commandRemove(args) {
890
918
  const removeArgs = ["worktree", "remove"]
891
919
  if (options.discard) removeArgs.push("--force")
892
920
  removeArgs.push(targetPath)
921
+ console.log(`Removing worktree ${metadata?.id ?? targetPath}...`)
893
922
  git(removeArgs, repository.primaryPath)
894
923
  if (metadata?.metadataPath && existsSync(metadata.metadataPath)) unlinkSync(metadata.metadataPath)
895
924
 
@@ -897,7 +926,20 @@ async function commandRemove(args) {
897
926
  if (worktree.branch && !options["keep-branch"]) {
898
927
  const deleteArgs = ["branch", options.discard ? "-D" : "-d", "--", worktree.branch]
899
928
  const result = git(deleteArgs, repository.primaryPath, { allowFailure: true })
900
- branchMessage = result.status === 0 ? `Deleted branch: ${worktree.branch}` : `Kept branch: ${worktree.branch}`
929
+ if (result.status === 0) {
930
+ branchMessage = `Deleted branch: ${worktree.branch}`
931
+ } else if (!options.discard) {
932
+ console.log(`Branch '${worktree.branch}' could not be deleted safely.`)
933
+ const forceDelete = await ask("Force-delete the branch? [y/N] ")
934
+ if (forceDelete) {
935
+ git(["branch", "-D", "--", worktree.branch], repository.primaryPath)
936
+ branchMessage = `Deleted branch: ${worktree.branch}`
937
+ } else {
938
+ branchMessage = `Kept branch: ${worktree.branch}\nDelete later: git branch -D -- ${worktree.branch}`
939
+ }
940
+ } else {
941
+ branchMessage = `Kept branch: ${worktree.branch}`
942
+ }
901
943
  } else if (worktree.branch) branchMessage = `Kept branch: ${worktree.branch}`
902
944
 
903
945
  console.log(`Removed worktree: ${metadata?.id ?? targetPath}`)
@@ -1311,9 +1353,10 @@ Options:
1311
1353
 
1312
1354
  Behavior:
1313
1355
  Without --discard, dirty worktrees are rejected and branches are deleted only
1314
- when 'git branch -d' considers deletion safe. The primary worktree cannot be
1315
- removed. Removing the current worktree returns an integrated shell to the
1316
- primary worktree.
1356
+ when 'git branch -d' considers deletion safe. If safe deletion fails, an
1357
+ interactive terminal asks whether to force-delete the branch; non-interactive
1358
+ use keeps it. The primary worktree cannot be removed. Removing the current
1359
+ worktree returns an integrated shell to the primary worktree.
1317
1360
 
1318
1361
  Examples:
1319
1362
  gwt remove
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junheep/gwt",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Lightweight native Git worktree workflows",
5
5
  "license": "MIT",
6
6
  "author": "Junhee Park",