@tsuzuku/arise 1.1.0 → 1.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.
- package/index.js +2 -1
- package/lib/config.js +55 -2
- package/lib/git.js +8 -0
- package/lib/lifecycle/create.js +21 -13
- package/lib/lifecycle/nuke.js +16 -11
- package/package.json +1 -2
- package/worktree.schema.json +0 -130
package/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const pkg = require('./package.json');
|
|
1
2
|
const { parseArgs, showUsage } = require('./lib/cli');
|
|
2
3
|
const { resolveConfiguration } = require('./lib/config');
|
|
3
4
|
const { executeCreate } = require('./lib/lifecycle/create');
|
|
@@ -12,7 +13,7 @@ async function run(argv = process.argv.slice(2), cwd = process.cwd()) {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
if (flags.showVersion) {
|
|
15
|
-
console.log(
|
|
16
|
+
console.log(`arise v${pkg.version}`);
|
|
16
17
|
process.exit(0);
|
|
17
18
|
}
|
|
18
19
|
|
package/lib/config.js
CHANGED
|
@@ -70,7 +70,56 @@ function loadConfigFile(filePath) {
|
|
|
70
70
|
|
|
71
71
|
function resolveConfiguration(flags = {}, cwd = process.cwd()) {
|
|
72
72
|
const repoRoot = git.getRepoRootDir(cwd);
|
|
73
|
-
const
|
|
73
|
+
const searchDirs = [];
|
|
74
|
+
|
|
75
|
+
const addSearchDir = (dir) => {
|
|
76
|
+
if (dir && !searchDirs.includes(dir)) {
|
|
77
|
+
searchDirs.push(dir);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
let worktrees = [];
|
|
82
|
+
try {
|
|
83
|
+
worktrees = git.getWorktrees({ repoDir: repoRoot });
|
|
84
|
+
} catch (e) {}
|
|
85
|
+
|
|
86
|
+
// 1. Explicit directory if --dirname / -d is passed
|
|
87
|
+
if (flags.dirname) {
|
|
88
|
+
const resolvedDir = path.isAbsolute(flags.dirname)
|
|
89
|
+
? flags.dirname
|
|
90
|
+
: path.resolve(repoRoot || cwd, flags.dirname);
|
|
91
|
+
addSearchDir(resolvedDir);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 2. Directory for the specified branch (--branch / -b)
|
|
95
|
+
if (flags.branch) {
|
|
96
|
+
const matchingWt = worktrees.find((wt) => wt.branch === flags.branch);
|
|
97
|
+
if (matchingWt && matchingWt.path) {
|
|
98
|
+
addSearchDir(matchingWt.path);
|
|
99
|
+
}
|
|
100
|
+
const branchDir = path.resolve(repoRoot || cwd, flags.branch.replace(/\//g, '-'));
|
|
101
|
+
addSearchDir(branchDir);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 3. Current working directory
|
|
105
|
+
addSearchDir(cwd);
|
|
106
|
+
|
|
107
|
+
// 4. Repository root directory
|
|
108
|
+
if (repoRoot) {
|
|
109
|
+
addSearchDir(repoRoot);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 5. Fallback to existing worktrees (prioritizing primary branches like main, master, develop)
|
|
113
|
+
const primaryBranches = ['main', 'master', 'develop', 'prod', 'staging'];
|
|
114
|
+
const primaryWts = worktrees.filter((wt) => !wt.isBare && wt.branch && primaryBranches.includes(wt.branch));
|
|
115
|
+
for (const wt of primaryWts) {
|
|
116
|
+
if (wt.path) addSearchDir(wt.path);
|
|
117
|
+
}
|
|
118
|
+
for (const wt of worktrees) {
|
|
119
|
+
if (wt.path && !wt.isBare) addSearchDir(wt.path);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const configFile = findConfigFile(searchDirs);
|
|
74
123
|
const fileConfig = loadConfigFile(configFile);
|
|
75
124
|
|
|
76
125
|
// 1. Resolve Preset
|
|
@@ -80,7 +129,11 @@ function resolveConfiguration(flags = {}, cwd = process.cwd()) {
|
|
|
80
129
|
preset = getPreset(presetName);
|
|
81
130
|
}
|
|
82
131
|
if (!preset) {
|
|
83
|
-
|
|
132
|
+
const detectDir = (configFile && path.dirname(configFile)) || cwd;
|
|
133
|
+
preset = detectPreset(detectDir);
|
|
134
|
+
if (preset.name === 'generic' && detectDir !== cwd) {
|
|
135
|
+
preset = detectPreset(cwd);
|
|
136
|
+
}
|
|
84
137
|
}
|
|
85
138
|
|
|
86
139
|
// 2. Merge Repo Settings
|
package/lib/git.js
CHANGED
|
@@ -7,6 +7,10 @@ function getGitPrefix(options = {}) {
|
|
|
7
7
|
return `git --git-dir="${options.bareRepo}"`;
|
|
8
8
|
}
|
|
9
9
|
if (options.repoDir) {
|
|
10
|
+
const dotBare = path.join(options.repoDir, '.bare');
|
|
11
|
+
if (fs.existsSync(dotBare)) {
|
|
12
|
+
return `git --git-dir="${dotBare}"`;
|
|
13
|
+
}
|
|
10
14
|
return `git -C "${options.repoDir}"`;
|
|
11
15
|
}
|
|
12
16
|
return 'git';
|
|
@@ -16,6 +20,10 @@ function getRepoRootDir(cwd = process.cwd(), bareRepo = null) {
|
|
|
16
20
|
if (bareRepo && fs.existsSync(bareRepo)) {
|
|
17
21
|
return bareRepo;
|
|
18
22
|
}
|
|
23
|
+
const dotBare = path.join(cwd, '.bare');
|
|
24
|
+
if (fs.existsSync(dotBare)) {
|
|
25
|
+
return cwd;
|
|
26
|
+
}
|
|
19
27
|
try {
|
|
20
28
|
const commonDir = execSync('git rev-parse --git-common-dir', { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
21
29
|
const absoluteCommonDir = path.isAbsolute(commonDir) ? commonDir : path.resolve(cwd, commonDir);
|
package/lib/lifecycle/create.js
CHANGED
|
@@ -4,6 +4,7 @@ const git = require('../git');
|
|
|
4
4
|
const herdr = require('../herdr');
|
|
5
5
|
const layout = require('../layout');
|
|
6
6
|
const { createContext } = require('../context');
|
|
7
|
+
const { resolveConfiguration } = require('../config');
|
|
7
8
|
const { showUsage } = require('../cli');
|
|
8
9
|
|
|
9
10
|
async function executeCreate(flags, config, cwd = process.cwd()) {
|
|
@@ -24,8 +25,8 @@ async function executeCreate(flags, config, cwd = process.cwd()) {
|
|
|
24
25
|
|
|
25
26
|
// 2. Resolve Session / Workspace Name
|
|
26
27
|
const baseWorkspace = flags.workspaceName || dirname.replace(/[\s.:]/g, '_');
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
let prefix = config.workspace.labelPrefix || '';
|
|
29
|
+
let sessionName = (prefix && !baseWorkspace.startsWith(prefix.trim()))
|
|
29
30
|
? `${prefix}${baseWorkspace}`
|
|
30
31
|
: baseWorkspace;
|
|
31
32
|
|
|
@@ -69,25 +70,32 @@ async function executeCreate(flags, config, cwd = process.cwd()) {
|
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
// 5.
|
|
73
|
+
// 5. Re-resolve config using target worktree directory for branch-specific overrides
|
|
74
|
+
const activeConfig = resolveConfiguration(flags, worktreePathToUse);
|
|
75
|
+
prefix = activeConfig.workspace.labelPrefix || '';
|
|
76
|
+
sessionName = (prefix && !baseWorkspace.startsWith(prefix.trim()))
|
|
77
|
+
? `${prefix}${baseWorkspace}`
|
|
78
|
+
: baseWorkspace;
|
|
79
|
+
|
|
80
|
+
// Initialize Context for Hooks
|
|
73
81
|
const ctx = createContext({
|
|
74
82
|
worktreePath: worktreePathToUse,
|
|
75
83
|
repoRoot,
|
|
76
|
-
bareRepo:
|
|
84
|
+
bareRepo: activeConfig.repo.bareRepo,
|
|
77
85
|
branch,
|
|
78
|
-
source: flags.source ||
|
|
86
|
+
source: flags.source || activeConfig.repo.defaultBaseBranch,
|
|
79
87
|
flags,
|
|
80
|
-
preset:
|
|
81
|
-
config,
|
|
88
|
+
preset: activeConfig.preset,
|
|
89
|
+
config: activeConfig,
|
|
82
90
|
});
|
|
83
91
|
|
|
84
92
|
// 6. Run Preset / Config Hooks
|
|
85
|
-
if (typeof
|
|
86
|
-
await
|
|
93
|
+
if (typeof activeConfig.hooks.onSyncPrimary === 'function') {
|
|
94
|
+
await activeConfig.hooks.onSyncPrimary(ctx);
|
|
87
95
|
}
|
|
88
96
|
|
|
89
|
-
if (!worktreeExists && typeof
|
|
90
|
-
await
|
|
97
|
+
if (!worktreeExists && typeof activeConfig.hooks.onScaffold === 'function') {
|
|
98
|
+
await activeConfig.hooks.onScaffold(ctx);
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
// 7. Orchestrate Herdr Workspace
|
|
@@ -105,10 +113,10 @@ async function executeCreate(flags, config, cwd = process.cwd()) {
|
|
|
105
113
|
// 8. Render Terminal Layout
|
|
106
114
|
console.log(`==> Configuring terminal panes...`);
|
|
107
115
|
layout.renderLayout({
|
|
108
|
-
layout:
|
|
116
|
+
layout: activeConfig.layout,
|
|
109
117
|
rootPaneId: ws.rootPaneId,
|
|
110
118
|
cwd: worktreePathToUse,
|
|
111
|
-
focusTarget: flags.focusTarget ||
|
|
119
|
+
focusTarget: flags.focusTarget || activeConfig.workspace.defaultFocus,
|
|
112
120
|
});
|
|
113
121
|
|
|
114
122
|
// Focus the workspace
|
package/lib/lifecycle/nuke.js
CHANGED
|
@@ -4,6 +4,7 @@ const { execSync } = require('child_process');
|
|
|
4
4
|
const git = require('../git');
|
|
5
5
|
const herdr = require('../herdr');
|
|
6
6
|
const { createContext } = require('../context');
|
|
7
|
+
const { resolveConfiguration } = require('../config');
|
|
7
8
|
const { showUsage } = require('../cli');
|
|
8
9
|
|
|
9
10
|
async function executeNuke(flags, config, cwd = process.cwd()) {
|
|
@@ -50,6 +51,10 @@ async function executeNuke(flags, config, cwd = process.cwd()) {
|
|
|
50
51
|
const targetDirname = path.basename(targetWorktreePath);
|
|
51
52
|
const targetSessionName = targetDirname.replace(/[\s.:]/g, '_');
|
|
52
53
|
|
|
54
|
+
const activeConfig = fs.existsSync(targetWorktreePath)
|
|
55
|
+
? resolveConfiguration(flags, targetWorktreePath)
|
|
56
|
+
: config;
|
|
57
|
+
|
|
53
58
|
let targetBranch = flags.branch;
|
|
54
59
|
if (!targetBranch && existingWorktree && existingWorktree.branch) {
|
|
55
60
|
targetBranch = existingWorktree.branch;
|
|
@@ -64,14 +69,14 @@ async function executeNuke(flags, config, cwd = process.cwd()) {
|
|
|
64
69
|
}
|
|
65
70
|
if (!targetBranch) {
|
|
66
71
|
try {
|
|
67
|
-
const gitCmd = git.getGitPrefix({ repoDir: repoRoot, bareRepo:
|
|
72
|
+
const gitCmd = git.getGitPrefix({ repoDir: repoRoot, bareRepo: activeConfig.repo.bareRepo });
|
|
68
73
|
execSync(`${gitCmd} rev-parse --verify "${target}"`, { stdio: 'ignore' });
|
|
69
74
|
targetBranch = target;
|
|
70
75
|
} catch (e) {}
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
console.log(`\n=== Starting Worktree Nuke ===`);
|
|
74
|
-
console.log(`Active Preset: ${
|
|
79
|
+
console.log(`Active Preset: ${activeConfig.preset ? activeConfig.preset.name : 'generic'}`);
|
|
75
80
|
console.log(`Repository Root: ${repoRoot}`);
|
|
76
81
|
console.log(`Target Worktree Path: ${targetWorktreePath}`);
|
|
77
82
|
console.log(`Associated Branch: ${targetBranch || '(None detected)'}`);
|
|
@@ -96,16 +101,16 @@ async function executeNuke(flags, config, cwd = process.cwd()) {
|
|
|
96
101
|
const ctx = createContext({
|
|
97
102
|
worktreePath: targetWorktreePath,
|
|
98
103
|
repoRoot,
|
|
99
|
-
bareRepo:
|
|
104
|
+
bareRepo: activeConfig.repo.bareRepo,
|
|
100
105
|
branch: targetBranch,
|
|
101
106
|
flags,
|
|
102
|
-
preset:
|
|
103
|
-
config,
|
|
107
|
+
preset: activeConfig.preset,
|
|
108
|
+
config: activeConfig,
|
|
104
109
|
});
|
|
105
110
|
|
|
106
111
|
// Pre-Nuke Hook
|
|
107
|
-
if (typeof
|
|
108
|
-
await
|
|
112
|
+
if (typeof activeConfig.hooks.onPreNuke === 'function') {
|
|
113
|
+
await activeConfig.hooks.onPreNuke(ctx);
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
// 1. Remove Worktree & Directory
|
|
@@ -139,12 +144,12 @@ async function executeNuke(flags, config, cwd = process.cwd()) {
|
|
|
139
144
|
// Helper for final cleanup steps (Herdr workspace close + Post-Nuke hook)
|
|
140
145
|
const finishCleanup = async () => {
|
|
141
146
|
// Post-Nuke Hook
|
|
142
|
-
if (typeof
|
|
143
|
-
await
|
|
147
|
+
if (typeof activeConfig.hooks.onPostNuke === 'function') {
|
|
148
|
+
await activeConfig.hooks.onPostNuke(ctx);
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
// Close Herdr Workspace if running (performed last so active pane is not terminated mid-cleanup)
|
|
147
|
-
const prefix =
|
|
152
|
+
const prefix = activeConfig.workspace.labelPrefix || '';
|
|
148
153
|
const matchTargets = [
|
|
149
154
|
targetSessionName,
|
|
150
155
|
targetDirname,
|
|
@@ -165,7 +170,7 @@ async function executeNuke(flags, config, cwd = process.cwd()) {
|
|
|
165
170
|
return;
|
|
166
171
|
}
|
|
167
172
|
|
|
168
|
-
const protectedBranches =
|
|
173
|
+
const protectedBranches = activeConfig.repo.protectedBranches || ['main', 'master', 'develop', 'prod', 'staging', 'production'];
|
|
169
174
|
if (targetBranch && protectedBranches.includes(targetBranch)) {
|
|
170
175
|
console.warn(`\n==> WARNING: "${targetBranch}" is a protected branch. Skipping local and remote branch deletion.`);
|
|
171
176
|
await finishCleanup();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsuzuku/arise",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"index.js",
|
|
21
21
|
"types.d.ts",
|
|
22
22
|
"arise.schema.json",
|
|
23
|
-
"worktree.schema.json",
|
|
24
23
|
"README.md"
|
|
25
24
|
],
|
|
26
25
|
"engines": {
|
package/worktree.schema.json
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"title": "HerdrWorktreeConfig",
|
|
4
|
-
"description": "Configuration schema for Herdr Worktree (.worktreerc.json)",
|
|
5
|
-
"type": "object",
|
|
6
|
-
"properties": {
|
|
7
|
-
"preset": {
|
|
8
|
-
"type": "string",
|
|
9
|
-
"description": "Preset name to use ('node', 'laravel', 'generic' or custom registered preset)"
|
|
10
|
-
},
|
|
11
|
-
"repo": {
|
|
12
|
-
"type": "object",
|
|
13
|
-
"description": "Repository topology and branch configuration",
|
|
14
|
-
"properties": {
|
|
15
|
-
"bareRepo": {
|
|
16
|
-
"type": ["string", "null"],
|
|
17
|
-
"description": "Path to bare git repository (e.g. '/path/to/bare/repo.git')"
|
|
18
|
-
},
|
|
19
|
-
"worktreesBase": {
|
|
20
|
-
"type": ["string", "null"],
|
|
21
|
-
"description": "Base directory where worktrees should be placed (e.g. '/path/to/worktrees')"
|
|
22
|
-
},
|
|
23
|
-
"defaultBaseBranch": {
|
|
24
|
-
"type": "string",
|
|
25
|
-
"description": "Default base branch to branch off (e.g. 'develop', 'prod', 'main')"
|
|
26
|
-
},
|
|
27
|
-
"protectedBranches": {
|
|
28
|
-
"type": "array",
|
|
29
|
-
"items": { "type": "string" },
|
|
30
|
-
"description": "Array of branch names protected against deletion during --nuke"
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"additionalProperties": false
|
|
34
|
-
},
|
|
35
|
-
"workspace": {
|
|
36
|
-
"type": "object",
|
|
37
|
-
"description": "Herdr workspace naming, agent, and focus configuration",
|
|
38
|
-
"properties": {
|
|
39
|
-
"labelPrefix": {
|
|
40
|
-
"type": "string",
|
|
41
|
-
"description": "Prefix added to Herdr workspace labels (e.g. '[BE] ')"
|
|
42
|
-
},
|
|
43
|
-
"agent": {
|
|
44
|
-
"type": ["string", "object", "null"],
|
|
45
|
-
"description": "CLI AI agent to run in the workspace pane ('agy', 'claude', 'aider', 'copilot', 'none', or custom command object)",
|
|
46
|
-
"properties": {
|
|
47
|
-
"cmd": {
|
|
48
|
-
"type": ["string", "null"],
|
|
49
|
-
"description": "Command to execute the agent"
|
|
50
|
-
},
|
|
51
|
-
"title": {
|
|
52
|
-
"type": "string",
|
|
53
|
-
"description": "Display title for the agent pane in Herdr"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
"defaultFocus": {
|
|
58
|
-
"type": "string",
|
|
59
|
-
"description": "Default pane to focus upon creation ('agent', 'agy', 'claude', 'vim', 'logs', 'server', 'shell')"
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
"additionalProperties": false
|
|
63
|
-
},
|
|
64
|
-
"layout": {
|
|
65
|
-
"type": "array",
|
|
66
|
-
"description": "Declarative 4-pane quadrant layout definitions",
|
|
67
|
-
"items": {
|
|
68
|
-
"type": "object",
|
|
69
|
-
"required": ["id", "title"],
|
|
70
|
-
"properties": {
|
|
71
|
-
"id": {
|
|
72
|
-
"type": "string",
|
|
73
|
-
"description": "Unique identifier for this pane within the layout"
|
|
74
|
-
},
|
|
75
|
-
"title": {
|
|
76
|
-
"type": "string",
|
|
77
|
-
"description": "Title displayed on the Herdr pane"
|
|
78
|
-
},
|
|
79
|
-
"cmd": {
|
|
80
|
-
"type": ["string", "null"],
|
|
81
|
-
"description": "Command to run when pane is opened (e.g. 'vim .', 'npm run dev', 'tail -f ...')"
|
|
82
|
-
},
|
|
83
|
-
"position": {
|
|
84
|
-
"type": "string",
|
|
85
|
-
"enum": ["root"],
|
|
86
|
-
"description": "Set to 'root' for the initial root pane"
|
|
87
|
-
},
|
|
88
|
-
"from": {
|
|
89
|
-
"type": "string",
|
|
90
|
-
"description": "ID of parent pane to split from"
|
|
91
|
-
},
|
|
92
|
-
"split": {
|
|
93
|
-
"type": "string",
|
|
94
|
-
"enum": ["right", "down"],
|
|
95
|
-
"description": "Split direction ('right' or 'down')"
|
|
96
|
-
},
|
|
97
|
-
"focus": {
|
|
98
|
-
"type": "boolean",
|
|
99
|
-
"description": "Whether this pane should receive focus by default"
|
|
100
|
-
},
|
|
101
|
-
"isAgent": {
|
|
102
|
-
"type": "boolean",
|
|
103
|
-
"description": "Whether this pane is designated as the AI CLI agent pane"
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
"additionalProperties": false
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
"scaffold": {
|
|
110
|
-
"type": "object",
|
|
111
|
-
"description": "Scaffolding options (environment files, symlinks, dependency installation, etc.)",
|
|
112
|
-
"properties": {
|
|
113
|
-
"envSource": {
|
|
114
|
-
"type": ["string", "null"],
|
|
115
|
-
"description": "Path to .env source file to copy into new worktree"
|
|
116
|
-
},
|
|
117
|
-
"symlink": {
|
|
118
|
-
"type": ["string", "null"],
|
|
119
|
-
"description": "Path to web server symlink to update to active worktree"
|
|
120
|
-
},
|
|
121
|
-
"install": {
|
|
122
|
-
"type": ["string", "boolean", "null"],
|
|
123
|
-
"description": "Command to run to install dependencies upon creation (e.g. 'npm install --legacy-peer-deps', 'pnpm install', 'composer install --no-interaction'), or false/null to skip installation."
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
"additionalProperties": true
|
|
127
|
-
}
|
|
128
|
-
},
|
|
129
|
-
"additionalProperties": false
|
|
130
|
-
}
|