@tsuzuku/arise 1.1.0 → 1.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.
package/lib/config.js CHANGED
@@ -53,12 +53,19 @@ function findConfigFile(searchDirs = []) {
53
53
  return null;
54
54
  }
55
55
 
56
+ function stripJsonComments(str) {
57
+ return str
58
+ .replace(/\/\*[\s\S]*?\*\//g, '')
59
+ .replace(/\/\/.*$/gm, '')
60
+ .trim();
61
+ }
62
+
56
63
  function loadConfigFile(filePath) {
57
64
  if (!filePath) return {};
58
65
  try {
59
66
  if (filePath.endsWith('.json') || filePath.endsWith('.worktreerc') || filePath.endsWith('.ariserc')) {
60
67
  const content = fs.readFileSync(filePath, 'utf8');
61
- return JSON.parse(content);
68
+ return JSON.parse(stripJsonComments(content));
62
69
  } else {
63
70
  return require(path.resolve(filePath));
64
71
  }
@@ -68,9 +75,67 @@ function loadConfigFile(filePath) {
68
75
  }
69
76
  }
70
77
 
71
- function resolveConfiguration(flags = {}, cwd = process.cwd()) {
72
- const repoRoot = git.getRepoRootDir(cwd);
73
- const configFile = findConfigFile([cwd, repoRoot]);
78
+ function resolveConfiguration(flags = {}, cwd = process.cwd(), callerCwd = null) {
79
+ const repoRoot = git.getRepoRootDir(cwd) || (callerCwd ? git.getRepoRootDir(callerCwd) : null);
80
+ const isBare = git.isBareRepo(repoRoot);
81
+ const searchDirs = [];
82
+
83
+ const addSearchDir = (dir) => {
84
+ if (dir && !searchDirs.includes(dir)) {
85
+ searchDirs.push(dir);
86
+ }
87
+ };
88
+
89
+ let worktrees = [];
90
+ try {
91
+ worktrees = git.getWorktrees({ repoDir: repoRoot, bareRepo: isBare ? repoRoot : null });
92
+ } catch (e) {}
93
+
94
+ // 1. Explicit directory if --dirname / -d is passed
95
+ if (flags.dirname) {
96
+ const resolvedDir = path.isAbsolute(flags.dirname)
97
+ ? flags.dirname
98
+ : path.resolve(repoRoot || cwd, flags.dirname);
99
+ addSearchDir(resolvedDir);
100
+ }
101
+
102
+ // 2. Directory for the specified branch (--branch / -b)
103
+ if (flags.branch) {
104
+ const matchingWt = worktrees.find((wt) => wt.branch === flags.branch);
105
+ if (matchingWt && matchingWt.path) {
106
+ addSearchDir(matchingWt.path);
107
+ }
108
+ const branchDir = path.resolve(repoRoot || cwd, flags.branch.replace(/\//g, '-'));
109
+ addSearchDir(branchDir);
110
+ }
111
+
112
+ // 3. Target working directory
113
+ addSearchDir(cwd);
114
+
115
+ // 4. Fallback working directory (caller working directory)
116
+ if (callerCwd) {
117
+ addSearchDir(callerCwd);
118
+ }
119
+
120
+ // 5. Repository root directory
121
+ if (repoRoot) {
122
+ addSearchDir(repoRoot);
123
+ if (isBare) {
124
+ addSearchDir(path.dirname(repoRoot));
125
+ }
126
+ }
127
+
128
+ // 6. Fallback to existing worktrees (prioritizing primary branches like main, master, develop)
129
+ const primaryBranches = ['main', 'master', 'develop', 'prod', 'staging'];
130
+ const primaryWts = worktrees.filter((wt) => !wt.isBare && wt.branch && primaryBranches.includes(wt.branch));
131
+ for (const wt of primaryWts) {
132
+ if (wt.path) addSearchDir(wt.path);
133
+ }
134
+ for (const wt of worktrees) {
135
+ if (wt.path && !wt.isBare) addSearchDir(wt.path);
136
+ }
137
+
138
+ const configFile = findConfigFile(searchDirs);
74
139
  const fileConfig = loadConfigFile(configFile);
75
140
 
76
141
  // 1. Resolve Preset
@@ -80,12 +145,16 @@ function resolveConfiguration(flags = {}, cwd = process.cwd()) {
80
145
  preset = getPreset(presetName);
81
146
  }
82
147
  if (!preset) {
83
- preset = detectPreset(cwd);
148
+ const detectDir = (configFile && path.dirname(configFile)) || cwd;
149
+ preset = detectPreset(detectDir);
150
+ if (preset.name === 'generic' && detectDir !== cwd) {
151
+ preset = detectPreset(cwd);
152
+ }
84
153
  }
85
154
 
86
155
  // 2. Merge Repo Settings
87
156
  const repoConfig = {
88
- bareRepo: (fileConfig.repo && fileConfig.repo.bareRepo) || (preset.repo && preset.repo.bareRepo) || null,
157
+ bareRepo: (fileConfig.repo && fileConfig.repo.bareRepo) || (preset.repo && preset.repo.bareRepo) || (isBare ? repoRoot : null),
89
158
  worktreesBase: (fileConfig.repo && fileConfig.repo.worktreesBase) || (preset.repo && preset.repo.worktreesBase) || null,
90
159
  defaultBaseBranch: (fileConfig.repo && fileConfig.repo.defaultBaseBranch) || (preset.repo && preset.repo.defaultBaseBranch) || 'develop',
91
160
  protectedBranches: (fileConfig.repo && fileConfig.repo.protectedBranches) || (preset.repo && preset.repo.protectedBranches) || ['main', 'master', 'develop', 'prod', 'staging'],
package/lib/git.js CHANGED
@@ -2,11 +2,39 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { execSync } = require('child_process');
4
4
 
5
+ function isBareRepo(repoPath) {
6
+ if (!repoPath || !fs.existsSync(repoPath)) return false;
7
+ try {
8
+ const isBare = execSync(`git --git-dir="${repoPath}" rev-parse --is-bare-repository`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
9
+ return isBare === 'true';
10
+ } catch (e) {
11
+ return false;
12
+ }
13
+ }
14
+
15
+ function getLocalBranches(options = {}) {
16
+ const gitCmd = getGitPrefix(options);
17
+ try {
18
+ const output = execSync(`${gitCmd} branch --format="%(refname:short)"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
19
+ if (!output) return [];
20
+ return output.split('\n').map(b => b.trim()).filter(Boolean);
21
+ } catch (err) {
22
+ return [];
23
+ }
24
+ }
25
+
5
26
  function getGitPrefix(options = {}) {
6
27
  if (options.bareRepo) {
7
28
  return `git --git-dir="${options.bareRepo}"`;
8
29
  }
9
30
  if (options.repoDir) {
31
+ const dotBare = path.join(options.repoDir, '.bare');
32
+ if (fs.existsSync(dotBare)) {
33
+ return `git --git-dir="${dotBare}"`;
34
+ }
35
+ if (isBareRepo(options.repoDir)) {
36
+ return `git --git-dir="${options.repoDir}"`;
37
+ }
10
38
  return `git -C "${options.repoDir}"`;
11
39
  }
12
40
  return 'git';
@@ -16,9 +44,16 @@ function getRepoRootDir(cwd = process.cwd(), bareRepo = null) {
16
44
  if (bareRepo && fs.existsSync(bareRepo)) {
17
45
  return bareRepo;
18
46
  }
47
+ const dotBare = path.join(cwd, '.bare');
48
+ if (fs.existsSync(dotBare)) {
49
+ return cwd;
50
+ }
19
51
  try {
20
52
  const commonDir = execSync('git rev-parse --git-common-dir', { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
21
53
  const absoluteCommonDir = path.isAbsolute(commonDir) ? commonDir : path.resolve(cwd, commonDir);
54
+ if (isBareRepo(absoluteCommonDir)) {
55
+ return absoluteCommonDir;
56
+ }
22
57
  return path.dirname(absoluteCommonDir);
23
58
  } catch (err) {
24
59
  return cwd;
@@ -278,4 +313,6 @@ module.exports = {
278
313
  pruneWorktrees,
279
314
  deleteLocalBranch,
280
315
  deleteRemoteBranch,
316
+ getLocalBranches,
317
+ isBareRepo,
281
318
  };
package/lib/herdr.js CHANGED
@@ -176,15 +176,50 @@ function runInPane(paneId, command) {
176
176
  }
177
177
  }
178
178
 
179
- function closeWorkspacesMatching(matchTargets = []) {
180
- const targets = matchTargets.filter(Boolean);
181
- if (!targets.length) return;
179
+ function listPanes() {
180
+ try {
181
+ const listOutput = execSync('herdr pane list', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
182
+ const data = parseJsonFromOutput(listOutput);
183
+ return (data && data.result && data.result.panes) || [];
184
+ } catch (err) {
185
+ return [];
186
+ }
187
+ }
188
+
189
+ function closeWorkspacesForWorktree(worktreePath, matchTargets = []) {
190
+ const matchedWorkspaceIds = new Set();
191
+ const normalizedPath = worktreePath ? path.resolve(worktreePath) : null;
182
192
 
193
+ // 1. Path-based matching via herdr pane list (highest fidelity, immune to prefix/label changes)
194
+ if (normalizedPath) {
195
+ const panes = listPanes();
196
+ for (const pane of panes) {
197
+ if (pane.workspace_id) {
198
+ const paneCwd = pane.cwd ? path.resolve(pane.cwd) : null;
199
+ const fgCwd = pane.foreground_cwd ? path.resolve(pane.foreground_cwd) : null;
200
+ if (
201
+ (paneCwd && (paneCwd === normalizedPath || paneCwd.startsWith(normalizedPath + path.sep))) ||
202
+ (fgCwd && (fgCwd === normalizedPath || fgCwd.startsWith(normalizedPath + path.sep)))
203
+ ) {
204
+ matchedWorkspaceIds.add(pane.workspace_id);
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ // 2. Exact label matching if matchTargets are provided
211
+ const targets = (Array.isArray(matchTargets) ? matchTargets : [matchTargets]).filter(Boolean);
183
212
  const workspaces = listWorkspaces();
184
- const matchingWorkspaces = workspaces.filter(w => targets.includes(w.label));
213
+ if (targets.length > 0) {
214
+ for (const ws of workspaces) {
215
+ if (ws.label && targets.includes(ws.label)) {
216
+ matchedWorkspaceIds.add(ws.workspace_id);
217
+ }
218
+ }
219
+ }
185
220
 
186
- for (const ws of matchingWorkspaces) {
187
- if (ws.workspace_id) {
221
+ for (const ws of workspaces) {
222
+ if (matchedWorkspaceIds.has(ws.workspace_id)) {
188
223
  console.log(`Closing Herdr workspace "${ws.label}" (ID: ${ws.workspace_id}) and terminating panes...`);
189
224
  closeWorkspace(ws.workspace_id);
190
225
  console.log(`Herdr workspace "${ws.label}" closed.`);
@@ -192,6 +227,10 @@ function closeWorkspacesMatching(matchTargets = []) {
192
227
  }
193
228
  }
194
229
 
230
+ function closeWorkspacesMatching(matchTargets = [], worktreePath = null) {
231
+ return closeWorkspacesForWorktree(worktreePath, matchTargets);
232
+ }
233
+
195
234
  function attachOrSwitchSession(sessionName) {
196
235
  if (process.env.HERDR_ENV === '1') {
197
236
  console.log(`Already inside herdr. Workspace "${sessionName}" created and focused!`);
@@ -206,6 +245,7 @@ module.exports = {
206
245
  getRecommendedInstallCommand,
207
246
  ensureHerdrInstalled,
208
247
  listWorkspaces,
248
+ listPanes,
209
249
  createWorkspace,
210
250
  closeWorkspace,
211
251
  focusWorkspace,
@@ -213,5 +253,6 @@ module.exports = {
213
253
  renamePane,
214
254
  runInPane,
215
255
  closeWorkspacesMatching,
256
+ closeWorkspacesForWorktree,
216
257
  attachOrSwitchSession,
217
258
  };