git-workspace-service 0.2.0 → 0.3.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/dist/index.cjs +178 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -3
- package/dist/index.d.ts +76 -3
- package/dist/index.js +178 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -324,24 +324,28 @@ var WorkspaceService = class {
|
|
|
324
324
|
credential = parent.credential;
|
|
325
325
|
} else {
|
|
326
326
|
await fs3__namespace.mkdir(workspacePath, { recursive: true });
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
327
|
+
if (config.userCredentials) {
|
|
328
|
+
credential = await this.credentialService.getCredentials({
|
|
329
|
+
repo: config.repo,
|
|
330
|
+
access: "write",
|
|
331
|
+
context: {
|
|
332
|
+
executionId: config.execution.id,
|
|
333
|
+
taskId: config.task.id,
|
|
334
|
+
userId: config.user?.id,
|
|
335
|
+
reason: `Workspace for ${config.task.role} in ${config.execution.patternName}`
|
|
336
|
+
},
|
|
337
|
+
userProvided: config.userCredentials
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
if (credential) {
|
|
341
|
+
await this.emitEvent({
|
|
342
|
+
type: "credential:granted",
|
|
343
|
+
workspaceId,
|
|
344
|
+
credentialId: credential.id,
|
|
331
345
|
executionId: config.execution.id,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
},
|
|
336
|
-
userProvided: config.userCredentials
|
|
337
|
-
});
|
|
338
|
-
await this.emitEvent({
|
|
339
|
-
type: "credential:granted",
|
|
340
|
-
workspaceId,
|
|
341
|
-
credentialId: credential.id,
|
|
342
|
-
executionId: config.execution.id,
|
|
343
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
344
|
-
});
|
|
346
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
347
|
+
});
|
|
348
|
+
}
|
|
345
349
|
}
|
|
346
350
|
const branchInfo = createBranchInfo(
|
|
347
351
|
{
|
|
@@ -358,15 +362,55 @@ var WorkspaceService = class {
|
|
|
358
362
|
repo: config.repo,
|
|
359
363
|
branch: branchInfo,
|
|
360
364
|
credential,
|
|
365
|
+
// Will be set before any write operations
|
|
361
366
|
provisionedAt: /* @__PURE__ */ new Date(),
|
|
362
367
|
status: "provisioning",
|
|
363
368
|
strategy,
|
|
364
|
-
parentWorkspaceId: config.parentWorkspace
|
|
369
|
+
parentWorkspaceId: config.parentWorkspace,
|
|
370
|
+
onComplete: config.onComplete,
|
|
371
|
+
progress: {
|
|
372
|
+
phase: "initializing",
|
|
373
|
+
message: "Initializing workspace",
|
|
374
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
375
|
+
}
|
|
365
376
|
};
|
|
366
377
|
this.workspaces.set(workspaceId, workspace);
|
|
367
378
|
try {
|
|
368
379
|
if (strategy === "clone") {
|
|
369
|
-
|
|
380
|
+
this.updateProgress(workspace, "cloning", "Cloning repository");
|
|
381
|
+
if (!credential) {
|
|
382
|
+
const cloneResult = await this.tryUnauthenticatedClone(workspace);
|
|
383
|
+
if (!cloneResult.success) {
|
|
384
|
+
this.log(
|
|
385
|
+
"info",
|
|
386
|
+
{ workspaceId, error: cloneResult.error },
|
|
387
|
+
"Unauthenticated clone failed, requesting credentials"
|
|
388
|
+
);
|
|
389
|
+
credential = await this.credentialService.getCredentials({
|
|
390
|
+
repo: config.repo,
|
|
391
|
+
access: "write",
|
|
392
|
+
context: {
|
|
393
|
+
executionId: config.execution.id,
|
|
394
|
+
taskId: config.task.id,
|
|
395
|
+
userId: config.user?.id,
|
|
396
|
+
reason: `Workspace for ${config.task.role} in ${config.execution.patternName}`
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
workspace.credential = credential;
|
|
400
|
+
this.workspaces.set(workspaceId, workspace);
|
|
401
|
+
await this.emitEvent({
|
|
402
|
+
type: "credential:granted",
|
|
403
|
+
workspaceId,
|
|
404
|
+
credentialId: credential.id,
|
|
405
|
+
executionId: config.execution.id,
|
|
406
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
407
|
+
});
|
|
408
|
+
await this.cloneRepo(workspace, credential.token);
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
await this.cloneRepo(workspace, credential.token);
|
|
412
|
+
}
|
|
413
|
+
this.updateProgress(workspace, "creating_branch", "Creating branch");
|
|
370
414
|
await this.createBranch(workspace);
|
|
371
415
|
} else {
|
|
372
416
|
const parent = this.workspaces.get(config.parentWorkspace);
|
|
@@ -384,9 +428,12 @@ var WorkspaceService = class {
|
|
|
384
428
|
data: { parentWorkspaceId: parent.id }
|
|
385
429
|
});
|
|
386
430
|
}
|
|
431
|
+
this.updateProgress(workspace, "configuring", "Configuring git");
|
|
387
432
|
await this.configureGit(workspace);
|
|
388
433
|
workspace.status = "ready";
|
|
434
|
+
this.updateProgress(workspace, "ready", "Workspace ready");
|
|
389
435
|
this.workspaces.set(workspaceId, workspace);
|
|
436
|
+
await this.executeCompletionHook(workspace, "success");
|
|
390
437
|
this.log(
|
|
391
438
|
"info",
|
|
392
439
|
{
|
|
@@ -406,8 +453,9 @@ var WorkspaceService = class {
|
|
|
406
453
|
return workspace;
|
|
407
454
|
} catch (error) {
|
|
408
455
|
workspace.status = "error";
|
|
409
|
-
this.workspaces.set(workspaceId, workspace);
|
|
410
456
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
457
|
+
this.updateProgress(workspace, "error", errorMessage);
|
|
458
|
+
this.workspaces.set(workspaceId, workspace);
|
|
411
459
|
this.log("error", { workspaceId, error: errorMessage }, "Failed to provision workspace");
|
|
412
460
|
await this.emitEvent({
|
|
413
461
|
type: "workspace:error",
|
|
@@ -416,6 +464,7 @@ var WorkspaceService = class {
|
|
|
416
464
|
timestamp: /* @__PURE__ */ new Date(),
|
|
417
465
|
error: errorMessage
|
|
418
466
|
});
|
|
467
|
+
await this.executeCompletionHook(workspace, "error");
|
|
419
468
|
throw error;
|
|
420
469
|
}
|
|
421
470
|
}
|
|
@@ -581,7 +630,7 @@ var WorkspaceService = class {
|
|
|
581
630
|
});
|
|
582
631
|
}
|
|
583
632
|
}
|
|
584
|
-
if (workspace.strategy === "clone") {
|
|
633
|
+
if (workspace.strategy === "clone" && workspace.credential) {
|
|
585
634
|
await this.credentialService.revokeCredential(workspace.credential.id);
|
|
586
635
|
await this.emitEvent({
|
|
587
636
|
type: "credential:revoked",
|
|
@@ -617,6 +666,36 @@ var WorkspaceService = class {
|
|
|
617
666
|
// ─────────────────────────────────────────────────────────────
|
|
618
667
|
// Private Methods
|
|
619
668
|
// ─────────────────────────────────────────────────────────────
|
|
669
|
+
/**
|
|
670
|
+
* Try to clone a public repository without authentication
|
|
671
|
+
*/
|
|
672
|
+
async tryUnauthenticatedClone(workspace) {
|
|
673
|
+
let cloneUrl = workspace.repo;
|
|
674
|
+
if (cloneUrl.startsWith("git@github.com:")) {
|
|
675
|
+
cloneUrl = cloneUrl.replace("git@github.com:", "https://github.com/");
|
|
676
|
+
}
|
|
677
|
+
if (!cloneUrl.endsWith(".git")) {
|
|
678
|
+
cloneUrl = `${cloneUrl}.git`;
|
|
679
|
+
}
|
|
680
|
+
if (!cloneUrl.startsWith("https://")) {
|
|
681
|
+
cloneUrl = `https://${cloneUrl}`;
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
await this.execInDir(
|
|
685
|
+
workspace.path,
|
|
686
|
+
`git clone --depth 1 --branch ${workspace.branch.baseBranch} ${cloneUrl} .`
|
|
687
|
+
);
|
|
688
|
+
this.log("info", { workspaceId: workspace.id }, "Public repository cloned without authentication");
|
|
689
|
+
return { success: true };
|
|
690
|
+
} catch (error) {
|
|
691
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
692
|
+
const isAuthError = errorMessage.includes("401") || errorMessage.includes("403") || errorMessage.includes("Authentication failed") || errorMessage.includes("could not read Username") || errorMessage.includes("terminal prompts disabled");
|
|
693
|
+
if (isAuthError) {
|
|
694
|
+
return { success: false, error: "Authentication required" };
|
|
695
|
+
}
|
|
696
|
+
throw error;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
620
699
|
async cloneRepo(workspace, token) {
|
|
621
700
|
const cloneUrl = token ? this.buildAuthenticatedUrl(workspace.repo, token) : workspace.repo;
|
|
622
701
|
await this.execInDir(
|
|
@@ -640,11 +719,11 @@ var WorkspaceService = class {
|
|
|
640
719
|
async configureGit(workspace) {
|
|
641
720
|
await this.execInDir(workspace.path, 'git config user.name "Workspace Agent"');
|
|
642
721
|
await this.execInDir(workspace.path, 'git config user.email "agent@workspace.local"');
|
|
643
|
-
if (!workspace.credential.token) {
|
|
722
|
+
if (!workspace.credential || !workspace.credential.token) {
|
|
644
723
|
this.log(
|
|
645
724
|
"debug",
|
|
646
725
|
{ workspaceId: workspace.id },
|
|
647
|
-
"Using SSH authentication, skipping credential helper"
|
|
726
|
+
workspace.credential ? "Using SSH authentication, skipping credential helper" : "No credentials (public repo), skipping credential helper"
|
|
648
727
|
);
|
|
649
728
|
return;
|
|
650
729
|
}
|
|
@@ -670,9 +749,19 @@ var WorkspaceService = class {
|
|
|
670
749
|
);
|
|
671
750
|
}
|
|
672
751
|
async pushBranch(workspace) {
|
|
752
|
+
if (!workspace.credential) {
|
|
753
|
+
throw new Error(
|
|
754
|
+
"Push requires authentication. This workspace was cloned from a public repository without credentials."
|
|
755
|
+
);
|
|
756
|
+
}
|
|
673
757
|
await this.execInDir(workspace.path, `git push -u origin ${workspace.branch.name}`);
|
|
674
758
|
}
|
|
675
759
|
async createPullRequest(workspace, config) {
|
|
760
|
+
if (!workspace.credential) {
|
|
761
|
+
throw new Error(
|
|
762
|
+
"Pull request creation requires authentication. This workspace was cloned from a public repository without credentials."
|
|
763
|
+
);
|
|
764
|
+
}
|
|
676
765
|
const repoInfo = this.parseRepo(workspace.repo);
|
|
677
766
|
if (!repoInfo) {
|
|
678
767
|
throw new Error(`Invalid repository format: ${workspace.repo}`);
|
|
@@ -752,6 +841,72 @@ var WorkspaceService = class {
|
|
|
752
841
|
}
|
|
753
842
|
}
|
|
754
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Update workspace progress
|
|
846
|
+
*/
|
|
847
|
+
updateProgress(workspace, phase, message) {
|
|
848
|
+
workspace.progress = {
|
|
849
|
+
phase,
|
|
850
|
+
message,
|
|
851
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
852
|
+
};
|
|
853
|
+
this.workspaces.set(workspace.id, workspace);
|
|
854
|
+
this.log(
|
|
855
|
+
"debug",
|
|
856
|
+
{ workspaceId: workspace.id, phase, message },
|
|
857
|
+
"Progress updated"
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Execute completion hook if configured
|
|
862
|
+
*/
|
|
863
|
+
async executeCompletionHook(workspace, status) {
|
|
864
|
+
const hook = workspace.onComplete;
|
|
865
|
+
if (!hook) return;
|
|
866
|
+
if (status === "error" && hook.runOnError === false) {
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const env = {
|
|
870
|
+
...process.env,
|
|
871
|
+
WORKSPACE_ID: workspace.id,
|
|
872
|
+
REPO: workspace.repo,
|
|
873
|
+
BRANCH: workspace.branch.name,
|
|
874
|
+
STATUS: status,
|
|
875
|
+
WORKSPACE_PATH: workspace.path
|
|
876
|
+
};
|
|
877
|
+
if (hook.command) {
|
|
878
|
+
try {
|
|
879
|
+
this.log("info", { workspaceId: workspace.id, command: hook.command }, "Executing completion hook command");
|
|
880
|
+
await execAsync(hook.command, { env });
|
|
881
|
+
} catch (error) {
|
|
882
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
883
|
+
this.log("warn", { workspaceId: workspace.id, error: errorMessage }, "Completion hook command failed");
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (hook.webhook) {
|
|
887
|
+
try {
|
|
888
|
+
this.log("info", { workspaceId: workspace.id, webhook: hook.webhook }, "Calling completion webhook");
|
|
889
|
+
const payload = {
|
|
890
|
+
workspaceId: workspace.id,
|
|
891
|
+
repo: workspace.repo,
|
|
892
|
+
branch: workspace.branch.name,
|
|
893
|
+
status,
|
|
894
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
895
|
+
};
|
|
896
|
+
await fetch(hook.webhook, {
|
|
897
|
+
method: "POST",
|
|
898
|
+
headers: {
|
|
899
|
+
"Content-Type": "application/json",
|
|
900
|
+
...hook.webhookHeaders
|
|
901
|
+
},
|
|
902
|
+
body: JSON.stringify(payload)
|
|
903
|
+
});
|
|
904
|
+
} catch (error) {
|
|
905
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
906
|
+
this.log("warn", { workspaceId: workspace.id, error: errorMessage }, "Completion webhook failed");
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
755
910
|
};
|
|
756
911
|
|
|
757
912
|
// src/oauth/device-flow.ts
|