workshell 0.1.0 → 0.4.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.
- package/README.md +47 -5
- package/dist/index.js +287 -267
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,6 +68,7 @@ This approach has some nice properties.
|
|
|
68
68
|
|
|
69
69
|
- 🖥️ **Tab-local workspaces** — Normally a `git checkout`/`git switch` changes your active branch for all terminals. With workshells, you can functionality open branches *in the current tab only*.
|
|
70
70
|
- 🌳 **Full isolation** — Each workshell is isolated on disk, so the changes you make don't interfere anything else you're doing.
|
|
71
|
+
- 📦 **Instant setup** — Untracked files (`.env.*`, etc) are automatically copied using [copy-on-write](https://notes.billmill.org/blog/2024/03/How_I_use_git_worktrees.html). For JS projects, the package manager is auto-detected and `npm/yarn/pnpm/bun install` runs automatically.
|
|
71
72
|
- 🙅♂️ **Never stash again** — You can `wk open` a branch even with uncommitted changes. When you exit the subshell, things will be exactly the same as they were. ☕️
|
|
72
73
|
- **Consistent with branch semantics** — As with regular `git switch`, `wk close` won't let you close the subshell if you have unstaged/uncommitted changes. This is a feature, not a bug! Regular worktrees make it easy to lose your work in a forgotten corner of your file system.
|
|
73
74
|
- 🤖 **Agent-ready** — Spin up parallel workshells so multiple agents can work simultaneously without conflicts.
|
|
@@ -310,9 +311,48 @@ Choice (1/2): 1
|
|
|
310
311
|
|
|
311
312
|
<br />
|
|
312
313
|
|
|
314
|
+
## Automatic file copying
|
|
315
|
+
|
|
316
|
+
When you create a workshell, `wk` automatically copies all gitignored files from your repo root to the new worktree using **copy-on-write**. This means:
|
|
317
|
+
|
|
318
|
+
- `.env`, `.venv`, etc. are instantly available
|
|
319
|
+
- Copy-on-write is near-instant and uses zero extra disk space (until files are modified)
|
|
320
|
+
|
|
321
|
+
This works automatically on macOS (APFS) and Linux (Btrfs/XFS). On other filesystems, files are copied normally.
|
|
322
|
+
|
|
323
|
+
### JS package manager detection
|
|
324
|
+
|
|
325
|
+
For JavaScript projects, `wk` auto-detects your package manager from lockfiles:
|
|
326
|
+
|
|
327
|
+
| Lockfile | Package Manager |
|
|
328
|
+
|----------|-----------------|
|
|
329
|
+
| `pnpm-lock.yaml` | pnpm |
|
|
330
|
+
| `yarn.lock` | yarn |
|
|
331
|
+
| `bun.lockb` / `bun.lock` | bun |
|
|
332
|
+
| `package-lock.json` | npm |
|
|
333
|
+
|
|
334
|
+
When detected, `node_modules` is skipped during copying and `<pm> install` runs automatically. This is faster than copying large `node_modules` directories.
|
|
335
|
+
|
|
336
|
+
### Python package manager detection
|
|
337
|
+
|
|
338
|
+
For Python projects, `wk` auto-detects your package manager from lockfiles:
|
|
339
|
+
|
|
340
|
+
| Lockfile | Package Manager | Install Command |
|
|
341
|
+
|----------|-----------------|-----------------|
|
|
342
|
+
| `uv.lock` | uv | `uv sync` |
|
|
343
|
+
| `poetry.lock` | poetry | `poetry install` |
|
|
344
|
+
| `Pipfile.lock` | pipenv | `pipenv install` |
|
|
345
|
+
| `pdm.lock` | pdm | `pdm install` |
|
|
346
|
+
|
|
347
|
+
When detected, `.venv` and `venv` are skipped during copying and the install command runs automatically. Mixed JS+Python projects are supported: both ecosystems are detected and their installs run in sequence.
|
|
348
|
+
|
|
349
|
+
To disable auto-detection, add a custom `setup` script in `workshell.toml`.
|
|
350
|
+
|
|
351
|
+
<br />
|
|
352
|
+
|
|
313
353
|
## `workshell.toml`
|
|
314
354
|
|
|
315
|
-
You can configure `wk` with a `workshell.toml` file.
|
|
355
|
+
You can optionally configure `wk` with a `workshell.toml` file.
|
|
316
356
|
|
|
317
357
|
`wk` looks for config files in the following order:
|
|
318
358
|
|
|
@@ -323,13 +363,15 @@ You can configure `wk` with a `workshell.toml` file. This is useful for running
|
|
|
323
363
|
|
|
324
364
|
<br />
|
|
325
365
|
|
|
326
|
-
|
|
366
|
+
### Options
|
|
327
367
|
|
|
328
368
|
```toml
|
|
329
|
-
# setup script executed in subshell after initialization
|
|
330
|
-
#
|
|
369
|
+
# Optional: setup script executed in subshell after initialization
|
|
370
|
+
# Variable substitutions:
|
|
331
371
|
# `{{ branch }}` — The name of the opened branch, e.g. `feature/auth`
|
|
332
372
|
# `{{ repo_path }}` — The absolute path to main repo, e.g. `/path/to/repo`
|
|
333
373
|
# `{{ worktree_path }}` — The absolute path to worktree, e.g. `~/.workshell/worktrees/.../repo@feat`
|
|
334
|
-
setup = "
|
|
374
|
+
setup = "nvm use"
|
|
335
375
|
```
|
|
376
|
+
|
|
377
|
+
Note: Setting `setup` disables automatic package manager detection. If you want both, include the install command in your setup script.
|
package/dist/index.js
CHANGED
|
@@ -5783,6 +5783,7 @@ var require_src = __commonJS({
|
|
|
5783
5783
|
|
|
5784
5784
|
// index.ts
|
|
5785
5785
|
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
5786
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5786
5787
|
|
|
5787
5788
|
// commands/new.ts
|
|
5788
5789
|
import { basename as basename3, dirname as dirname3, join as join3 } from "path";
|
|
@@ -6604,7 +6605,7 @@ function loadConfig() {
|
|
|
6604
6605
|
|
|
6605
6606
|
// utils.ts
|
|
6606
6607
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
6607
|
-
import { execSync as execSync2, spawnSync } from "child_process";
|
|
6608
|
+
import { execSync as execSync2, execFileSync, spawnSync } from "child_process";
|
|
6608
6609
|
import { existsSync as existsSync2, readSync, openSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, rmSync, lstatSync, readdirSync, readlinkSync } from "fs";
|
|
6609
6610
|
import { basename as basename2, dirname as dirname2, join as join2 } from "path";
|
|
6610
6611
|
import { tmpdir } from "os";
|
|
@@ -6749,6 +6750,9 @@ function isInsideWorktree() {
|
|
|
6749
6750
|
return false;
|
|
6750
6751
|
}
|
|
6751
6752
|
}
|
|
6753
|
+
function isInsideWorkshell() {
|
|
6754
|
+
return process.env.WK_WORKSHELL === "1";
|
|
6755
|
+
}
|
|
6752
6756
|
function hasUncommittedChanges() {
|
|
6753
6757
|
try {
|
|
6754
6758
|
const status = execSync2("git status --porcelain", { encoding: "utf-8" }).trim();
|
|
@@ -6942,13 +6946,14 @@ end
|
|
|
6942
6946
|
# wk setup script
|
|
6943
6947
|
${setupCommand}
|
|
6944
6948
|
` : "";
|
|
6949
|
+
const workshellEnv = { ...process.env, WK_WORKSHELL: "1" };
|
|
6945
6950
|
if (shell.endsWith("zsh")) {
|
|
6946
6951
|
const tmpDir = join2(tmpdir(), `wk-${process.pid}`);
|
|
6947
6952
|
mkdirSync2(tmpDir, { recursive: true });
|
|
6948
6953
|
try {
|
|
6949
6954
|
writeFileSync2(join2(tmpDir, ".zshrc"), `[[ -f "$HOME/.zshrc" ]] && source "$HOME/.zshrc"
|
|
6950
6955
|
${zshScript}${setupSection}`);
|
|
6951
|
-
spawnSync(shell, ["-l"], { cwd, stdio: "inherit", env: { ...
|
|
6956
|
+
spawnSync(shell, ["-l"], { cwd, stdio: "inherit", env: { ...workshellEnv, ZDOTDIR: tmpDir } });
|
|
6952
6957
|
} finally {
|
|
6953
6958
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
6954
6959
|
}
|
|
@@ -6959,7 +6964,7 @@ ${zshScript}${setupSection}`);
|
|
|
6959
6964
|
const rcFile = join2(tmpDir, ".bashrc");
|
|
6960
6965
|
writeFileSync2(rcFile, `[[ -f "$HOME/.bashrc" ]] && source "$HOME/.bashrc"
|
|
6961
6966
|
${bashScript}${setupSection}`);
|
|
6962
|
-
spawnSync(shell, ["--rcfile", rcFile, "-il"], { cwd, stdio: "inherit" });
|
|
6967
|
+
spawnSync(shell, ["--rcfile", rcFile, "-il"], { cwd, stdio: "inherit", env: workshellEnv });
|
|
6963
6968
|
} finally {
|
|
6964
6969
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
6965
6970
|
}
|
|
@@ -6967,7 +6972,7 @@ ${bashScript}${setupSection}`);
|
|
|
6967
6972
|
const fishInit = setupCommand ? `${fishScript}
|
|
6968
6973
|
# wk setup script
|
|
6969
6974
|
${setupCommand}` : fishScript;
|
|
6970
|
-
spawnSync(shell, ["--init-command", fishInit], { cwd, stdio: "inherit" });
|
|
6975
|
+
spawnSync(shell, ["--init-command", fishInit], { cwd, stdio: "inherit", env: workshellEnv });
|
|
6971
6976
|
} else {
|
|
6972
6977
|
console.error(`Error: Unsupported shell '${shell}'`);
|
|
6973
6978
|
console.error("wk requires bash, zsh, or fish.");
|
|
@@ -6976,6 +6981,47 @@ ${setupCommand}` : fishScript;
|
|
|
6976
6981
|
}
|
|
6977
6982
|
var success = (text) => import_picocolors.default.green(import_picocolors.default.bold("\u2713")) + " " + text;
|
|
6978
6983
|
var warn = (text) => import_picocolors.default.yellow(import_picocolors.default.bold("\u26A0")) + " " + text;
|
|
6984
|
+
function hasBinary(name) {
|
|
6985
|
+
try {
|
|
6986
|
+
execSync2(`command -v ${name}`, { stdio: "ignore" });
|
|
6987
|
+
return true;
|
|
6988
|
+
} catch {
|
|
6989
|
+
return false;
|
|
6990
|
+
}
|
|
6991
|
+
}
|
|
6992
|
+
function detectJsPackageManager(cwd) {
|
|
6993
|
+
if (existsSync2(join2(cwd, "pnpm-lock.yaml")) && hasBinary("pnpm")) return "pnpm";
|
|
6994
|
+
if (existsSync2(join2(cwd, "yarn.lock")) && hasBinary("yarn")) return "yarn";
|
|
6995
|
+
if ((existsSync2(join2(cwd, "bun.lockb")) || existsSync2(join2(cwd, "bun.lock"))) && hasBinary("bun")) return "bun";
|
|
6996
|
+
if (existsSync2(join2(cwd, "package-lock.json")) && hasBinary("npm")) return "npm";
|
|
6997
|
+
return null;
|
|
6998
|
+
}
|
|
6999
|
+
function detectPythonPackageManager(cwd) {
|
|
7000
|
+
if (existsSync2(join2(cwd, "uv.lock")) && hasBinary("uv")) return "uv";
|
|
7001
|
+
if (existsSync2(join2(cwd, "poetry.lock")) && hasBinary("poetry")) return "poetry";
|
|
7002
|
+
if (existsSync2(join2(cwd, "Pipfile.lock")) && hasBinary("pipenv")) return "pipenv";
|
|
7003
|
+
if (existsSync2(join2(cwd, "pdm.lock")) && hasBinary("pdm")) return "pdm";
|
|
7004
|
+
return null;
|
|
7005
|
+
}
|
|
7006
|
+
var installCmds = {
|
|
7007
|
+
npm: "npm install",
|
|
7008
|
+
yarn: "yarn install",
|
|
7009
|
+
pnpm: "pnpm install",
|
|
7010
|
+
bun: "bun install",
|
|
7011
|
+
uv: "uv sync",
|
|
7012
|
+
poetry: "poetry install",
|
|
7013
|
+
pipenv: "pipenv install",
|
|
7014
|
+
pdm: "pdm install"
|
|
7015
|
+
};
|
|
7016
|
+
function runPackageManagerInstall(pm, cwd) {
|
|
7017
|
+
const cmd2 = installCmds[pm];
|
|
7018
|
+
console.log(dim(`Running ${cmd2}...`));
|
|
7019
|
+
try {
|
|
7020
|
+
execSync2(cmd2, { cwd, stdio: "inherit" });
|
|
7021
|
+
} catch {
|
|
7022
|
+
console.error(warn(`${cmd2} failed \u2014 you may need to run it manually`));
|
|
7023
|
+
}
|
|
7024
|
+
}
|
|
6979
7025
|
var fail = (text) => import_picocolors.default.red(import_picocolors.default.bold("\u2717")) + " " + text;
|
|
6980
7026
|
var dim = (text) => import_picocolors.default.dim(text);
|
|
6981
7027
|
var bold = (text) => import_picocolors.default.bold(text);
|
|
@@ -6983,6 +7029,71 @@ var green = (text) => import_picocolors.default.green(text);
|
|
|
6983
7029
|
var cyan = (text) => import_picocolors.default.cyan(text);
|
|
6984
7030
|
var yellow = (text) => import_picocolors.default.yellow(text);
|
|
6985
7031
|
var red = (text) => import_picocolors.default.red(text);
|
|
7032
|
+
function cpCow(src, dest) {
|
|
7033
|
+
try {
|
|
7034
|
+
if (process.platform === "darwin") {
|
|
7035
|
+
execFileSync("cp", ["-RPc", src, dest], { stdio: "ignore" });
|
|
7036
|
+
} else if (process.platform === "linux") {
|
|
7037
|
+
execFileSync("cp", ["-RP", "--reflink=auto", src, dest], { stdio: "ignore" });
|
|
7038
|
+
} else {
|
|
7039
|
+
execFileSync("cp", ["-RP", src, dest], { stdio: "ignore" });
|
|
7040
|
+
}
|
|
7041
|
+
} catch (err) {
|
|
7042
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
|
|
7043
|
+
return;
|
|
7044
|
+
}
|
|
7045
|
+
if (process.platform === "darwin") {
|
|
7046
|
+
try {
|
|
7047
|
+
execFileSync("cp", ["-RP", src, dest], { stdio: "ignore" });
|
|
7048
|
+
return;
|
|
7049
|
+
} catch {
|
|
7050
|
+
}
|
|
7051
|
+
}
|
|
7052
|
+
console.error(warn(`Failed to copy ${src}: ${err instanceof Error ? err.message : String(err)}`));
|
|
7053
|
+
}
|
|
7054
|
+
}
|
|
7055
|
+
function getIgnoredPaths(repoPath) {
|
|
7056
|
+
try {
|
|
7057
|
+
const output = execFileSync(
|
|
7058
|
+
"git",
|
|
7059
|
+
["ls-files", "--others", "--ignored", "--exclude-standard", "--directory"],
|
|
7060
|
+
{ cwd: repoPath, encoding: "utf-8" }
|
|
7061
|
+
);
|
|
7062
|
+
const paths = output.split("\n").map((p) => p.replace(/\/$/, "")).filter((p) => p.length > 0 && p !== ".git").sort();
|
|
7063
|
+
const minimal = [];
|
|
7064
|
+
let prev = "";
|
|
7065
|
+
for (const path of paths) {
|
|
7066
|
+
if (prev && path.startsWith(prev + "/")) continue;
|
|
7067
|
+
minimal.push(path);
|
|
7068
|
+
prev = path;
|
|
7069
|
+
}
|
|
7070
|
+
return minimal;
|
|
7071
|
+
} catch {
|
|
7072
|
+
return [];
|
|
7073
|
+
}
|
|
7074
|
+
}
|
|
7075
|
+
var JUNK_FILES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", "desktop.ini"]);
|
|
7076
|
+
function isJunkFile(item) {
|
|
7077
|
+
const base = item.includes("/") ? item.slice(item.lastIndexOf("/") + 1) : item;
|
|
7078
|
+
return JUNK_FILES.has(base);
|
|
7079
|
+
}
|
|
7080
|
+
function copyIgnoredFiles(repoPath, worktreePath, skipDirs) {
|
|
7081
|
+
let items = getIgnoredPaths(repoPath);
|
|
7082
|
+
items = items.filter((item) => !isJunkFile(item));
|
|
7083
|
+
if (skipDirs && skipDirs.length > 0) {
|
|
7084
|
+
items = items.filter(
|
|
7085
|
+
(item) => !skipDirs.some((dir) => item.split("/").includes(dir))
|
|
7086
|
+
);
|
|
7087
|
+
}
|
|
7088
|
+
if (items.length === 0) return;
|
|
7089
|
+
console.log(dim(`Copying untracked files:`));
|
|
7090
|
+
for (const item of items) {
|
|
7091
|
+
console.log(dim(` ${item}`));
|
|
7092
|
+
const dest = join2(worktreePath, item);
|
|
7093
|
+
mkdirSync2(dirname2(dest), { recursive: true });
|
|
7094
|
+
cpCow(join2(repoPath, item), dest);
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
6986
7097
|
function autoCleanupWorktree(worktreeId, path, store, saveStore2) {
|
|
6987
7098
|
const branch = getWorktreeBranch(path);
|
|
6988
7099
|
const status = getWorktreeStatus(path);
|
|
@@ -7075,6 +7186,15 @@ function newCommand(branchName, fromBranch) {
|
|
|
7075
7186
|
mkdirSync3(dirname3(worktreePath), { recursive: true });
|
|
7076
7187
|
createWorktree(branch, worktreePath, fromBranch);
|
|
7077
7188
|
initSubmodules(worktreePath);
|
|
7189
|
+
const config = loadConfig();
|
|
7190
|
+
const jsPm = config?.setup ? null : detectJsPackageManager(mainWorktree);
|
|
7191
|
+
const pyPm = config?.setup ? null : detectPythonPackageManager(mainWorktree);
|
|
7192
|
+
const skipDirs = [];
|
|
7193
|
+
if (jsPm) skipDirs.push("node_modules");
|
|
7194
|
+
if (pyPm) skipDirs.push(".venv", "venv");
|
|
7195
|
+
copyIgnoredFiles(mainWorktree, worktreePath, skipDirs);
|
|
7196
|
+
if (jsPm) runPackageManagerInstall(jsPm, worktreePath);
|
|
7197
|
+
if (pyPm) runPackageManagerInstall(pyPm, worktreePath);
|
|
7078
7198
|
setWorktreeMeta(store, worktreeId, {
|
|
7079
7199
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7080
7200
|
});
|
|
@@ -7098,37 +7218,78 @@ function newCommand(branchName, fromBranch) {
|
|
|
7098
7218
|
}
|
|
7099
7219
|
|
|
7100
7220
|
// commands/open.ts
|
|
7221
|
+
import { execSync as execSync3 } from "child_process";
|
|
7101
7222
|
import { mkdirSync as mkdirSync4 } from "fs";
|
|
7102
7223
|
import { basename as basename4, dirname as dirname4, join as join4 } from "path";
|
|
7103
|
-
function
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7224
|
+
function isGhInstalled() {
|
|
7225
|
+
try {
|
|
7226
|
+
execSync3("gh --version", { stdio: "pipe", encoding: "utf-8" });
|
|
7227
|
+
return true;
|
|
7228
|
+
} catch {
|
|
7229
|
+
return false;
|
|
7230
|
+
}
|
|
7231
|
+
}
|
|
7232
|
+
function isGhAuthenticated() {
|
|
7233
|
+
try {
|
|
7234
|
+
execSync3("gh auth status", { stdio: "pipe", encoding: "utf-8" });
|
|
7235
|
+
return true;
|
|
7236
|
+
} catch {
|
|
7237
|
+
return false;
|
|
7238
|
+
}
|
|
7239
|
+
}
|
|
7240
|
+
function getPRInfo(prRef) {
|
|
7241
|
+
try {
|
|
7242
|
+
const output = execSync3(
|
|
7243
|
+
`gh pr view ${prRef} --json number,headRefName`,
|
|
7244
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
7245
|
+
);
|
|
7246
|
+
return JSON.parse(output);
|
|
7247
|
+
} catch {
|
|
7248
|
+
return null;
|
|
7249
|
+
}
|
|
7250
|
+
}
|
|
7251
|
+
function fetchPRBranch(prRef, prInfo) {
|
|
7252
|
+
try {
|
|
7253
|
+
execSync3(`git fetch origin ${prInfo.headRefName}:${prInfo.headRefName}`, { stdio: "pipe" });
|
|
7254
|
+
return true;
|
|
7255
|
+
} catch {
|
|
7256
|
+
}
|
|
7257
|
+
const currentBranch = getCurrentBranch();
|
|
7258
|
+
try {
|
|
7259
|
+
execSync3(`gh pr checkout ${prRef}`, { stdio: "pipe" });
|
|
7260
|
+
execSync3(`git checkout "${currentBranch}"`, { stdio: "pipe" });
|
|
7261
|
+
return true;
|
|
7262
|
+
} catch {
|
|
7263
|
+
try {
|
|
7264
|
+
execSync3(`git checkout "${currentBranch}"`, { stdio: "pipe" });
|
|
7265
|
+
} catch {
|
|
7112
7266
|
}
|
|
7113
|
-
console.log();
|
|
7114
7267
|
}
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7268
|
+
try {
|
|
7269
|
+
execSync3(`git fetch origin pull/${prInfo.number}/head:${prInfo.headRefName}`, { stdio: "pipe" });
|
|
7270
|
+
return true;
|
|
7271
|
+
} catch {
|
|
7272
|
+
return false;
|
|
7118
7273
|
}
|
|
7274
|
+
}
|
|
7275
|
+
function openBranch(branch, prNumber) {
|
|
7119
7276
|
const parentBranch = getCurrentBranch();
|
|
7277
|
+
const mainWorktree = getMainWorktree();
|
|
7120
7278
|
const existingWorktreePath = getWorktreeForBranch(branch);
|
|
7121
7279
|
if (existingWorktreePath) {
|
|
7122
7280
|
const worktreeId2 = getWorktreeId(existingWorktreePath);
|
|
7123
|
-
const mainWorktree2 = getMainWorktree();
|
|
7124
7281
|
console.log();
|
|
7125
|
-
|
|
7282
|
+
if (prNumber) {
|
|
7283
|
+
console.log(success(bold(`PR #${prNumber}`)), dim(`branch: ${cyan(branch)} (existing worktree)`));
|
|
7284
|
+
} else {
|
|
7285
|
+
console.log(success(bold(branch)), dim(`(existing worktree)`));
|
|
7286
|
+
}
|
|
7126
7287
|
console.log(dim("Type 'wk close' to return."));
|
|
7127
7288
|
console.log();
|
|
7128
7289
|
spawnShell(existingWorktreePath, {
|
|
7129
7290
|
branch,
|
|
7130
7291
|
worktreePath: existingWorktreePath,
|
|
7131
|
-
repoPath:
|
|
7292
|
+
repoPath: mainWorktree
|
|
7132
7293
|
});
|
|
7133
7294
|
console.log();
|
|
7134
7295
|
console.log(success(`Back in ${bold(parentBranch)}`));
|
|
@@ -7138,20 +7299,33 @@ function openCommand(branch) {
|
|
|
7138
7299
|
return;
|
|
7139
7300
|
}
|
|
7140
7301
|
const store = loadStore();
|
|
7141
|
-
const mainWorktree = getMainWorktree();
|
|
7142
7302
|
const repoName = basename4(mainWorktree);
|
|
7143
7303
|
const worktreeId = `${repoName}@${slugify(branch)}`;
|
|
7144
7304
|
const worktreePath = join4(getWorktreesDir(), worktreeId);
|
|
7145
7305
|
mkdirSync4(dirname4(worktreePath), { recursive: true });
|
|
7146
7306
|
createWorktreeForExistingBranch(branch, worktreePath);
|
|
7147
7307
|
initSubmodules(worktreePath);
|
|
7308
|
+
const config = loadConfig();
|
|
7309
|
+
const jsPm = config?.setup ? null : detectJsPackageManager(mainWorktree);
|
|
7310
|
+
const pyPm = config?.setup ? null : detectPythonPackageManager(mainWorktree);
|
|
7311
|
+
const skipDirs = [];
|
|
7312
|
+
if (jsPm) skipDirs.push("node_modules");
|
|
7313
|
+
if (pyPm) skipDirs.push(".venv", "venv");
|
|
7314
|
+
copyIgnoredFiles(mainWorktree, worktreePath, skipDirs);
|
|
7315
|
+
if (jsPm) runPackageManagerInstall(jsPm, worktreePath);
|
|
7316
|
+
if (pyPm) runPackageManagerInstall(pyPm, worktreePath);
|
|
7148
7317
|
setWorktreeMeta(store, worktreeId, {
|
|
7149
7318
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7150
7319
|
});
|
|
7151
7320
|
saveStore(store);
|
|
7152
7321
|
console.log();
|
|
7153
|
-
|
|
7154
|
-
|
|
7322
|
+
if (prNumber) {
|
|
7323
|
+
console.log(success(bold(`PR #${prNumber}`)), dim(`branch: ${cyan(branch)}`));
|
|
7324
|
+
console.log(dim(`Opened PR in ephemeral subshell`));
|
|
7325
|
+
} else {
|
|
7326
|
+
console.log(success(bold(branch)));
|
|
7327
|
+
console.log(dim(`Opened branch in ephemeral subshell`));
|
|
7328
|
+
}
|
|
7155
7329
|
console.log(dim("Type 'exit' or 'wk close' to return."));
|
|
7156
7330
|
console.log();
|
|
7157
7331
|
spawnShell(worktreePath, {
|
|
@@ -7165,6 +7339,55 @@ function openCommand(branch) {
|
|
|
7165
7339
|
autoCleanupWorktree(worktreeId, worktreePath, currentStore, saveStore);
|
|
7166
7340
|
console.log();
|
|
7167
7341
|
}
|
|
7342
|
+
function openCommand(ref) {
|
|
7343
|
+
if (isInsideWorkshell()) {
|
|
7344
|
+
const currentBranch = getCurrentBranch();
|
|
7345
|
+
console.log();
|
|
7346
|
+
console.log(warn(`You're inside a workshell (branch: ${cyan(currentBranch)})`));
|
|
7347
|
+
console.log(` This will nest subshells (subshell inside subshell).`);
|
|
7348
|
+
console.log();
|
|
7349
|
+
if (!confirm("Continue?")) {
|
|
7350
|
+
process.exit(0);
|
|
7351
|
+
}
|
|
7352
|
+
console.log();
|
|
7353
|
+
}
|
|
7354
|
+
if (branchExists(ref)) {
|
|
7355
|
+
openBranch(ref);
|
|
7356
|
+
return;
|
|
7357
|
+
}
|
|
7358
|
+
if (!isGhInstalled()) {
|
|
7359
|
+
console.error(fail(`Branch '${ref}' not found.`));
|
|
7360
|
+
console.error(dim(" Install GitHub CLI (gh) to open PRs: https://cli.github.com/"));
|
|
7361
|
+
process.exit(1);
|
|
7362
|
+
}
|
|
7363
|
+
if (!isGhAuthenticated()) {
|
|
7364
|
+
console.error(fail(`Branch '${ref}' not found.`));
|
|
7365
|
+
console.error(dim(" Run 'gh auth login' to open PRs from GitHub."));
|
|
7366
|
+
process.exit(1);
|
|
7367
|
+
}
|
|
7368
|
+
const prInfo = getPRInfo(ref);
|
|
7369
|
+
if (!prInfo) {
|
|
7370
|
+
console.error(fail(`'${ref}' is not a local branch or GitHub PR.`));
|
|
7371
|
+
process.exit(1);
|
|
7372
|
+
}
|
|
7373
|
+
const branch = prInfo.headRefName;
|
|
7374
|
+
const prNumber = prInfo.number;
|
|
7375
|
+
if (branchExists(branch)) {
|
|
7376
|
+
console.log(dim(`PR #${prNumber} \u2192 ${branch}`));
|
|
7377
|
+
openBranch(branch, prNumber);
|
|
7378
|
+
return;
|
|
7379
|
+
}
|
|
7380
|
+
console.log(dim(`Fetching PR #${prNumber}...`));
|
|
7381
|
+
if (!fetchPRBranch(ref, prInfo)) {
|
|
7382
|
+
console.error(fail(`Failed to fetch branch '${branch}' for PR #${prNumber}`));
|
|
7383
|
+
process.exit(1);
|
|
7384
|
+
}
|
|
7385
|
+
if (!branchExists(branch)) {
|
|
7386
|
+
console.error(fail(`Failed to fetch branch '${branch}' for PR #${prNumber}`));
|
|
7387
|
+
process.exit(1);
|
|
7388
|
+
}
|
|
7389
|
+
openBranch(branch, prNumber);
|
|
7390
|
+
}
|
|
7168
7391
|
|
|
7169
7392
|
// commands/ls.ts
|
|
7170
7393
|
var import_table = __toESM(require_src(), 1);
|
|
@@ -8015,7 +8238,7 @@ function lsCommand(plain = false) {
|
|
|
8015
8238
|
}
|
|
8016
8239
|
|
|
8017
8240
|
// commands/rm.ts
|
|
8018
|
-
import { execSync as
|
|
8241
|
+
import { execSync as execSync4 } from "child_process";
|
|
8019
8242
|
import { resolve as resolve2 } from "path";
|
|
8020
8243
|
function rmCommand(branch, force = false) {
|
|
8021
8244
|
const mainWorktree = getMainWorktree();
|
|
@@ -8046,8 +8269,8 @@ function rmCommand(branch, force = false) {
|
|
|
8046
8269
|
}
|
|
8047
8270
|
if (isDirty && force) {
|
|
8048
8271
|
try {
|
|
8049
|
-
|
|
8050
|
-
|
|
8272
|
+
execSync4(`git -C "${worktreePath}" reset --hard HEAD`, { stdio: "ignore" });
|
|
8273
|
+
execSync4(`git -C "${worktreePath}" clean -fd`, { stdio: "ignore" });
|
|
8051
8274
|
} catch {
|
|
8052
8275
|
}
|
|
8053
8276
|
}
|
|
@@ -8065,7 +8288,7 @@ function rmCommand(branch, force = false) {
|
|
|
8065
8288
|
}
|
|
8066
8289
|
|
|
8067
8290
|
// commands/status.ts
|
|
8068
|
-
import { execSync as
|
|
8291
|
+
import { execSync as execSync5 } from "child_process";
|
|
8069
8292
|
import { existsSync as existsSync4, readdirSync as readdirSync3 } from "fs";
|
|
8070
8293
|
import { resolve as resolve3 } from "path";
|
|
8071
8294
|
function getDetailedStatus(path) {
|
|
@@ -8073,7 +8296,7 @@ function getDetailedStatus(path) {
|
|
|
8073
8296
|
return [];
|
|
8074
8297
|
}
|
|
8075
8298
|
try {
|
|
8076
|
-
const status =
|
|
8299
|
+
const status = execSync5(`git -C "${path}" status --short`, { encoding: "utf-8" }).trim();
|
|
8077
8300
|
if (!status) {
|
|
8078
8301
|
return [];
|
|
8079
8302
|
}
|
|
@@ -8139,17 +8362,17 @@ function statusCommand() {
|
|
|
8139
8362
|
}
|
|
8140
8363
|
|
|
8141
8364
|
// commands/preclose.ts
|
|
8142
|
-
import { execSync as
|
|
8365
|
+
import { execSync as execSync6 } from "child_process";
|
|
8143
8366
|
function precloseCommand(force) {
|
|
8144
8367
|
if (!hasUncommittedChanges()) {
|
|
8145
8368
|
process.exit(0);
|
|
8146
8369
|
}
|
|
8147
8370
|
if (force) {
|
|
8148
8371
|
try {
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8372
|
+
execSync6("git reset --hard HEAD", { stdio: "ignore" });
|
|
8373
|
+
execSync6("git clean -ffd", { stdio: "ignore" });
|
|
8374
|
+
execSync6("git submodule foreach --recursive 'git reset --hard HEAD; git clean -ffd'", { stdio: "ignore" });
|
|
8375
|
+
execSync6("git submodule update --init --recursive --force", { stdio: "ignore" });
|
|
8153
8376
|
} catch {
|
|
8154
8377
|
}
|
|
8155
8378
|
process.exit(0);
|
|
@@ -8221,162 +8444,8 @@ function promptChoice() {
|
|
|
8221
8444
|
}
|
|
8222
8445
|
}
|
|
8223
8446
|
|
|
8224
|
-
// commands/pr.ts
|
|
8225
|
-
import { execSync as execSync6 } from "child_process";
|
|
8226
|
-
import { mkdirSync as mkdirSync5 } from "fs";
|
|
8227
|
-
import { basename as basename5, dirname as dirname5, join as join6 } from "path";
|
|
8228
|
-
function checkGhInstalled() {
|
|
8229
|
-
try {
|
|
8230
|
-
execSync6("gh --version", { stdio: "pipe", encoding: "utf-8" });
|
|
8231
|
-
} catch {
|
|
8232
|
-
console.error(fail("GitHub CLI (gh) is not installed."));
|
|
8233
|
-
console.error(dim(" Install it from: https://cli.github.com/"));
|
|
8234
|
-
process.exit(1);
|
|
8235
|
-
}
|
|
8236
|
-
}
|
|
8237
|
-
function checkGhAuthenticated() {
|
|
8238
|
-
try {
|
|
8239
|
-
execSync6("gh auth status", { stdio: "pipe", encoding: "utf-8" });
|
|
8240
|
-
} catch {
|
|
8241
|
-
console.error(fail("Not authenticated with GitHub CLI."));
|
|
8242
|
-
console.error(dim(" Run: gh auth login"));
|
|
8243
|
-
process.exit(1);
|
|
8244
|
-
}
|
|
8245
|
-
}
|
|
8246
|
-
function getPRInfo(prRef) {
|
|
8247
|
-
try {
|
|
8248
|
-
const output = execSync6(
|
|
8249
|
-
`gh pr view ${prRef} --json number,headRefName,headRepository,headRepositoryOwner,isCrossRepository`,
|
|
8250
|
-
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
8251
|
-
);
|
|
8252
|
-
return JSON.parse(output);
|
|
8253
|
-
} catch (err) {
|
|
8254
|
-
const message = err instanceof Error && "stderr" in err ? err.stderr : String(err);
|
|
8255
|
-
if (message.includes("Could not resolve")) {
|
|
8256
|
-
console.error(fail(`PR '${prRef}' not found.`));
|
|
8257
|
-
console.error(dim(" Make sure you're in a GitHub repository and the PR exists."));
|
|
8258
|
-
} else if (message.includes("no pull requests found")) {
|
|
8259
|
-
console.error(fail(`No pull request found for '${prRef}'.`));
|
|
8260
|
-
} else {
|
|
8261
|
-
console.error(fail(`Failed to get PR info: ${message}`));
|
|
8262
|
-
}
|
|
8263
|
-
process.exit(1);
|
|
8264
|
-
}
|
|
8265
|
-
}
|
|
8266
|
-
function fetchPRBranch(prRef, prInfo) {
|
|
8267
|
-
try {
|
|
8268
|
-
execSync6(`git fetch origin ${prInfo.headRefName}:${prInfo.headRefName}`, { stdio: "pipe" });
|
|
8269
|
-
return;
|
|
8270
|
-
} catch {
|
|
8271
|
-
}
|
|
8272
|
-
const currentBranch = getCurrentBranch();
|
|
8273
|
-
try {
|
|
8274
|
-
execSync6(`gh pr checkout ${prRef}`, { stdio: "pipe" });
|
|
8275
|
-
execSync6(`git checkout "${currentBranch}"`, { stdio: "pipe" });
|
|
8276
|
-
return;
|
|
8277
|
-
} catch {
|
|
8278
|
-
try {
|
|
8279
|
-
execSync6(`git checkout "${currentBranch}"`, { stdio: "pipe" });
|
|
8280
|
-
} catch {
|
|
8281
|
-
}
|
|
8282
|
-
}
|
|
8283
|
-
try {
|
|
8284
|
-
execSync6(`git fetch origin pull/${prInfo.number}/head:${prInfo.headRefName}`, { stdio: "pipe" });
|
|
8285
|
-
return;
|
|
8286
|
-
} catch (fetchErr) {
|
|
8287
|
-
const message = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
|
|
8288
|
-
console.error(fail(`Failed to fetch PR branch: ${message}`));
|
|
8289
|
-
process.exit(1);
|
|
8290
|
-
}
|
|
8291
|
-
}
|
|
8292
|
-
function branchExistsLocally(branch) {
|
|
8293
|
-
try {
|
|
8294
|
-
execSync6(`git show-ref --verify --quiet refs/heads/${branch}`);
|
|
8295
|
-
return true;
|
|
8296
|
-
} catch {
|
|
8297
|
-
return false;
|
|
8298
|
-
}
|
|
8299
|
-
}
|
|
8300
|
-
function createWorktreeForPR(branch, path) {
|
|
8301
|
-
execSync6(`git worktree add "${path}" "${branch}"`, { stdio: "ignore" });
|
|
8302
|
-
}
|
|
8303
|
-
function prOpenCommand(prRef) {
|
|
8304
|
-
checkGhInstalled();
|
|
8305
|
-
checkGhAuthenticated();
|
|
8306
|
-
const prInfo = getPRInfo(prRef);
|
|
8307
|
-
const branch = prInfo.headRefName;
|
|
8308
|
-
const prNumber = prInfo.number;
|
|
8309
|
-
if (isInsideWorktree()) {
|
|
8310
|
-
const currentBranch = getCurrentBranch();
|
|
8311
|
-
console.log();
|
|
8312
|
-
console.log(warn(`You're inside a worktree (branch: ${cyan(currentBranch)})`));
|
|
8313
|
-
console.log(` This will nest subshells (subshell inside subshell).`);
|
|
8314
|
-
console.log();
|
|
8315
|
-
if (!confirm("Continue?")) {
|
|
8316
|
-
process.exit(0);
|
|
8317
|
-
}
|
|
8318
|
-
console.log();
|
|
8319
|
-
}
|
|
8320
|
-
const parentBranch = getCurrentBranch();
|
|
8321
|
-
const existingWorktreePath = getWorktreeForBranch(branch);
|
|
8322
|
-
if (existingWorktreePath) {
|
|
8323
|
-
const worktreeId2 = getWorktreeId(existingWorktreePath);
|
|
8324
|
-
const mainWorktree2 = getMainWorktree();
|
|
8325
|
-
console.log();
|
|
8326
|
-
console.log(success(bold(`PR #${prNumber}`)), dim(`branch: ${cyan(branch)} (existing worktree)`));
|
|
8327
|
-
console.log(dim("Type 'wk close' to return."));
|
|
8328
|
-
console.log();
|
|
8329
|
-
spawnShell(existingWorktreePath, {
|
|
8330
|
-
branch,
|
|
8331
|
-
worktreePath: existingWorktreePath,
|
|
8332
|
-
repoPath: mainWorktree2
|
|
8333
|
-
});
|
|
8334
|
-
console.log();
|
|
8335
|
-
console.log(success(`Back in ${bold(parentBranch)}`));
|
|
8336
|
-
const currentStore2 = loadStore();
|
|
8337
|
-
autoCleanupWorktree(worktreeId2, existingWorktreePath, currentStore2, saveStore);
|
|
8338
|
-
console.log();
|
|
8339
|
-
return;
|
|
8340
|
-
}
|
|
8341
|
-
if (!branchExistsLocally(branch)) {
|
|
8342
|
-
console.log(dim(`Fetching PR #${prNumber}...`));
|
|
8343
|
-
fetchPRBranch(prRef, prInfo);
|
|
8344
|
-
}
|
|
8345
|
-
if (!branchExistsLocally(branch)) {
|
|
8346
|
-
console.error(fail(`Failed to fetch branch '${branch}' for PR #${prNumber}`));
|
|
8347
|
-
process.exit(1);
|
|
8348
|
-
}
|
|
8349
|
-
const store = loadStore();
|
|
8350
|
-
const mainWorktree = getMainWorktree();
|
|
8351
|
-
const repoName = basename5(mainWorktree);
|
|
8352
|
-
const worktreeId = `${repoName}@${slugify(branch)}`;
|
|
8353
|
-
const worktreePath = join6(getWorktreesDir(), worktreeId);
|
|
8354
|
-
mkdirSync5(dirname5(worktreePath), { recursive: true });
|
|
8355
|
-
createWorktreeForPR(branch, worktreePath);
|
|
8356
|
-
initSubmodules(worktreePath);
|
|
8357
|
-
setWorktreeMeta(store, worktreeId, {
|
|
8358
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
8359
|
-
});
|
|
8360
|
-
saveStore(store);
|
|
8361
|
-
console.log();
|
|
8362
|
-
console.log(success(bold(`PR #${prNumber}`)), dim(`branch: ${cyan(branch)}`));
|
|
8363
|
-
console.log(dim(`Opened PR in ephemeral subshell`));
|
|
8364
|
-
console.log(dim("Type 'exit' or 'wk close' to return."));
|
|
8365
|
-
console.log();
|
|
8366
|
-
spawnShell(worktreePath, {
|
|
8367
|
-
branch,
|
|
8368
|
-
worktreePath,
|
|
8369
|
-
repoPath: mainWorktree
|
|
8370
|
-
});
|
|
8371
|
-
console.log();
|
|
8372
|
-
console.log(success(`Back in ${bold(parentBranch)}`));
|
|
8373
|
-
const currentStore = loadStore();
|
|
8374
|
-
autoCleanupWorktree(worktreeId, worktreePath, currentStore, saveStore);
|
|
8375
|
-
console.log();
|
|
8376
|
-
}
|
|
8377
|
-
|
|
8378
8447
|
// index.ts
|
|
8379
|
-
var VERSION = "0.
|
|
8448
|
+
var VERSION = "0.4.0";
|
|
8380
8449
|
function printHelp() {
|
|
8381
8450
|
const dim2 = import_picocolors4.default.dim;
|
|
8382
8451
|
const cyan2 = import_picocolors4.default.cyan;
|
|
@@ -8387,9 +8456,8 @@ ${import_picocolors4.default.bold("Usage:")} ${cyan2("wk")} ${dim2("<command>")}
|
|
|
8387
8456
|
|
|
8388
8457
|
${import_picocolors4.default.bold("Commands:")}
|
|
8389
8458
|
${green2("new")} ${dim2("[branch]")} Create a branch and open it ${dim2("[--from <branch>]")}
|
|
8390
|
-
${green2("open")} ${dim2("<
|
|
8391
|
-
${green2("
|
|
8392
|
-
${green2("close")} Exit current subshell
|
|
8459
|
+
${green2("open")} ${dim2("<ref>")} Open a branch or PR in an ephemeral subshell
|
|
8460
|
+
${green2("close")} Exit current workshell
|
|
8393
8461
|
${green2("ls")} List open branches
|
|
8394
8462
|
${green2("status")} Show current branch
|
|
8395
8463
|
${green2("rm")} ${dim2("<branch>")} Remove a branch's worktree
|
|
@@ -8453,15 +8521,24 @@ ${import_picocolors4.default.bold("Description:")}
|
|
|
8453
8521
|
function printOpenHelp() {
|
|
8454
8522
|
const dim2 = import_picocolors4.default.dim;
|
|
8455
8523
|
const cyan2 = import_picocolors4.default.cyan;
|
|
8456
|
-
console.log(`${import_picocolors4.default.bold("wk open")} - Open a branch in an ephemeral subshell
|
|
8524
|
+
console.log(`${import_picocolors4.default.bold("wk open")} - Open a branch or PR in an ephemeral subshell
|
|
8457
8525
|
|
|
8458
|
-
${import_picocolors4.default.bold("Usage:")} ${cyan2("wk open")} ${dim2("<
|
|
8526
|
+
${import_picocolors4.default.bold("Usage:")} ${cyan2("wk open")} ${dim2("<ref>")}
|
|
8459
8527
|
|
|
8460
8528
|
${import_picocolors4.default.bold("Arguments:")}
|
|
8461
|
-
${dim2("<
|
|
8529
|
+
${dim2("<ref>")} Branch name, PR number, or PR URL ${dim2("(required)")}
|
|
8530
|
+
|
|
8531
|
+
${import_picocolors4.default.bold("Examples:")}
|
|
8532
|
+
${dim2("$")} ${cyan2("wk open feature-branch")} ${dim2("# Open local branch")}
|
|
8533
|
+
${dim2("$")} ${cyan2("wk open 123")} ${dim2("# Open PR #123")}
|
|
8534
|
+
${dim2("$")} ${cyan2("wk open https://github.com/o/r/pull/123")}
|
|
8462
8535
|
|
|
8463
8536
|
${import_picocolors4.default.bold("Description:")}
|
|
8464
|
-
Opens the specified
|
|
8537
|
+
Opens the specified ref in an ephemeral subshell.
|
|
8538
|
+
|
|
8539
|
+
First tries to open as a local branch. If not found, tries to fetch
|
|
8540
|
+
as a GitHub PR (requires gh CLI to be installed and authenticated).
|
|
8541
|
+
|
|
8465
8542
|
If a worktree already exists for the branch, uses that.
|
|
8466
8543
|
Otherwise, creates a new worktree automatically.
|
|
8467
8544
|
Type 'wk close' to return.`);
|
|
@@ -8511,7 +8588,7 @@ ${import_picocolors4.default.bold("Description:")}
|
|
|
8511
8588
|
function printCloseHelp() {
|
|
8512
8589
|
const dim2 = import_picocolors4.default.dim;
|
|
8513
8590
|
const cyan2 = import_picocolors4.default.cyan;
|
|
8514
|
-
console.log(`${import_picocolors4.default.bold("wk close")} - Exit current
|
|
8591
|
+
console.log(`${import_picocolors4.default.bold("wk close")} - Exit current workshell
|
|
8515
8592
|
|
|
8516
8593
|
${import_picocolors4.default.bold("Usage:")} ${cyan2("wk close")} ${dim2("[options]")}
|
|
8517
8594
|
|
|
@@ -8519,7 +8596,7 @@ ${import_picocolors4.default.bold("Options:")}
|
|
|
8519
8596
|
${cyan2("-f")}, ${cyan2("--force")} Discard uncommitted changes and exit
|
|
8520
8597
|
|
|
8521
8598
|
${import_picocolors4.default.bold("Description:")}
|
|
8522
|
-
Exits the current
|
|
8599
|
+
Exits the current workshell and returns to the parent.
|
|
8523
8600
|
|
|
8524
8601
|
On exit, if the branch is clean:
|
|
8525
8602
|
- Worktree is pruned (branch is kept)
|
|
@@ -8544,51 +8621,6 @@ ${import_picocolors4.default.bold("Config locations")} ${dim2("(in precedence or
|
|
|
8544
8621
|
${cyan2(".git/workshell.toml")} Local only, not committed
|
|
8545
8622
|
${cyan2("workshell.toml")} Project root, can be committed`);
|
|
8546
8623
|
}
|
|
8547
|
-
function printPrHelp() {
|
|
8548
|
-
const dim2 = import_picocolors4.default.dim;
|
|
8549
|
-
const cyan2 = import_picocolors4.default.cyan;
|
|
8550
|
-
console.log(`${import_picocolors4.default.bold("wk pr")} - Work with GitHub Pull Requests
|
|
8551
|
-
|
|
8552
|
-
${import_picocolors4.default.bold("Usage:")} ${cyan2("wk pr")} ${dim2("<subcommand>")} ${dim2("[options]")}
|
|
8553
|
-
|
|
8554
|
-
${import_picocolors4.default.bold("Subcommands:")}
|
|
8555
|
-
${cyan2("open")} ${dim2("<ref>")} Open a PR in an ephemeral subshell
|
|
8556
|
-
|
|
8557
|
-
${import_picocolors4.default.bold("Examples:")}
|
|
8558
|
-
${dim2("$")} ${cyan2("wk pr open 123")} ${dim2("# Open PR by number")}
|
|
8559
|
-
${dim2("$")} ${cyan2("wk pr open https://github.com/o/r/pull/123")} ${dim2("# Open PR by URL")}
|
|
8560
|
-
|
|
8561
|
-
${import_picocolors4.default.bold("Requirements:")}
|
|
8562
|
-
Requires GitHub CLI (gh) to be installed and authenticated.
|
|
8563
|
-
${dim2("Install: https://cli.github.com/")}
|
|
8564
|
-
${dim2("Auth: gh auth login")}`);
|
|
8565
|
-
}
|
|
8566
|
-
function printPrOpenHelp() {
|
|
8567
|
-
const dim2 = import_picocolors4.default.dim;
|
|
8568
|
-
const cyan2 = import_picocolors4.default.cyan;
|
|
8569
|
-
console.log(`${import_picocolors4.default.bold("wk pr open")} - Open a GitHub PR in an ephemeral subshell
|
|
8570
|
-
|
|
8571
|
-
${import_picocolors4.default.bold("Usage:")} ${cyan2("wk pr open")} ${dim2("<ref>")}
|
|
8572
|
-
|
|
8573
|
-
${import_picocolors4.default.bold("Arguments:")}
|
|
8574
|
-
${dim2("<ref>")} PR number, URL, or branch name ${dim2("(required)")}
|
|
8575
|
-
|
|
8576
|
-
${import_picocolors4.default.bold("Examples:")}
|
|
8577
|
-
${dim2("$")} ${cyan2("wk pr open 123")}
|
|
8578
|
-
${dim2("$")} ${cyan2("wk pr open https://github.com/owner/repo/pull/123")}
|
|
8579
|
-
${dim2("$")} ${cyan2("wk pr open feature-branch")}
|
|
8580
|
-
|
|
8581
|
-
${import_picocolors4.default.bold("Description:")}
|
|
8582
|
-
Fetches the PR branch and opens it in an ephemeral subshell.
|
|
8583
|
-
If a worktree already exists for the branch, uses that.
|
|
8584
|
-
Otherwise, creates a new worktree automatically.
|
|
8585
|
-
Type 'wk close' to return.
|
|
8586
|
-
|
|
8587
|
-
${import_picocolors4.default.bold("Requirements:")}
|
|
8588
|
-
Requires GitHub CLI (gh) to be installed and authenticated.
|
|
8589
|
-
${dim2("Install: https://cli.github.com/")}
|
|
8590
|
-
${dim2("Auth: gh auth login")}`);
|
|
8591
|
-
}
|
|
8592
8624
|
function isHelp(arg) {
|
|
8593
8625
|
return arg === "--help" || arg === "-h";
|
|
8594
8626
|
}
|
|
@@ -8630,7 +8662,7 @@ switch (cmd) {
|
|
|
8630
8662
|
process.exit(0);
|
|
8631
8663
|
}
|
|
8632
8664
|
if (!args[1]) {
|
|
8633
|
-
console.error("Usage: wk open <branch>");
|
|
8665
|
+
console.error("Usage: wk open <branch|pr-number|pr-url>");
|
|
8634
8666
|
process.exit(1);
|
|
8635
8667
|
}
|
|
8636
8668
|
openCommand(args[1]);
|
|
@@ -8667,35 +8699,23 @@ switch (cmd) {
|
|
|
8667
8699
|
}
|
|
8668
8700
|
configCommand();
|
|
8669
8701
|
break;
|
|
8670
|
-
case "pr": {
|
|
8671
|
-
const subCmd = args[1];
|
|
8672
|
-
if (!subCmd || isHelp(subCmd)) {
|
|
8673
|
-
printPrHelp();
|
|
8674
|
-
process.exit(0);
|
|
8675
|
-
}
|
|
8676
|
-
if (subCmd === "open") {
|
|
8677
|
-
if (isHelp(args[2])) {
|
|
8678
|
-
printPrOpenHelp();
|
|
8679
|
-
process.exit(0);
|
|
8680
|
-
}
|
|
8681
|
-
if (!args[2]) {
|
|
8682
|
-
console.error("Usage: wk pr open <pr-number|url|branch>");
|
|
8683
|
-
process.exit(1);
|
|
8684
|
-
}
|
|
8685
|
-
prOpenCommand(args[2]);
|
|
8686
|
-
} else {
|
|
8687
|
-
console.error(`Unknown pr subcommand: ${subCmd}`);
|
|
8688
|
-
console.error("Run 'wk pr --help' for usage.");
|
|
8689
|
-
process.exit(1);
|
|
8690
|
-
}
|
|
8691
|
-
break;
|
|
8692
|
-
}
|
|
8693
8702
|
case "close":
|
|
8694
8703
|
if (isHelp(args[1])) {
|
|
8695
8704
|
printCloseHelp();
|
|
8696
8705
|
process.exit(0);
|
|
8697
8706
|
}
|
|
8698
|
-
|
|
8707
|
+
if (!isInsideWorkshell() && isInsideWorktree()) {
|
|
8708
|
+
const currentBranch = getCurrentBranch();
|
|
8709
|
+
const mainWorktree = getMainWorktree();
|
|
8710
|
+
const shell = process.env.SHELL || "/bin/bash";
|
|
8711
|
+
console.log();
|
|
8712
|
+
console.log(dim(`You're in a worktree (branch: ${cyan(currentBranch)}) but not a workshell.`));
|
|
8713
|
+
console.log(dim(`Opening repo root in a fresh shell...`));
|
|
8714
|
+
console.log();
|
|
8715
|
+
spawnSync2(shell, [], { cwd: mainWorktree, stdio: "inherit" });
|
|
8716
|
+
process.exit(0);
|
|
8717
|
+
}
|
|
8718
|
+
console.error(fail("'wk close' only works inside a workshell."));
|
|
8699
8719
|
process.exit(1);
|
|
8700
8720
|
break;
|
|
8701
8721
|
case "__preclose":
|