@gricha/perry 0.3.18 → 0.3.19
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/dist/client/api.js +2 -2
- package/dist/index.js +4 -4
- package/dist/perry-worker +0 -0
- package/dist/worker/client.js +5 -1
- package/dist/workspace/manager.js +55 -20
- package/package.json +1 -1
package/dist/client/api.js
CHANGED
|
@@ -174,7 +174,7 @@ export class ApiClient {
|
|
|
174
174
|
return new ApiClientError('Unknown error', 0, 'UNKNOWN');
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
-
export function createApiClient(worker, port) {
|
|
177
|
+
export function createApiClient(worker, port, timeoutMs) {
|
|
178
178
|
let baseUrl;
|
|
179
179
|
if (worker.includes('://')) {
|
|
180
180
|
baseUrl = worker;
|
|
@@ -186,5 +186,5 @@ export function createApiClient(worker, port) {
|
|
|
186
186
|
const effectivePort = port || DEFAULT_AGENT_PORT;
|
|
187
187
|
baseUrl = `http://${worker}:${effectivePort}`;
|
|
188
188
|
}
|
|
189
|
-
return new ApiClient({ baseUrl });
|
|
189
|
+
return new ApiClient({ baseUrl, timeout: timeoutMs });
|
|
190
190
|
}
|
package/dist/index.js
CHANGED
|
@@ -118,9 +118,9 @@ async function checkLocalAgent() {
|
|
|
118
118
|
return false;
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
-
async function getClient() {
|
|
121
|
+
async function getClient(timeoutMs) {
|
|
122
122
|
const worker = await getWorkerWithFallback();
|
|
123
|
-
return createApiClient(worker);
|
|
123
|
+
return createApiClient(worker, undefined, timeoutMs);
|
|
124
124
|
}
|
|
125
125
|
async function getWorkerWithFallback() {
|
|
126
126
|
let worker = await getWorker();
|
|
@@ -284,7 +284,7 @@ program
|
|
|
284
284
|
.option('-a, --all', 'Sync all running workspaces')
|
|
285
285
|
.action(async (name, options) => {
|
|
286
286
|
try {
|
|
287
|
-
const client = await getClient();
|
|
287
|
+
const client = await getClient(5 * 60 * 1000);
|
|
288
288
|
if (options.all) {
|
|
289
289
|
console.log('Syncing all running workspaces...');
|
|
290
290
|
const result = await client.syncAllWorkspaces();
|
|
@@ -846,7 +846,7 @@ program
|
|
|
846
846
|
try {
|
|
847
847
|
console.log('');
|
|
848
848
|
console.log('Syncing all running workspaces...');
|
|
849
|
-
const client = createApiClient(`localhost:${agentPort}
|
|
849
|
+
const client = createApiClient(`localhost:${agentPort}`, undefined, 5 * 60 * 1000);
|
|
850
850
|
const result = await client.syncAllWorkspaces();
|
|
851
851
|
if (result.results.length === 0) {
|
|
852
852
|
console.log('No running workspaces to sync.');
|
package/dist/perry-worker
CHANGED
|
Binary file
|
package/dist/worker/client.js
CHANGED
|
@@ -31,7 +31,11 @@ async function isWorkerRunning(ip) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
async function startWorkerInContainer(containerName) {
|
|
34
|
-
await execInContainer(containerName, [
|
|
34
|
+
await execInContainer(containerName, [
|
|
35
|
+
'sh',
|
|
36
|
+
'-c',
|
|
37
|
+
"nohup sh -c 'if [ -x /usr/local/bin/perry ]; then exec /usr/local/bin/perry worker serve; else exec perry worker serve; fi' > /tmp/perry-worker.log 2>&1 &",
|
|
38
|
+
], { user: 'workspace' });
|
|
35
39
|
}
|
|
36
40
|
async function ensureWorkerRunning(containerName) {
|
|
37
41
|
const ip = await getContainerIp(containerName);
|
|
@@ -217,35 +217,38 @@ export class WorkspaceManager {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
|
-
async setupWorkspaceCredentials(containerName, workspaceName) {
|
|
220
|
+
async setupWorkspaceCredentials(containerName, workspaceName, options) {
|
|
221
221
|
await this.copyGitConfig(containerName);
|
|
222
222
|
await this.copyCredentialFiles(containerName);
|
|
223
223
|
await this.syncEnvironmentFile(containerName);
|
|
224
224
|
await syncAllAgents(containerName, this.config);
|
|
225
225
|
await this.copyPerryWorker(containerName);
|
|
226
|
-
await this.
|
|
226
|
+
await this.ensurePerryOnPath(containerName);
|
|
227
|
+
await this.startWorkerServer(containerName, options);
|
|
227
228
|
if (workspaceName) {
|
|
228
229
|
await this.setupSSHKeys(containerName, workspaceName);
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
232
|
async copyPerryWorker(containerName) {
|
|
232
233
|
const installedPath = path.join(os.homedir(), '.perry', 'bin', 'perry');
|
|
234
|
+
const cwdDistPath = path.join(process.cwd(), 'dist', 'perry-worker');
|
|
233
235
|
const distDir = path.dirname(new URL(import.meta.url).pathname);
|
|
234
236
|
const distPath = path.join(distDir, '..', 'perry-worker');
|
|
235
|
-
let workerBinaryPath =
|
|
236
|
-
|
|
237
|
-
await fs.access(installedPath);
|
|
238
|
-
workerBinaryPath = installedPath;
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
237
|
+
let workerBinaryPath = null;
|
|
238
|
+
for (const candidate of [installedPath, cwdDistPath, distPath]) {
|
|
241
239
|
try {
|
|
242
|
-
await fs.access(
|
|
240
|
+
await fs.access(candidate);
|
|
241
|
+
workerBinaryPath = candidate;
|
|
242
|
+
break;
|
|
243
243
|
}
|
|
244
244
|
catch {
|
|
245
|
-
|
|
246
|
-
return;
|
|
245
|
+
// Try next
|
|
247
246
|
}
|
|
248
247
|
}
|
|
248
|
+
if (!workerBinaryPath) {
|
|
249
|
+
console.warn(`[sync] perry binary not found at ${installedPath}, ${cwdDistPath}, or ${distPath}, session discovery may not work`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
249
252
|
const destPath = '/usr/local/bin/perry';
|
|
250
253
|
await docker.copyToContainer(containerName, workerBinaryPath, destPath);
|
|
251
254
|
await docker.execInContainer(containerName, ['chown', 'root:root', destPath], {
|
|
@@ -255,6 +258,13 @@ export class WorkspaceManager {
|
|
|
255
258
|
user: 'root',
|
|
256
259
|
});
|
|
257
260
|
}
|
|
261
|
+
async ensurePerryOnPath(containerName) {
|
|
262
|
+
await docker.execInContainer(containerName, [
|
|
263
|
+
'sh',
|
|
264
|
+
'-c',
|
|
265
|
+
'if [ -x /usr/local/bin/perry ]; then mkdir -p /home/workspace/.local/bin && ln -sf /usr/local/bin/perry /home/workspace/.local/bin/perry; fi',
|
|
266
|
+
], { user: 'workspace' });
|
|
267
|
+
}
|
|
258
268
|
async updateWorkerBinary(name) {
|
|
259
269
|
const workspace = await this.state.getWorkspace(name);
|
|
260
270
|
if (!workspace) {
|
|
@@ -267,27 +277,42 @@ export class WorkspaceManager {
|
|
|
267
277
|
}
|
|
268
278
|
await docker.execInContainer(containerName, ['sh', '-c', 'pkill -f "perry worker serve" || true'], { user: 'workspace' });
|
|
269
279
|
await this.copyPerryWorker(containerName);
|
|
270
|
-
await this.startWorkerServer(containerName);
|
|
280
|
+
await this.startWorkerServer(containerName, { strictWorker: true });
|
|
271
281
|
}
|
|
272
|
-
async startWorkerServer(containerName) {
|
|
282
|
+
async startWorkerServer(containerName, options) {
|
|
273
283
|
const WORKER_PORT = 7392;
|
|
274
284
|
const ip = await docker.getContainerIp(containerName);
|
|
275
285
|
if (!ip) {
|
|
276
286
|
console.warn(`[sync] Could not get container IP for ${containerName}, skipping worker server`);
|
|
277
287
|
return;
|
|
278
288
|
}
|
|
289
|
+
const desiredVersion = pkg.version;
|
|
290
|
+
const hasSyncedPerry = (await docker.execInContainer(containerName, ['sh', '-c', 'test -x /usr/local/bin/perry'], {
|
|
291
|
+
user: 'workspace',
|
|
292
|
+
})).exitCode === 0;
|
|
279
293
|
try {
|
|
280
294
|
const healthResponse = await fetch(`http://${ip}:${WORKER_PORT}/health`, {
|
|
281
295
|
signal: AbortSignal.timeout(1000),
|
|
282
296
|
});
|
|
283
297
|
if (healthResponse.ok) {
|
|
284
|
-
|
|
298
|
+
if (!hasSyncedPerry) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const health = (await healthResponse.json().catch(() => null));
|
|
302
|
+
if (health?.version === desiredVersion) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
await docker.execInContainer(containerName, ['sh', '-c', 'pkill -f "perry worker serve" || true'], { user: 'workspace' });
|
|
285
306
|
}
|
|
286
307
|
}
|
|
287
308
|
catch {
|
|
288
309
|
// Worker not running, start it
|
|
289
310
|
}
|
|
290
|
-
await docker.execInContainer(containerName, [
|
|
311
|
+
await docker.execInContainer(containerName, [
|
|
312
|
+
'sh',
|
|
313
|
+
'-c',
|
|
314
|
+
"nohup sh -c 'if [ -x /usr/local/bin/perry ]; then exec /usr/local/bin/perry worker serve; else exec perry worker serve; fi' > /tmp/perry-worker.log 2>&1 &",
|
|
315
|
+
], { user: 'workspace' });
|
|
291
316
|
const deadline = Date.now() + 10000;
|
|
292
317
|
while (Date.now() < deadline) {
|
|
293
318
|
await new Promise((r) => setTimeout(r, 200));
|
|
@@ -295,7 +320,14 @@ export class WorkspaceManager {
|
|
|
295
320
|
const response = await fetch(`http://${ip}:${WORKER_PORT}/health`, {
|
|
296
321
|
signal: AbortSignal.timeout(500),
|
|
297
322
|
});
|
|
298
|
-
if (response.ok) {
|
|
323
|
+
if (!response.ok) {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (!hasSyncedPerry) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const health = (await response.json().catch(() => null));
|
|
330
|
+
if (health?.version === desiredVersion) {
|
|
299
331
|
return;
|
|
300
332
|
}
|
|
301
333
|
}
|
|
@@ -303,6 +335,9 @@ export class WorkspaceManager {
|
|
|
303
335
|
// Not ready yet
|
|
304
336
|
}
|
|
305
337
|
}
|
|
338
|
+
if (options.strictWorker && hasSyncedPerry) {
|
|
339
|
+
throw new Error(`[sync] Worker server failed to start in ${containerName}. Check /tmp/perry-worker.log`);
|
|
340
|
+
}
|
|
306
341
|
console.warn(`[sync] Worker server failed to start in ${containerName}`);
|
|
307
342
|
}
|
|
308
343
|
async runUserScripts(containerName) {
|
|
@@ -473,7 +508,7 @@ export class WorkspaceManager {
|
|
|
473
508
|
await this.state.setWorkspace(workspace);
|
|
474
509
|
await docker.startContainer(containerName);
|
|
475
510
|
await docker.waitForContainerReady(containerName);
|
|
476
|
-
await this.setupWorkspaceCredentials(containerName, name);
|
|
511
|
+
await this.setupWorkspaceCredentials(containerName, name, { strictWorker: false });
|
|
477
512
|
workspace.status = 'running';
|
|
478
513
|
await this.state.setWorkspace(workspace);
|
|
479
514
|
await this.runUserScripts(containerName);
|
|
@@ -551,7 +586,7 @@ export class WorkspaceManager {
|
|
|
551
586
|
}
|
|
552
587
|
await docker.startContainer(containerName);
|
|
553
588
|
await docker.waitForContainerReady(containerName);
|
|
554
|
-
await this.setupWorkspaceCredentials(containerName, name);
|
|
589
|
+
await this.setupWorkspaceCredentials(containerName, name, { strictWorker: false });
|
|
555
590
|
workspace.status = 'running';
|
|
556
591
|
workspace.lastUsed = new Date().toISOString();
|
|
557
592
|
await this.state.setWorkspace(workspace);
|
|
@@ -630,7 +665,7 @@ export class WorkspaceManager {
|
|
|
630
665
|
if (!running) {
|
|
631
666
|
throw new Error(`Workspace '${name}' is not running`);
|
|
632
667
|
}
|
|
633
|
-
await this.setupWorkspaceCredentials(containerName, name);
|
|
668
|
+
await this.setupWorkspaceCredentials(containerName, name, { strictWorker: true });
|
|
634
669
|
}
|
|
635
670
|
async setPortForwards(name, forwards) {
|
|
636
671
|
const workspace = await this.state.getWorkspace(name);
|
|
@@ -722,7 +757,7 @@ export class WorkspaceManager {
|
|
|
722
757
|
await this.state.setWorkspace(workspace);
|
|
723
758
|
await docker.startContainer(cloneContainerName);
|
|
724
759
|
await docker.waitForContainerReady(cloneContainerName);
|
|
725
|
-
await this.setupWorkspaceCredentials(cloneContainerName, cloneName);
|
|
760
|
+
await this.setupWorkspaceCredentials(cloneContainerName, cloneName, { strictWorker: false });
|
|
726
761
|
workspace.status = 'running';
|
|
727
762
|
await this.state.setWorkspace(workspace);
|
|
728
763
|
await this.runUserScripts(cloneContainerName);
|