@tokenbooks/wt 0.2.1 → 0.3.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 +59 -28
- package/dist/cli.js +16 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/new.js +30 -5
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/prune.d.ts +8 -0
- package/dist/commands/prune.js +210 -0
- package/dist/commands/prune.js.map +1 -0
- package/dist/commands/prune.spec.d.ts +1 -0
- package/dist/commands/prune.spec.js +173 -0
- package/dist/commands/prune.spec.js.map +1 -0
- package/dist/commands/remove.d.ts +1 -0
- package/dist/commands/remove.js +39 -1
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/setup.js +144 -7
- package/dist/commands/setup.js.map +1 -1
- package/dist/core/env-patcher.js +8 -8
- package/dist/core/env-patcher.js.map +1 -1
- package/dist/core/git.d.ts +15 -0
- package/dist/core/git.js +59 -0
- package/dist/core/git.js.map +1 -1
- package/dist/core/managed-redis.d.ts +19 -0
- package/dist/core/managed-redis.js +266 -0
- package/dist/core/managed-redis.js.map +1 -0
- package/dist/core/slot-allocator.d.ts +10 -0
- package/dist/core/slot-allocator.js +98 -0
- package/dist/core/slot-allocator.js.map +1 -1
- package/dist/output.js +14 -3
- package/dist/output.js.map +1 -1
- package/dist/schemas/config.schema.d.ts +45 -27
- package/dist/schemas/config.schema.js +22 -6
- package/dist/schemas/config.schema.js.map +1 -1
- package/dist/schemas/registry.schema.d.ts +4 -2
- package/dist/schemas/registry.schema.js +2 -1
- package/dist/schemas/registry.schema.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +2 -1
- package/skills/wt/SKILL.md +31 -12
package/dist/core/git.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
type CommandLogger = (command: string) => void;
|
|
2
|
+
export interface PrunableWorktree {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly reason: string;
|
|
5
|
+
}
|
|
2
6
|
/**
|
|
3
7
|
* Get the main (bare) worktree path from git.
|
|
4
8
|
* Parses `git worktree list --porcelain` to find the first entry.
|
|
@@ -18,4 +22,15 @@ export declare function createWorktree(basePath: string, branchName: string, log
|
|
|
18
22
|
export declare function removeWorktree(worktreePath: string, logCommand?: CommandLogger): void;
|
|
19
23
|
/** Get the current branch name for a worktree path */
|
|
20
24
|
export declare function getBranchName(worktreePath: string): string;
|
|
25
|
+
/** Get uncommitted changes in a worktree (staged, unstaged, untracked) */
|
|
26
|
+
export declare function getUncommittedChanges(worktreePath: string): string[];
|
|
27
|
+
/** Get commits not pushed to upstream tracking branch */
|
|
28
|
+
export declare function getUnsyncedStatus(worktreePath: string): {
|
|
29
|
+
unpushedCommits: string[];
|
|
30
|
+
noUpstream: boolean;
|
|
31
|
+
};
|
|
32
|
+
/** List worktrees that Git currently marks as prunable. */
|
|
33
|
+
export declare function listPrunableWorktrees(): PrunableWorktree[];
|
|
34
|
+
/** Remove Git metadata for prunable worktrees. */
|
|
35
|
+
export declare function pruneWorktrees(logCommand?: CommandLogger): void;
|
|
21
36
|
export {};
|
package/dist/core/git.js
CHANGED
|
@@ -38,6 +38,10 @@ exports.isMainWorktree = isMainWorktree;
|
|
|
38
38
|
exports.createWorktree = createWorktree;
|
|
39
39
|
exports.removeWorktree = removeWorktree;
|
|
40
40
|
exports.getBranchName = getBranchName;
|
|
41
|
+
exports.getUncommittedChanges = getUncommittedChanges;
|
|
42
|
+
exports.getUnsyncedStatus = getUnsyncedStatus;
|
|
43
|
+
exports.listPrunableWorktrees = listPrunableWorktrees;
|
|
44
|
+
exports.pruneWorktrees = pruneWorktrees;
|
|
41
45
|
const node_child_process_1 = require("node:child_process");
|
|
42
46
|
const path = __importStar(require("node:path"));
|
|
43
47
|
/**
|
|
@@ -91,6 +95,61 @@ function getBranchName(worktreePath) {
|
|
|
91
95
|
encoding: 'utf-8',
|
|
92
96
|
}).trim();
|
|
93
97
|
}
|
|
98
|
+
/** Get uncommitted changes in a worktree (staged, unstaged, untracked) */
|
|
99
|
+
function getUncommittedChanges(worktreePath) {
|
|
100
|
+
const output = (0, node_child_process_1.execSync)('git status --porcelain', {
|
|
101
|
+
cwd: worktreePath,
|
|
102
|
+
encoding: 'utf-8',
|
|
103
|
+
}).trim();
|
|
104
|
+
return output.length > 0 ? output.split('\n') : [];
|
|
105
|
+
}
|
|
106
|
+
/** Get commits not pushed to upstream tracking branch */
|
|
107
|
+
function getUnsyncedStatus(worktreePath) {
|
|
108
|
+
try {
|
|
109
|
+
const output = (0, node_child_process_1.execSync)('git log @{upstream}..HEAD --oneline', {
|
|
110
|
+
cwd: worktreePath,
|
|
111
|
+
encoding: 'utf-8',
|
|
112
|
+
stdio: 'pipe',
|
|
113
|
+
}).trim();
|
|
114
|
+
return {
|
|
115
|
+
unpushedCommits: output.length > 0 ? output.split('\n') : [],
|
|
116
|
+
noUpstream: false,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return { unpushedCommits: [], noUpstream: true };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** List worktrees that Git currently marks as prunable. */
|
|
124
|
+
function listPrunableWorktrees() {
|
|
125
|
+
const output = (0, node_child_process_1.execSync)('git worktree list --porcelain', {
|
|
126
|
+
encoding: 'utf-8',
|
|
127
|
+
});
|
|
128
|
+
const blocks = output
|
|
129
|
+
.split('\n\n')
|
|
130
|
+
.map((block) => block.trim())
|
|
131
|
+
.filter((block) => block.length > 0);
|
|
132
|
+
const prunable = [];
|
|
133
|
+
for (const block of blocks) {
|
|
134
|
+
const lines = block.split('\n');
|
|
135
|
+
const worktreeLine = lines.find((line) => line.startsWith('worktree '));
|
|
136
|
+
const prunableLine = lines.find((line) => line.startsWith('prunable '));
|
|
137
|
+
if (!worktreeLine || !prunableLine) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
prunable.push({
|
|
141
|
+
path: worktreeLine.replace('worktree ', ''),
|
|
142
|
+
reason: prunableLine.replace('prunable ', ''),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return prunable;
|
|
146
|
+
}
|
|
147
|
+
/** Remove Git metadata for prunable worktrees. */
|
|
148
|
+
function pruneWorktrees(logCommand) {
|
|
149
|
+
const command = 'git worktree prune --verbose';
|
|
150
|
+
logCommand?.(command);
|
|
151
|
+
(0, node_child_process_1.execSync)(command, { stdio: 'pipe' });
|
|
152
|
+
}
|
|
94
153
|
/** Check if a branch exists locally */
|
|
95
154
|
function branchExistsLocally(branchName) {
|
|
96
155
|
try {
|
package/dist/core/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/core/git.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/core/git.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ServiceConfig, WtConfig } from '../types';
|
|
2
|
+
interface EnsureManagedRedisContainerOptions {
|
|
3
|
+
readonly mainRoot: string;
|
|
4
|
+
readonly slot: number;
|
|
5
|
+
readonly branchName: string;
|
|
6
|
+
readonly worktreePath: string;
|
|
7
|
+
readonly port: number;
|
|
8
|
+
readonly sourceUrl: string | null;
|
|
9
|
+
readonly log?: (message: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function usesManagedRedis(config: WtConfig): boolean;
|
|
12
|
+
export declare function getManagedRedisService(config: WtConfig): ServiceConfig | null;
|
|
13
|
+
export declare function getAllocationServices(config: WtConfig): readonly ServiceConfig[];
|
|
14
|
+
export declare function getManagedRedisContainerName(mainRoot: string, slot: number): string;
|
|
15
|
+
export declare function readManagedRedisSourceUrl(mainRoot: string, config: WtConfig): string | null;
|
|
16
|
+
export declare function patchManagedRedisUrl(sourceUrl: string, port: number): string;
|
|
17
|
+
export declare function ensureManagedRedisContainer(options: EnsureManagedRedisContainerOptions): string;
|
|
18
|
+
export declare function removeManagedRedisContainer(mainRoot: string, slot: number, log?: (message: string) => void): boolean;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.usesManagedRedis = usesManagedRedis;
|
|
37
|
+
exports.getManagedRedisService = getManagedRedisService;
|
|
38
|
+
exports.getAllocationServices = getAllocationServices;
|
|
39
|
+
exports.getManagedRedisContainerName = getManagedRedisContainerName;
|
|
40
|
+
exports.readManagedRedisSourceUrl = readManagedRedisSourceUrl;
|
|
41
|
+
exports.patchManagedRedisUrl = patchManagedRedisUrl;
|
|
42
|
+
exports.ensureManagedRedisContainer = ensureManagedRedisContainer;
|
|
43
|
+
exports.removeManagedRedisContainer = removeManagedRedisContainer;
|
|
44
|
+
const crypto = __importStar(require("node:crypto"));
|
|
45
|
+
const fs = __importStar(require("node:fs"));
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
47
|
+
const node_child_process_1 = require("node:child_process");
|
|
48
|
+
const MANAGED_REDIS_SERVICE_NAME = 'redis';
|
|
49
|
+
const DEFAULT_REDIS_PORT = 6379;
|
|
50
|
+
const DEFAULT_REDIS_IMAGE = 'redis:8-alpine';
|
|
51
|
+
const DOCKER_LABEL_PREFIX = 'dev.tokenbooks.wt';
|
|
52
|
+
function slugify(input) {
|
|
53
|
+
return input
|
|
54
|
+
.toLowerCase()
|
|
55
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
56
|
+
.replace(/^-+|-+$/g, '')
|
|
57
|
+
.slice(0, 32) || 'repo';
|
|
58
|
+
}
|
|
59
|
+
function repoHash(mainRoot) {
|
|
60
|
+
return crypto
|
|
61
|
+
.createHash('sha1')
|
|
62
|
+
.update(mainRoot)
|
|
63
|
+
.digest('hex')
|
|
64
|
+
.slice(0, 8);
|
|
65
|
+
}
|
|
66
|
+
function runDocker(args) {
|
|
67
|
+
try {
|
|
68
|
+
return (0, node_child_process_1.execFileSync)('docker', args, {
|
|
69
|
+
encoding: 'utf-8',
|
|
70
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
71
|
+
}).trim();
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
|
|
75
|
+
throw new Error('Docker CLI not found on PATH.');
|
|
76
|
+
}
|
|
77
|
+
const stderr = err && typeof err === 'object' && 'stderr' in err
|
|
78
|
+
? String(err.stderr ?? '').trim()
|
|
79
|
+
: '';
|
|
80
|
+
throw new Error(stderr || `Docker command failed: docker ${args.join(' ')}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function inspectManagedRedisContainer(containerName) {
|
|
84
|
+
try {
|
|
85
|
+
const raw = runDocker(['container', 'inspect', containerName]);
|
|
86
|
+
const parsed = JSON.parse(raw);
|
|
87
|
+
return parsed[0] ?? null;
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
91
|
+
if (message.includes('No such container')) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function parseManagedRedisPassword(sourceUrl) {
|
|
98
|
+
if (!sourceUrl) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
let parsed;
|
|
102
|
+
try {
|
|
103
|
+
parsed = new URL(sourceUrl);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
if (parsed.protocol !== 'redis:') {
|
|
109
|
+
throw new Error(`Managed Redis only supports redis:// URLs, got: ${parsed.protocol}//`);
|
|
110
|
+
}
|
|
111
|
+
if (parsed.username && parsed.username !== 'default') {
|
|
112
|
+
throw new Error(`Managed Redis does not support non-default ACL usernames in REDIS_URL, got: ${parsed.username}`);
|
|
113
|
+
}
|
|
114
|
+
return parsed.password || undefined;
|
|
115
|
+
}
|
|
116
|
+
function passwordHash(password) {
|
|
117
|
+
return crypto
|
|
118
|
+
.createHash('sha256')
|
|
119
|
+
.update(password ?? '')
|
|
120
|
+
.digest('hex');
|
|
121
|
+
}
|
|
122
|
+
function usesManagedRedis(config) {
|
|
123
|
+
return config.envFiles.some((envFile) => envFile.patches.some((patch) => patch.type === 'redis'));
|
|
124
|
+
}
|
|
125
|
+
function getManagedRedisService(config) {
|
|
126
|
+
if (!usesManagedRedis(config)) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const configured = config.services.find((service) => service.name === MANAGED_REDIS_SERVICE_NAME);
|
|
130
|
+
if (configured) {
|
|
131
|
+
return configured;
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
name: MANAGED_REDIS_SERVICE_NAME,
|
|
135
|
+
defaultPort: DEFAULT_REDIS_PORT,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function getAllocationServices(config) {
|
|
139
|
+
const redisService = getManagedRedisService(config);
|
|
140
|
+
if (!redisService) {
|
|
141
|
+
return config.services;
|
|
142
|
+
}
|
|
143
|
+
if (config.services.some((service) => service.name === MANAGED_REDIS_SERVICE_NAME)) {
|
|
144
|
+
return config.services;
|
|
145
|
+
}
|
|
146
|
+
return [...config.services, redisService];
|
|
147
|
+
}
|
|
148
|
+
function getManagedRedisContainerName(mainRoot, slot) {
|
|
149
|
+
const repoName = slugify(path.basename(mainRoot));
|
|
150
|
+
return `wt-${repoName}-${repoHash(mainRoot)}-slot-${slot}-redis`;
|
|
151
|
+
}
|
|
152
|
+
function readManagedRedisSourceUrl(mainRoot, config) {
|
|
153
|
+
for (const envFile of config.envFiles) {
|
|
154
|
+
const redisVars = envFile.patches
|
|
155
|
+
.filter((patch) => patch.type === 'redis')
|
|
156
|
+
.map((patch) => patch.var);
|
|
157
|
+
if (redisVars.length === 0) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const sourcePath = path.join(mainRoot, envFile.source);
|
|
161
|
+
if (!fs.existsSync(sourcePath)) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const content = fs.readFileSync(sourcePath, 'utf-8');
|
|
165
|
+
for (const redisVar of redisVars) {
|
|
166
|
+
const match = content.match(new RegExp(`^${redisVar}=["']?([^"'\\n]+)`, 'm'));
|
|
167
|
+
if (match?.[1]) {
|
|
168
|
+
return match[1];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function patchManagedRedisUrl(sourceUrl, port) {
|
|
175
|
+
const parsed = new URL(sourceUrl);
|
|
176
|
+
if (parsed.protocol !== 'redis:') {
|
|
177
|
+
throw new Error(`Managed Redis only supports redis:// URLs, got: ${parsed.protocol}//`);
|
|
178
|
+
}
|
|
179
|
+
parsed.hostname = '127.0.0.1';
|
|
180
|
+
parsed.port = String(port);
|
|
181
|
+
parsed.pathname = '/0';
|
|
182
|
+
parsed.search = '';
|
|
183
|
+
parsed.hash = '';
|
|
184
|
+
return parsed.toString();
|
|
185
|
+
}
|
|
186
|
+
function ensureManagedRedisContainer(options) {
|
|
187
|
+
const containerName = getManagedRedisContainerName(options.mainRoot, options.slot);
|
|
188
|
+
const inspect = inspectManagedRedisContainer(containerName);
|
|
189
|
+
const password = parseManagedRedisPassword(options.sourceUrl);
|
|
190
|
+
const expectedPasswordHash = passwordHash(password);
|
|
191
|
+
const expectedPort = String(options.port);
|
|
192
|
+
if (inspect) {
|
|
193
|
+
const actualPort = inspect.NetworkSettings?.Ports?.['6379/tcp']?.[0]?.HostPort;
|
|
194
|
+
const actualPasswordHash = inspect.Config?.Labels?.[`${DOCKER_LABEL_PREFIX}.redis-password-sha256`];
|
|
195
|
+
const actualRepoRoot = inspect.Config?.Labels?.[`${DOCKER_LABEL_PREFIX}.repo-root`];
|
|
196
|
+
const shouldRecreate = actualPort !== expectedPort
|
|
197
|
+
|| actualPasswordHash !== expectedPasswordHash
|
|
198
|
+
|| actualRepoRoot !== options.mainRoot
|
|
199
|
+
|| inspect.Config?.Image !== DEFAULT_REDIS_IMAGE;
|
|
200
|
+
if (shouldRecreate) {
|
|
201
|
+
runDocker(['rm', '-f', containerName]);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
if (!inspect.State?.Running) {
|
|
205
|
+
runDocker(['start', containerName]);
|
|
206
|
+
options.log?.(`Started Redis container '${containerName}'.`);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
options.log?.(`Reusing Redis container '${containerName}'.`);
|
|
210
|
+
}
|
|
211
|
+
return containerName;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const args = [
|
|
215
|
+
'run',
|
|
216
|
+
'-d',
|
|
217
|
+
'--name',
|
|
218
|
+
containerName,
|
|
219
|
+
'--restart',
|
|
220
|
+
'unless-stopped',
|
|
221
|
+
'--label',
|
|
222
|
+
`${DOCKER_LABEL_PREFIX}.managed=true`,
|
|
223
|
+
'--label',
|
|
224
|
+
`${DOCKER_LABEL_PREFIX}.repo-root=${options.mainRoot}`,
|
|
225
|
+
'--label',
|
|
226
|
+
`${DOCKER_LABEL_PREFIX}.service=redis`,
|
|
227
|
+
'--label',
|
|
228
|
+
`${DOCKER_LABEL_PREFIX}.purpose=git-worktree-redis`,
|
|
229
|
+
'--label',
|
|
230
|
+
`${DOCKER_LABEL_PREFIX}.slot=${options.slot}`,
|
|
231
|
+
'--label',
|
|
232
|
+
`${DOCKER_LABEL_PREFIX}.branch=${options.branchName}`,
|
|
233
|
+
'--label',
|
|
234
|
+
`${DOCKER_LABEL_PREFIX}.worktree=${options.worktreePath}`,
|
|
235
|
+
'--label',
|
|
236
|
+
`${DOCKER_LABEL_PREFIX}.redis-password-sha256=${expectedPasswordHash}`,
|
|
237
|
+
'-p',
|
|
238
|
+
`127.0.0.1:${options.port}:6379`,
|
|
239
|
+
DEFAULT_REDIS_IMAGE,
|
|
240
|
+
'redis-server',
|
|
241
|
+
'--save',
|
|
242
|
+
'',
|
|
243
|
+
'--appendonly',
|
|
244
|
+
'no',
|
|
245
|
+
'--port',
|
|
246
|
+
'6379',
|
|
247
|
+
];
|
|
248
|
+
if (password) {
|
|
249
|
+
args.push('--requirepass', password);
|
|
250
|
+
}
|
|
251
|
+
runDocker(args);
|
|
252
|
+
options.log?.(`Started Redis container '${containerName}' on port ${options.port}.`);
|
|
253
|
+
return containerName;
|
|
254
|
+
}
|
|
255
|
+
function removeManagedRedisContainer(mainRoot, slot, log) {
|
|
256
|
+
const containerName = getManagedRedisContainerName(mainRoot, slot);
|
|
257
|
+
const inspect = inspectManagedRedisContainer(containerName);
|
|
258
|
+
if (!inspect) {
|
|
259
|
+
log?.(`Skipping Redis container cleanup; not found: ${containerName}`);
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
runDocker(['rm', '-f', containerName]);
|
|
263
|
+
log?.(`Removed Redis container '${containerName}'.`);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=managed-redis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"managed-redis.js","sourceRoot":"","sources":["../../src/core/managed-redis.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,16 @@ export declare function calculatePorts(slot: number, services: readonly ServiceC
|
|
|
9
9
|
* Returns "baseName_wtN" where N is the slot number.
|
|
10
10
|
*/
|
|
11
11
|
export declare function calculateDbName(slot: number, baseName: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Validate that generated ports stay within range and never collide across
|
|
14
|
+
* the main worktree (slot 0) and configured worktree slots.
|
|
15
|
+
*/
|
|
16
|
+
export declare function validatePortPlan(services: readonly ServiceConfig[], maxSlots: number, stride: number): void;
|
|
17
|
+
export declare function findUnavailableServicePorts(ports: Record<string, number>): Promise<Array<{
|
|
18
|
+
service: string;
|
|
19
|
+
port: number;
|
|
20
|
+
}>>;
|
|
21
|
+
export declare function findAvailablePortSafeSlot(registry: Registry, maxSlots: number, services: readonly ServiceConfig[], stride: number): Promise<number | null>;
|
|
12
22
|
/**
|
|
13
23
|
* Find the next available slot in the registry.
|
|
14
24
|
* Scans slots 1..maxSlots and returns the first unoccupied one.
|
|
@@ -1,8 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.calculatePorts = calculatePorts;
|
|
4
37
|
exports.calculateDbName = calculateDbName;
|
|
38
|
+
exports.validatePortPlan = validatePortPlan;
|
|
39
|
+
exports.findUnavailableServicePorts = findUnavailableServicePorts;
|
|
40
|
+
exports.findAvailablePortSafeSlot = findAvailablePortSafeSlot;
|
|
5
41
|
exports.findAvailableSlot = findAvailableSlot;
|
|
42
|
+
const net = __importStar(require("node:net"));
|
|
6
43
|
/**
|
|
7
44
|
* Calculate port assignments for a given slot.
|
|
8
45
|
* Formula: slot * stride + service.defaultPort
|
|
@@ -21,6 +58,67 @@ function calculatePorts(slot, services, stride) {
|
|
|
21
58
|
function calculateDbName(slot, baseName) {
|
|
22
59
|
return `${baseName}_wt${slot}`;
|
|
23
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Validate that generated ports stay within range and never collide across
|
|
63
|
+
* the main worktree (slot 0) and configured worktree slots.
|
|
64
|
+
*/
|
|
65
|
+
function validatePortPlan(services, maxSlots, stride) {
|
|
66
|
+
const seen = new Map();
|
|
67
|
+
for (let slot = 0; slot <= maxSlots; slot++) {
|
|
68
|
+
for (const service of services) {
|
|
69
|
+
const port = slot * stride + service.defaultPort;
|
|
70
|
+
if (port > 65535) {
|
|
71
|
+
throw new Error(`Port ${port} for service '${service.name}' in slot ${slot} exceeds 65535. ` +
|
|
72
|
+
'Reduce maxSlots, portStride, or the default port.');
|
|
73
|
+
}
|
|
74
|
+
const owner = `slot ${slot} (${service.name})`;
|
|
75
|
+
const existing = seen.get(port);
|
|
76
|
+
if (existing) {
|
|
77
|
+
throw new Error(`Port ${port} collides between ${existing} and ${owner}. ` +
|
|
78
|
+
'Adjust service default ports or increase portStride.');
|
|
79
|
+
}
|
|
80
|
+
seen.set(port, owner);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function isPortAvailable(port) {
|
|
85
|
+
return await new Promise((resolve) => {
|
|
86
|
+
const server = net.createServer();
|
|
87
|
+
const finish = (available) => {
|
|
88
|
+
server.removeAllListeners();
|
|
89
|
+
resolve(available);
|
|
90
|
+
};
|
|
91
|
+
server.once('error', () => finish(false));
|
|
92
|
+
server.once('listening', () => {
|
|
93
|
+
server.close(() => finish(true));
|
|
94
|
+
});
|
|
95
|
+
server.listen(port, '127.0.0.1');
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function findUnavailableServicePorts(ports) {
|
|
99
|
+
const entries = Object.entries(ports);
|
|
100
|
+
const checks = await Promise.all(entries.map(async ([service, port]) => ({
|
|
101
|
+
service,
|
|
102
|
+
port,
|
|
103
|
+
available: await isPortAvailable(port),
|
|
104
|
+
})));
|
|
105
|
+
return checks
|
|
106
|
+
.filter((item) => !item.available)
|
|
107
|
+
.map(({ service, port }) => ({ service, port }));
|
|
108
|
+
}
|
|
109
|
+
async function findAvailablePortSafeSlot(registry, maxSlots, services, stride) {
|
|
110
|
+
for (let slot = 1; slot <= maxSlots; slot++) {
|
|
111
|
+
if (String(slot) in registry.allocations) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const ports = calculatePorts(slot, services, stride);
|
|
115
|
+
const unavailable = await findUnavailableServicePorts(ports);
|
|
116
|
+
if (unavailable.length === 0) {
|
|
117
|
+
return slot;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
24
122
|
/**
|
|
25
123
|
* Find the next available slot in the registry.
|
|
26
124
|
* Scans slots 1..maxSlots and returns the first unoccupied one.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slot-allocator.js","sourceRoot":"","sources":["../../src/core/slot-allocator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"slot-allocator.js","sourceRoot":"","sources":["../../src/core/slot-allocator.ts"],"names":[],"mappings":"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"}
|
package/dist/output.js
CHANGED
|
@@ -64,15 +64,24 @@ function formatAllocationTable(allocations) {
|
|
|
64
64
|
.map(([name, port]) => `${name}:${port}`)
|
|
65
65
|
.join(' ');
|
|
66
66
|
const status = fs.existsSync(alloc.worktreePath) ? 'ok' : 'stale';
|
|
67
|
-
return padRow([slot, alloc.branchName, alloc.dbName,
|
|
67
|
+
return padRow([slot, alloc.branchName, alloc.dbName, formatRedisCell(alloc), portStr, status]);
|
|
68
68
|
});
|
|
69
69
|
return [header, separator, ...rows].join('\n');
|
|
70
70
|
}
|
|
71
71
|
/** Pad columns to fixed widths for table output */
|
|
72
72
|
function padRow(cols) {
|
|
73
|
-
const widths = [6, 30, 20,
|
|
73
|
+
const widths = [6, 30, 20, 18, 35, 8];
|
|
74
74
|
return cols.map((col, i) => col.padEnd(widths[i] ?? 10)).join('');
|
|
75
75
|
}
|
|
76
|
+
function formatRedisCell(alloc) {
|
|
77
|
+
if (alloc.redisContainerName) {
|
|
78
|
+
return alloc.ports.redis ? `port:${alloc.ports.redis}` : alloc.redisContainerName;
|
|
79
|
+
}
|
|
80
|
+
if (alloc.redisDb !== undefined) {
|
|
81
|
+
return `db:${alloc.redisDb}`;
|
|
82
|
+
}
|
|
83
|
+
return '-';
|
|
84
|
+
}
|
|
76
85
|
/** Print a setup summary for human output */
|
|
77
86
|
function formatSetupSummary(slot, alloc) {
|
|
78
87
|
const portLines = Object.entries(alloc.ports)
|
|
@@ -82,7 +91,9 @@ function formatSetupSummary(slot, alloc) {
|
|
|
82
91
|
`Worktree configured (slot ${slot}):`,
|
|
83
92
|
` Branch: ${alloc.branchName}`,
|
|
84
93
|
` Database: ${alloc.dbName}`,
|
|
85
|
-
|
|
94
|
+
alloc.redisContainerName
|
|
95
|
+
? ` Redis: ${alloc.redisContainerName}${alloc.ports.redis ? ` (port ${alloc.ports.redis})` : ''}`
|
|
96
|
+
: ` Redis DB: ${alloc.redisDb ?? '-'}`,
|
|
86
97
|
` Ports:`,
|
|
87
98
|
portLines,
|
|
88
99
|
` Path: ${alloc.worktreePath}`,
|
package/dist/output.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,gCAEC;AAGD,0BAEC;AAGD,sBAEC;AAGD,sDAmBC;
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"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"}
|