workshell 0.0.7 → 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +139 -36
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7098,37 +7098,78 @@ function newCommand(branchName, fromBranch) {
7098
7098
  }
7099
7099
 
7100
7100
  // commands/open.ts
7101
+ import { execSync as execSync3 } from "child_process";
7101
7102
  import { mkdirSync as mkdirSync4 } from "fs";
7102
7103
  import { basename as basename4, dirname as dirname4, join as join4 } from "path";
7103
- function openCommand(branch) {
7104
- if (isInsideWorktree()) {
7105
- const currentBranch = getCurrentBranch();
7106
- console.log();
7107
- console.log(warn(`You're inside a worktree (branch: ${cyan(currentBranch)})`));
7108
- console.log(` This will nest subshells (subshell inside subshell).`);
7109
- console.log();
7110
- if (!confirm("Continue?")) {
7111
- process.exit(0);
7104
+ function isGhInstalled() {
7105
+ try {
7106
+ execSync3("gh --version", { stdio: "pipe", encoding: "utf-8" });
7107
+ return true;
7108
+ } catch {
7109
+ return false;
7110
+ }
7111
+ }
7112
+ function isGhAuthenticated() {
7113
+ try {
7114
+ execSync3("gh auth status", { stdio: "pipe", encoding: "utf-8" });
7115
+ return true;
7116
+ } catch {
7117
+ return false;
7118
+ }
7119
+ }
7120
+ function getPRInfo(prRef) {
7121
+ try {
7122
+ const output = execSync3(
7123
+ `gh pr view ${prRef} --json number,headRefName`,
7124
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
7125
+ );
7126
+ return JSON.parse(output);
7127
+ } catch {
7128
+ return null;
7129
+ }
7130
+ }
7131
+ function fetchPRBranch(prRef, prInfo) {
7132
+ try {
7133
+ execSync3(`git fetch origin ${prInfo.headRefName}:${prInfo.headRefName}`, { stdio: "pipe" });
7134
+ return true;
7135
+ } catch {
7136
+ }
7137
+ const currentBranch = getCurrentBranch();
7138
+ try {
7139
+ execSync3(`gh pr checkout ${prRef}`, { stdio: "pipe" });
7140
+ execSync3(`git checkout "${currentBranch}"`, { stdio: "pipe" });
7141
+ return true;
7142
+ } catch {
7143
+ try {
7144
+ execSync3(`git checkout "${currentBranch}"`, { stdio: "pipe" });
7145
+ } catch {
7112
7146
  }
7113
- console.log();
7114
7147
  }
7115
- if (!branchExists(branch)) {
7116
- console.error(`Error: branch '${branch}' not found`);
7117
- process.exit(1);
7148
+ try {
7149
+ execSync3(`git fetch origin pull/${prInfo.number}/head:${prInfo.headRefName}`, { stdio: "pipe" });
7150
+ return true;
7151
+ } catch {
7152
+ return false;
7118
7153
  }
7154
+ }
7155
+ function openBranch(branch, prNumber) {
7119
7156
  const parentBranch = getCurrentBranch();
7157
+ const mainWorktree = getMainWorktree();
7120
7158
  const existingWorktreePath = getWorktreeForBranch(branch);
7121
7159
  if (existingWorktreePath) {
7122
7160
  const worktreeId2 = getWorktreeId(existingWorktreePath);
7123
- const mainWorktree2 = getMainWorktree();
7124
7161
  console.log();
7125
- console.log(success(bold(branch)), dim(`(existing worktree)`));
7162
+ if (prNumber) {
7163
+ console.log(success(bold(`PR #${prNumber}`)), dim(`branch: ${cyan(branch)} (existing worktree)`));
7164
+ } else {
7165
+ console.log(success(bold(branch)), dim(`(existing worktree)`));
7166
+ }
7126
7167
  console.log(dim("Type 'wk close' to return."));
7127
7168
  console.log();
7128
7169
  spawnShell(existingWorktreePath, {
7129
7170
  branch,
7130
7171
  worktreePath: existingWorktreePath,
7131
- repoPath: mainWorktree2
7172
+ repoPath: mainWorktree
7132
7173
  });
7133
7174
  console.log();
7134
7175
  console.log(success(`Back in ${bold(parentBranch)}`));
@@ -7138,7 +7179,6 @@ function openCommand(branch) {
7138
7179
  return;
7139
7180
  }
7140
7181
  const store = loadStore();
7141
- const mainWorktree = getMainWorktree();
7142
7182
  const repoName = basename4(mainWorktree);
7143
7183
  const worktreeId = `${repoName}@${slugify(branch)}`;
7144
7184
  const worktreePath = join4(getWorktreesDir(), worktreeId);
@@ -7150,8 +7190,13 @@ function openCommand(branch) {
7150
7190
  });
7151
7191
  saveStore(store);
7152
7192
  console.log();
7153
- console.log(success(bold(branch)));
7154
- console.log(dim(`Opened branch in ephemeral subshell`));
7193
+ if (prNumber) {
7194
+ console.log(success(bold(`PR #${prNumber}`)), dim(`branch: ${cyan(branch)}`));
7195
+ console.log(dim(`Opened PR in ephemeral subshell`));
7196
+ } else {
7197
+ console.log(success(bold(branch)));
7198
+ console.log(dim(`Opened branch in ephemeral subshell`));
7199
+ }
7155
7200
  console.log(dim("Type 'exit' or 'wk close' to return."));
7156
7201
  console.log();
7157
7202
  spawnShell(worktreePath, {
@@ -7165,6 +7210,55 @@ function openCommand(branch) {
7165
7210
  autoCleanupWorktree(worktreeId, worktreePath, currentStore, saveStore);
7166
7211
  console.log();
7167
7212
  }
7213
+ function openCommand(ref) {
7214
+ if (isInsideWorktree()) {
7215
+ const currentBranch = getCurrentBranch();
7216
+ console.log();
7217
+ console.log(warn(`You're inside a worktree (branch: ${cyan(currentBranch)})`));
7218
+ console.log(` This will nest subshells (subshell inside subshell).`);
7219
+ console.log();
7220
+ if (!confirm("Continue?")) {
7221
+ process.exit(0);
7222
+ }
7223
+ console.log();
7224
+ }
7225
+ if (branchExists(ref)) {
7226
+ openBranch(ref);
7227
+ return;
7228
+ }
7229
+ if (!isGhInstalled()) {
7230
+ console.error(fail(`Branch '${ref}' not found.`));
7231
+ console.error(dim(" Install GitHub CLI (gh) to open PRs: https://cli.github.com/"));
7232
+ process.exit(1);
7233
+ }
7234
+ if (!isGhAuthenticated()) {
7235
+ console.error(fail(`Branch '${ref}' not found.`));
7236
+ console.error(dim(" Run 'gh auth login' to open PRs from GitHub."));
7237
+ process.exit(1);
7238
+ }
7239
+ const prInfo = getPRInfo(ref);
7240
+ if (!prInfo) {
7241
+ console.error(fail(`'${ref}' is not a local branch or GitHub PR.`));
7242
+ process.exit(1);
7243
+ }
7244
+ const branch = prInfo.headRefName;
7245
+ const prNumber = prInfo.number;
7246
+ if (branchExists(branch)) {
7247
+ console.log(dim(`PR #${prNumber} \u2192 ${branch}`));
7248
+ openBranch(branch, prNumber);
7249
+ return;
7250
+ }
7251
+ console.log(dim(`Fetching PR #${prNumber}...`));
7252
+ if (!fetchPRBranch(ref, prInfo)) {
7253
+ console.error(fail(`Failed to fetch branch '${branch}' for PR #${prNumber}`));
7254
+ process.exit(1);
7255
+ }
7256
+ if (!branchExists(branch)) {
7257
+ console.error(fail(`Failed to fetch branch '${branch}' for PR #${prNumber}`));
7258
+ process.exit(1);
7259
+ }
7260
+ openBranch(branch, prNumber);
7261
+ }
7168
7262
 
7169
7263
  // commands/ls.ts
7170
7264
  var import_table = __toESM(require_src(), 1);
@@ -8015,7 +8109,7 @@ function lsCommand(plain = false) {
8015
8109
  }
8016
8110
 
8017
8111
  // commands/rm.ts
8018
- import { execSync as execSync3 } from "child_process";
8112
+ import { execSync as execSync4 } from "child_process";
8019
8113
  import { resolve as resolve2 } from "path";
8020
8114
  function rmCommand(branch, force = false) {
8021
8115
  const mainWorktree = getMainWorktree();
@@ -8046,8 +8140,8 @@ function rmCommand(branch, force = false) {
8046
8140
  }
8047
8141
  if (isDirty && force) {
8048
8142
  try {
8049
- execSync3(`git -C "${worktreePath}" reset --hard HEAD`, { stdio: "ignore" });
8050
- execSync3(`git -C "${worktreePath}" clean -fd`, { stdio: "ignore" });
8143
+ execSync4(`git -C "${worktreePath}" reset --hard HEAD`, { stdio: "ignore" });
8144
+ execSync4(`git -C "${worktreePath}" clean -fd`, { stdio: "ignore" });
8051
8145
  } catch {
8052
8146
  }
8053
8147
  }
@@ -8065,7 +8159,7 @@ function rmCommand(branch, force = false) {
8065
8159
  }
8066
8160
 
8067
8161
  // commands/status.ts
8068
- import { execSync as execSync4 } from "child_process";
8162
+ import { execSync as execSync5 } from "child_process";
8069
8163
  import { existsSync as existsSync4, readdirSync as readdirSync3 } from "fs";
8070
8164
  import { resolve as resolve3 } from "path";
8071
8165
  function getDetailedStatus(path) {
@@ -8073,7 +8167,7 @@ function getDetailedStatus(path) {
8073
8167
  return [];
8074
8168
  }
8075
8169
  try {
8076
- const status = execSync4(`git -C "${path}" status --short`, { encoding: "utf-8" }).trim();
8170
+ const status = execSync5(`git -C "${path}" status --short`, { encoding: "utf-8" }).trim();
8077
8171
  if (!status) {
8078
8172
  return [];
8079
8173
  }
@@ -8139,17 +8233,17 @@ function statusCommand() {
8139
8233
  }
8140
8234
 
8141
8235
  // commands/preclose.ts
8142
- import { execSync as execSync5 } from "child_process";
8236
+ import { execSync as execSync6 } from "child_process";
8143
8237
  function precloseCommand(force) {
8144
8238
  if (!hasUncommittedChanges()) {
8145
8239
  process.exit(0);
8146
8240
  }
8147
8241
  if (force) {
8148
8242
  try {
8149
- execSync5("git reset --hard HEAD", { stdio: "ignore" });
8150
- execSync5("git clean -ffd", { stdio: "ignore" });
8151
- execSync5("git submodule foreach --recursive 'git reset --hard HEAD; git clean -ffd'", { stdio: "ignore" });
8152
- execSync5("git submodule update --init --recursive --force", { stdio: "ignore" });
8243
+ execSync6("git reset --hard HEAD", { stdio: "ignore" });
8244
+ execSync6("git clean -ffd", { stdio: "ignore" });
8245
+ execSync6("git submodule foreach --recursive 'git reset --hard HEAD; git clean -ffd'", { stdio: "ignore" });
8246
+ execSync6("git submodule update --init --recursive --force", { stdio: "ignore" });
8153
8247
  } catch {
8154
8248
  }
8155
8249
  process.exit(0);
@@ -8222,7 +8316,7 @@ function promptChoice() {
8222
8316
  }
8223
8317
 
8224
8318
  // index.ts
8225
- var VERSION = "0.0.7";
8319
+ var VERSION = "0.2.0";
8226
8320
  function printHelp() {
8227
8321
  const dim2 = import_picocolors4.default.dim;
8228
8322
  const cyan2 = import_picocolors4.default.cyan;
@@ -8233,7 +8327,7 @@ ${import_picocolors4.default.bold("Usage:")} ${cyan2("wk")} ${dim2("<command>")}
8233
8327
 
8234
8328
  ${import_picocolors4.default.bold("Commands:")}
8235
8329
  ${green2("new")} ${dim2("[branch]")} Create a branch and open it ${dim2("[--from <branch>]")}
8236
- ${green2("open")} ${dim2("<branch>")} Open a branch in an ephemeral subshell
8330
+ ${green2("open")} ${dim2("<ref>")} Open a branch or PR in an ephemeral subshell
8237
8331
  ${green2("close")} Exit current subshell
8238
8332
  ${green2("ls")} List open branches
8239
8333
  ${green2("status")} Show current branch
@@ -8298,15 +8392,24 @@ ${import_picocolors4.default.bold("Description:")}
8298
8392
  function printOpenHelp() {
8299
8393
  const dim2 = import_picocolors4.default.dim;
8300
8394
  const cyan2 = import_picocolors4.default.cyan;
8301
- console.log(`${import_picocolors4.default.bold("wk open")} - Open a branch in an ephemeral subshell
8395
+ console.log(`${import_picocolors4.default.bold("wk open")} - Open a branch or PR in an ephemeral subshell
8302
8396
 
8303
- ${import_picocolors4.default.bold("Usage:")} ${cyan2("wk open")} ${dim2("<branch>")}
8397
+ ${import_picocolors4.default.bold("Usage:")} ${cyan2("wk open")} ${dim2("<ref>")}
8304
8398
 
8305
8399
  ${import_picocolors4.default.bold("Arguments:")}
8306
- ${dim2("<branch>")} Branch name ${dim2("(required)")}
8400
+ ${dim2("<ref>")} Branch name, PR number, or PR URL ${dim2("(required)")}
8401
+
8402
+ ${import_picocolors4.default.bold("Examples:")}
8403
+ ${dim2("$")} ${cyan2("wk open feature-branch")} ${dim2("# Open local branch")}
8404
+ ${dim2("$")} ${cyan2("wk open 123")} ${dim2("# Open PR #123")}
8405
+ ${dim2("$")} ${cyan2("wk open https://github.com/o/r/pull/123")}
8307
8406
 
8308
8407
  ${import_picocolors4.default.bold("Description:")}
8309
- Opens the specified branch in an ephemeral subshell.
8408
+ Opens the specified ref in an ephemeral subshell.
8409
+
8410
+ First tries to open as a local branch. If not found, tries to fetch
8411
+ as a GitHub PR (requires gh CLI to be installed and authenticated).
8412
+
8310
8413
  If a worktree already exists for the branch, uses that.
8311
8414
  Otherwise, creates a new worktree automatically.
8312
8415
  Type 'wk close' to return.`);
@@ -8430,7 +8533,7 @@ switch (cmd) {
8430
8533
  process.exit(0);
8431
8534
  }
8432
8535
  if (!args[1]) {
8433
- console.error("Usage: wk open <branch>");
8536
+ console.error("Usage: wk open <branch|pr-number|pr-url>");
8434
8537
  process.exit(1);
8435
8538
  }
8436
8539
  openCommand(args[1]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workshell",
3
- "version": "0.0.7",
3
+ "version": "0.2.0",
4
4
  "description": "Agent- and human-friendly Git multitasking, powered by worktrees",
5
5
  "type": "module",
6
6
  "license": "MIT",