@mastra/factory 0.0.0 → 0.1.0-alpha.3
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/CHANGELOG.md +23 -0
- package/LICENSE.md +30 -0
- package/dist/auth.d.ts +181 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +370 -0
- package/dist/auth.js.map +1 -0
- package/dist/capabilities/connection.d.ts +9 -0
- package/dist/capabilities/connection.d.ts.map +1 -0
- package/dist/capabilities/connection.js +1 -0
- package/dist/capabilities/connection.js.map +1 -0
- package/dist/capabilities/intake.d.ts +93 -0
- package/dist/capabilities/intake.d.ts.map +1 -0
- package/dist/capabilities/intake.js +1 -0
- package/dist/capabilities/intake.js.map +1 -0
- package/dist/capabilities/version-control.d.ts +239 -0
- package/dist/capabilities/version-control.d.ts.map +1 -0
- package/dist/capabilities/version-control.js +1 -0
- package/dist/capabilities/version-control.js.map +1 -0
- package/dist/factory.d.ts +145 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +38245 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38245 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/base.d.ts +197 -0
- package/dist/integrations/base.d.ts.map +1 -0
- package/dist/integrations/base.js +1 -0
- package/dist/integrations/base.js.map +1 -0
- package/dist/integrations/github/config.d.ts +60 -0
- package/dist/integrations/github/config.d.ts.map +1 -0
- package/dist/integrations/github/config.js +28 -0
- package/dist/integrations/github/config.js.map +1 -0
- package/dist/integrations/github/factory-session.d.ts +12 -0
- package/dist/integrations/github/factory-session.d.ts.map +1 -0
- package/dist/integrations/github/factory-session.js +41 -0
- package/dist/integrations/github/factory-session.js.map +1 -0
- package/dist/integrations/github/integration.d.ts +191 -0
- package/dist/integrations/github/integration.d.ts.map +1 -0
- package/dist/integrations/github/integration.js +3001 -0
- package/dist/integrations/github/integration.js.map +1 -0
- package/dist/integrations/github/issue-triage.d.ts +27 -0
- package/dist/integrations/github/issue-triage.d.ts.map +1 -0
- package/dist/integrations/github/issue-triage.js +82 -0
- package/dist/integrations/github/issue-triage.js.map +1 -0
- package/dist/integrations/github/project-lock.d.ts +78 -0
- package/dist/integrations/github/project-lock.d.ts.map +1 -0
- package/dist/integrations/github/project-lock.js +70 -0
- package/dist/integrations/github/project-lock.js.map +1 -0
- package/dist/integrations/github/provenance.d.ts +27 -0
- package/dist/integrations/github/provenance.d.ts.map +1 -0
- package/dist/integrations/github/provenance.js +104 -0
- package/dist/integrations/github/provenance.js.map +1 -0
- package/dist/integrations/github/routes.d.ts +72 -0
- package/dist/integrations/github/routes.d.ts.map +1 -0
- package/dist/integrations/github/routes.js +1834 -0
- package/dist/integrations/github/routes.js.map +1 -0
- package/dist/integrations/github/sandbox.d.ts +268 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -0
- package/dist/integrations/github/sandbox.js +381 -0
- package/dist/integrations/github/sandbox.js.map +1 -0
- package/dist/integrations/github/session-subscriptions.d.ts +23 -0
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/session-subscriptions.js +190 -0
- package/dist/integrations/github/session-subscriptions.js.map +1 -0
- package/dist/integrations/github/subscriptions.d.ts +54 -0
- package/dist/integrations/github/subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/subscriptions.js +70 -0
- package/dist/integrations/github/subscriptions.js.map +1 -0
- package/dist/integrations/github/webhook.d.ts +87 -0
- package/dist/integrations/github/webhook.d.ts.map +1 -0
- package/dist/integrations/github/webhook.js +381 -0
- package/dist/integrations/github/webhook.js.map +1 -0
- package/dist/integrations/linear/agent-tools.d.ts +34 -0
- package/dist/integrations/linear/agent-tools.d.ts.map +1 -0
- package/dist/integrations/linear/agent-tools.js +108 -0
- package/dist/integrations/linear/agent-tools.js.map +1 -0
- package/dist/integrations/linear/integration.d.ts +233 -0
- package/dist/integrations/linear/integration.d.ts.map +1 -0
- package/dist/integrations/linear/integration.js +962 -0
- package/dist/integrations/linear/integration.js.map +1 -0
- package/dist/integrations/linear/routes.d.ts +61 -0
- package/dist/integrations/linear/routes.d.ts.map +1 -0
- package/dist/integrations/linear/routes.js +242 -0
- package/dist/integrations/linear/routes.js.map +1 -0
- package/dist/integrations/linear/storage.d.ts +65 -0
- package/dist/integrations/linear/storage.d.ts.map +1 -0
- package/dist/integrations/linear/storage.js +1 -0
- package/dist/integrations/linear/storage.js.map +1 -0
- package/dist/integrations/platform/api-client.d.ts +24 -0
- package/dist/integrations/platform/api-client.d.ts.map +1 -0
- package/dist/integrations/platform/api-client.js +153 -0
- package/dist/integrations/platform/api-client.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.d.ts +44 -0
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
- package/dist/integrations/platform/github/event-worker.js +585 -0
- package/dist/integrations/platform/github/event-worker.js.map +1 -0
- package/dist/integrations/platform/github/integration.d.ts +36 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -0
- package/dist/integrations/platform/github/integration.js +3253 -0
- package/dist/integrations/platform/github/integration.js.map +1 -0
- package/dist/integrations/platform/linear/integration.d.ts +46 -0
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -0
- package/dist/integrations/platform/linear/integration.js +891 -0
- package/dist/integrations/platform/linear/integration.js.map +1 -0
- package/dist/integrations/workos/integration.d.ts +39 -0
- package/dist/integrations/workos/integration.d.ts.map +1 -0
- package/dist/integrations/workos/integration.js +96 -0
- package/dist/integrations/workos/integration.js.map +1 -0
- package/dist/routes/config.d.ts +220 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +770 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/fs.d.ts +105 -0
- package/dist/routes/fs.d.ts.map +1 -0
- package/dist/routes/fs.js +294 -0
- package/dist/routes/fs.js.map +1 -0
- package/dist/routes/intake.d.ts +23 -0
- package/dist/routes/intake.d.ts.map +1 -0
- package/dist/routes/intake.js +179 -0
- package/dist/routes/intake.js.map +1 -0
- package/dist/routes/oauth.d.ts +43 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +25058 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/projects.d.ts +19 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +395 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/provider-credentials.d.ts +81 -0
- package/dist/routes/provider-credentials.d.ts.map +1 -0
- package/dist/routes/provider-credentials.js +68 -0
- package/dist/routes/provider-credentials.js.map +1 -0
- package/dist/routes/route.d.ts +48 -0
- package/dist/routes/route.d.ts.map +1 -0
- package/dist/routes/route.js +11 -0
- package/dist/routes/route.js.map +1 -0
- package/dist/routes/skills.d.ts +30 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +181 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/surface.d.ts +83 -0
- package/dist/routes/surface.d.ts.map +1 -0
- package/dist/routes/surface.js +28519 -0
- package/dist/routes/surface.js.map +1 -0
- package/dist/routes/tenant-credentials.d.ts +74 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -0
- package/dist/routes/tenant-credentials.js +181 -0
- package/dist/routes/tenant-credentials.js.map +1 -0
- package/dist/routes/work-items.d.ts +40 -0
- package/dist/routes/work-items.d.ts.map +1 -0
- package/dist/routes/work-items.js +902 -0
- package/dist/routes/work-items.js.map +1 -0
- package/dist/rules/binding-context.d.ts +5 -0
- package/dist/rules/binding-context.d.ts.map +1 -0
- package/dist/rules/binding-context.js +40 -0
- package/dist/rules/binding-context.js.map +1 -0
- package/dist/rules/defaults.d.ts +9 -0
- package/dist/rules/defaults.d.ts.map +1 -0
- package/dist/rules/defaults.js +334 -0
- package/dist/rules/defaults.js.map +1 -0
- package/dist/rules/dispatcher.d.ts +40 -0
- package/dist/rules/dispatcher.d.ts.map +1 -0
- package/dist/rules/dispatcher.js +695 -0
- package/dist/rules/dispatcher.js.map +1 -0
- package/dist/rules/github-service.d.ts +29 -0
- package/dist/rules/github-service.d.ts.map +1 -0
- package/dist/rules/github-service.js +474 -0
- package/dist/rules/github-service.js.map +1 -0
- package/dist/rules/index.d.ts +7 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +583 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/linear-service.d.ts +27 -0
- package/dist/rules/linear-service.d.ts.map +1 -0
- package/dist/rules/linear-service.js +343 -0
- package/dist/rules/linear-service.js.map +1 -0
- package/dist/rules/processor.d.ts +54 -0
- package/dist/rules/processor.d.ts.map +1 -0
- package/dist/rules/processor.js +629 -0
- package/dist/rules/processor.js.map +1 -0
- package/dist/rules/resolve.d.ts +17 -0
- package/dist/rules/resolve.d.ts.map +1 -0
- package/dist/rules/resolve.js +27 -0
- package/dist/rules/resolve.js.map +1 -0
- package/dist/rules/start-coordinator.d.ts +58 -0
- package/dist/rules/start-coordinator.d.ts.map +1 -0
- package/dist/rules/start-coordinator.js +136 -0
- package/dist/rules/start-coordinator.js.map +1 -0
- package/dist/rules/tools.d.ts +10 -0
- package/dist/rules/tools.d.ts.map +1 -0
- package/dist/rules/tools.js +93 -0
- package/dist/rules/tools.js.map +1 -0
- package/dist/rules/transition-service.d.ts +32 -0
- package/dist/rules/transition-service.d.ts.map +1 -0
- package/dist/rules/transition-service.js +474 -0
- package/dist/rules/transition-service.js.map +1 -0
- package/dist/rules/types.d.ts +225 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +33 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/validation.d.ts +11 -0
- package/dist/rules/validation.d.ts.map +1 -0
- package/dist/rules/validation.js +310 -0
- package/dist/rules/validation.js.map +1 -0
- package/dist/sandbox/fleet.d.ts +208 -0
- package/dist/sandbox/fleet.d.ts.map +1 -0
- package/dist/sandbox/fleet.js +259 -0
- package/dist/sandbox/fleet.js.map +1 -0
- package/dist/sandbox/reattach.d.ts +3 -0
- package/dist/sandbox/reattach.d.ts.map +1 -0
- package/dist/sandbox/reattach.js +9 -0
- package/dist/sandbox/reattach.js.map +1 -0
- package/dist/server-error.d.ts +12 -0
- package/dist/server-error.d.ts.map +1 -0
- package/dist/server-error.js +20 -0
- package/dist/server-error.js.map +1 -0
- package/dist/skills/service.d.ts +48 -0
- package/dist/skills/service.d.ts.map +1 -0
- package/dist/skills/service.js +45 -0
- package/dist/skills/service.js.map +1 -0
- package/dist/spa-static.d.ts +34 -0
- package/dist/spa-static.d.ts.map +1 -0
- package/dist/spa-static.js +73 -0
- package/dist/spa-static.js.map +1 -0
- package/dist/state-signing.d.ts +48 -0
- package/dist/state-signing.d.ts.map +1 -0
- package/dist/state-signing.js +48 -0
- package/dist/state-signing.js.map +1 -0
- package/dist/storage/domains/audit/agent-audit.d.ts +27 -0
- package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -0
- package/dist/storage/domains/audit/agent-audit.js +65 -0
- package/dist/storage/domains/audit/agent-audit.js.map +1 -0
- package/dist/storage/domains/audit/base.d.ts +140 -0
- package/dist/storage/domains/audit/base.d.ts.map +1 -0
- package/dist/storage/domains/audit/base.js +127 -0
- package/dist/storage/domains/audit/base.js.map +1 -0
- package/dist/storage/domains/audit/domain.d.ts +69 -0
- package/dist/storage/domains/audit/domain.d.ts.map +1 -0
- package/dist/storage/domains/audit/domain.js +165 -0
- package/dist/storage/domains/audit/domain.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +134 -0
- package/dist/storage/domains/credentials/base.d.ts.map +1 -0
- package/dist/storage/domains/credentials/base.js +257 -0
- package/dist/storage/domains/credentials/base.js.map +1 -0
- package/dist/storage/domains/intake/base.d.ts +38 -0
- package/dist/storage/domains/intake/base.d.ts.map +1 -0
- package/dist/storage/domains/intake/base.js +62 -0
- package/dist/storage/domains/intake/base.js.map +1 -0
- package/dist/storage/domains/integrations/base.d.ts +126 -0
- package/dist/storage/domains/integrations/base.d.ts.map +1 -0
- package/dist/storage/domains/integrations/base.js +252 -0
- package/dist/storage/domains/integrations/base.js.map +1 -0
- package/dist/storage/domains/model-packs/base.d.ts +49 -0
- package/dist/storage/domains/model-packs/base.d.ts.map +1 -0
- package/dist/storage/domains/model-packs/base.js +95 -0
- package/dist/storage/domains/model-packs/base.js.map +1 -0
- package/dist/storage/domains/projects/base.d.ts +55 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -0
- package/dist/storage/domains/projects/base.js +99 -0
- package/dist/storage/domains/projects/base.js.map +1 -0
- package/dist/storage/domains/queue-health/base.d.ts +52 -0
- package/dist/storage/domains/queue-health/base.d.ts.map +1 -0
- package/dist/storage/domains/queue-health/base.js +87 -0
- package/dist/storage/domains/queue-health/base.js.map +1 -0
- package/dist/storage/domains/source-control/base.d.ts +288 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -0
- package/dist/storage/domains/source-control/base.js +722 -0
- package/dist/storage/domains/source-control/base.js.map +1 -0
- package/dist/storage/domains/source-control/inmemory.d.ts +158 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -0
- package/dist/storage/domains/source-control/inmemory.js +348 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -0
- package/dist/storage/domains/work-items/base.d.ts +412 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -0
- package/dist/storage/domains/work-items/base.js +1174 -0
- package/dist/storage/domains/work-items/base.js.map +1 -0
- package/dist/storage/domains/work-items/metrics.d.ts +101 -0
- package/dist/storage/domains/work-items/metrics.d.ts.map +1 -0
- package/dist/storage/domains/work-items/metrics.js +203 -0
- package/dist/storage/domains/work-items/metrics.js.map +1 -0
- package/dist/workspace.d.ts +20 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +364 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +19 -19
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo materialization for GitHub-backed repositories.
|
|
3
|
+
*
|
|
4
|
+
* A GitHub repo is never cloned onto the server host. Instead each project gets
|
|
5
|
+
* its own isolated sandbox (provisioned by the fleet in `../sandbox/fleet`) and
|
|
6
|
+
* the repo is cloned *inside* that sandbox. The agent's file tools and command
|
|
7
|
+
* tools then operate entirely against the remote checkout.
|
|
8
|
+
*
|
|
9
|
+
* - `ensureProjectSandbox(row)` / `teardownProjectSandbox(row)` bind the fleet's
|
|
10
|
+
* provision/reattach/teardown lifecycle to the per-(project,user) sandbox row.
|
|
11
|
+
* - `materializeRepo(row, token)` runs `git clone` (first open) or `git pull`
|
|
12
|
+
* (re-open) inside the sandbox, using a short-lived installation token that is
|
|
13
|
+
* scrubbed from the git remote afterwards so it never persists in the VM.
|
|
14
|
+
*
|
|
15
|
+
* This module owns everything git/GitHub: clone/pull, commit/push, worktrees,
|
|
16
|
+
* and `gh pr create`. Sandbox provisioning, budgets, and workdir layout live in
|
|
17
|
+
* the fleet module.
|
|
18
|
+
*/
|
|
19
|
+
import type { MaterializationSandbox, ProgressFn, SandboxFleet } from '../../sandbox/fleet.js';
|
|
20
|
+
import type { ProjectRepositorySandbox, SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';
|
|
21
|
+
type SourceControlSandboxStorage = SourceControlStorageHandle['sandboxes'];
|
|
22
|
+
type MaterializationStore = Pick<SourceControlSandboxStorage, 'markMaterialized'>;
|
|
23
|
+
interface RepoMaterializationBinding {
|
|
24
|
+
id: string;
|
|
25
|
+
sandboxWorkdir: string;
|
|
26
|
+
materializedAt: Date | null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Provision a new sandbox (persisting its provider id on first open) or
|
|
30
|
+
* reattach to the stored one. Returns a started, live sandbox.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ensureProjectSandbox(options: {
|
|
33
|
+
fleet: SandboxFleet;
|
|
34
|
+
row: ProjectRepositorySandbox;
|
|
35
|
+
storage: SourceControlSandboxStorage;
|
|
36
|
+
onProgress?: ProgressFn;
|
|
37
|
+
}): Promise<MaterializationSandbox>;
|
|
38
|
+
/**
|
|
39
|
+
* Tear down a user's sandbox for a project: stop the live VM (best-effort) and
|
|
40
|
+
* clear the persisted `sandboxId`/`materializedAt` on the per-(project,user)
|
|
41
|
+
* binding row so the next open re-provisions cleanly.
|
|
42
|
+
*
|
|
43
|
+
* @param row the per-(project,user) sandbox binding to tear down
|
|
44
|
+
* @param sandbox an already-reattached live sandbox to stop, when available
|
|
45
|
+
*/
|
|
46
|
+
export declare function teardownProjectSandbox(options: {
|
|
47
|
+
fleet: SandboxFleet;
|
|
48
|
+
row: ProjectRepositorySandbox;
|
|
49
|
+
storage: SourceControlSandboxStorage;
|
|
50
|
+
sandbox?: MaterializationSandbox;
|
|
51
|
+
}): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Single-quote a string for safe POSIX shell interpolation. Wraps the value in
|
|
54
|
+
* single quotes and escapes any embedded single quote using the canonical
|
|
55
|
+
* close-quote / escaped-quote / reopen-quote sequence (`'\''`). This is the
|
|
56
|
+
* standard POSIX-safe construction and prevents the quoted string from being
|
|
57
|
+
* terminated early.
|
|
58
|
+
*/
|
|
59
|
+
export declare function shellQuote(value: string): string;
|
|
60
|
+
/** Error raised when the sandbox cannot materialize the repo (actionable). */
|
|
61
|
+
export declare class MaterializeError extends Error {
|
|
62
|
+
readonly code: 'git-missing' | 'egress-blocked' | 'clone-failed' | 'pull-failed' | 'push-failed' | 'commit-failed' | 'gh-missing' | 'pr-failed';
|
|
63
|
+
constructor(message: string, code: 'git-missing' | 'egress-blocked' | 'clone-failed' | 'pull-failed' | 'push-failed' | 'commit-failed' | 'gh-missing' | 'pr-failed');
|
|
64
|
+
}
|
|
65
|
+
/** Repo metadata needed to materialize, read from the org-owned project row. */
|
|
66
|
+
export interface RepoMaterializeInfo {
|
|
67
|
+
repoFullName: string;
|
|
68
|
+
defaultBranch: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Materialize the repo inside the user's sandbox. Clones on first open, pulls on
|
|
72
|
+
* re-open. Always scrubs the install token from the remote afterwards and sets
|
|
73
|
+
* `materialized_at` on the per-user sandbox binding row.
|
|
74
|
+
*
|
|
75
|
+
*/
|
|
76
|
+
export declare function materializeRepo(options: {
|
|
77
|
+
/** The per-(project,user) sandbox binding (provisioned via `ensureProjectSandbox`). */
|
|
78
|
+
row: RepoMaterializationBinding;
|
|
79
|
+
/** Repo metadata from the org-owned project row. */
|
|
80
|
+
repoInfo: RepoMaterializeInfo;
|
|
81
|
+
/** The live sandbox to run git inside. */
|
|
82
|
+
sandbox: MaterializationSandbox;
|
|
83
|
+
/** A freshly minted, short-lived installation access token. */
|
|
84
|
+
token: string;
|
|
85
|
+
storage: MaterializationStore;
|
|
86
|
+
onProgress?: ProgressFn;
|
|
87
|
+
}): Promise<void>;
|
|
88
|
+
/** Check out a session's branch inside its isolated repository clone. */
|
|
89
|
+
export declare function checkoutSessionBranch(sandbox: MaterializationSandbox, workdir: string, { branch, baseBranch, token, repoFullName, }: {
|
|
90
|
+
branch: string;
|
|
91
|
+
baseBranch: string;
|
|
92
|
+
token: string;
|
|
93
|
+
repoFullName: string;
|
|
94
|
+
}): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Validate a git ref (branch) name. Server-side defense-in-depth: only allow a
|
|
97
|
+
* conservative character set so a branch can never be built into a shell
|
|
98
|
+
* command in a way that escapes quoting. Mirrors the route-layer check.
|
|
99
|
+
*/
|
|
100
|
+
export declare function isValidGitRef(value: unknown): value is string;
|
|
101
|
+
/** Identity used to author commits inside the sandbox. */
|
|
102
|
+
export interface GitIdentity {
|
|
103
|
+
name?: string | null;
|
|
104
|
+
email?: string | null;
|
|
105
|
+
/** GitHub login, used to derive a stable noreply identity when name/email are absent. */
|
|
106
|
+
login?: string | null;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve a concrete `{ name, email }` for git authorship from a possibly-sparse
|
|
110
|
+
* identity. Falls back to a GitHub-style noreply identity so commits are never
|
|
111
|
+
* authored with an empty or host-derived identity.
|
|
112
|
+
*/
|
|
113
|
+
export declare function resolveGitIdentity(identity: GitIdentity): {
|
|
114
|
+
name: string;
|
|
115
|
+
email: string;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Configure `user.name` / `user.email` for the given repo working tree inside
|
|
119
|
+
* the sandbox so commits are authored correctly. Values are shell-quoted.
|
|
120
|
+
*/
|
|
121
|
+
export declare function configureGitIdentity(sandbox: MaterializationSandbox, workdir: string, identity: GitIdentity): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Temporarily rewrite `origin` to a tokenized URL, run `fn` (e.g. a push), and
|
|
124
|
+
* **always** scrub the remote back to the tokenless URL in a `finally`. The
|
|
125
|
+
* token therefore only ever lives in the remote URL for the duration of the
|
|
126
|
+
* operation and is never left in the VM's git config.
|
|
127
|
+
*
|
|
128
|
+
* On the success path the scrub must succeed (a leaked token is a hard error);
|
|
129
|
+
* if it fails we surface it. On the failure path the scrub is best-effort but
|
|
130
|
+
* still attempted, and the original operation error is rethrown.
|
|
131
|
+
*/
|
|
132
|
+
export declare function withInstallToken<T>(sandbox: MaterializationSandbox, workdir: string, repoFullName: string, token: string, fn: () => Promise<T>): Promise<T>;
|
|
133
|
+
/**
|
|
134
|
+
* Push a branch back to GitHub from inside the sandbox using a short-lived
|
|
135
|
+
* installation token. The branch is ref-validated, the token is injected only
|
|
136
|
+
* into the remote URL via `withInstallToken`, and egress failures are
|
|
137
|
+
* classified into actionable errors.
|
|
138
|
+
*/
|
|
139
|
+
export declare function pushBranch(sandbox: MaterializationSandbox, workdir: string, branch: string, token: string, repoFullName: string): Promise<void>;
|
|
140
|
+
export interface CommitResult {
|
|
141
|
+
/** True when a commit was created; false when there was nothing to commit. */
|
|
142
|
+
committed: boolean;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Stage every change in the working tree and create a commit inside the
|
|
146
|
+
* sandbox. The git identity is configured first so authorship is correct. When
|
|
147
|
+
* there is nothing to commit this is a no-op (`committed: false`) rather than an
|
|
148
|
+
* error, so callers can safely commit-then-push without first diffing.
|
|
149
|
+
*
|
|
150
|
+
* @param sandbox the live sandbox containing the checkout
|
|
151
|
+
* @param workdir the worktree (or repo) path to commit in
|
|
152
|
+
* @param message the commit message (quoted; arbitrary text is safe)
|
|
153
|
+
* @param identity authorship identity for the commit
|
|
154
|
+
*/
|
|
155
|
+
export declare function commitAll(sandbox: MaterializationSandbox, workdir: string, message: string, identity: GitIdentity): Promise<CommitResult>;
|
|
156
|
+
/** Error raised when a worktree cannot be created/reused inside the sandbox. */
|
|
157
|
+
export declare class WorktreeError extends Error {
|
|
158
|
+
readonly code: 'invalid-branch' | 'worktree-failed' | 'setup-failed';
|
|
159
|
+
constructor(message: string, code: 'invalid-branch' | 'worktree-failed' | 'setup-failed');
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Reduce a (already ref-validated) branch name to a filesystem-safe directory
|
|
163
|
+
* segment for the worktree path: slashes/dots/unsafe chars collapsed to `-`.
|
|
164
|
+
* This only affects the *directory name*, never the git branch itself.
|
|
165
|
+
*
|
|
166
|
+
* Sanitization is lossy (e.g. `feat/a` and `feat-a` both reduce to `feat-a`),
|
|
167
|
+
* so an 8-char hash of the original branch is appended whenever the sanitized
|
|
168
|
+
* form differs from the input. That keeps clean names (`main`) readable while
|
|
169
|
+
* guaranteeing distinct branches never share a worktree directory.
|
|
170
|
+
*/
|
|
171
|
+
export declare function safeBranchDir(branch: string): string;
|
|
172
|
+
/**
|
|
173
|
+
* Compute the absolute worktree path for a branch, server-side only. Worktrees
|
|
174
|
+
* live alongside the repo checkout under a sibling `worktrees/` directory so the
|
|
175
|
+
* repo's `.git` is shared. Never derived from client-supplied paths.
|
|
176
|
+
*/
|
|
177
|
+
export declare function computeWorktreePath(repoWorkdir: string, branch: string): string;
|
|
178
|
+
export interface EnsureWorktreeResult {
|
|
179
|
+
worktreePath: string;
|
|
180
|
+
branch: string;
|
|
181
|
+
baseBranch: string;
|
|
182
|
+
/** True when an existing worktree was reused rather than freshly created. */
|
|
183
|
+
reused: boolean;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Create (or reuse) a git worktree + branch inside the sandbox for a unit of
|
|
187
|
+
* work. Idempotent: if a worktree already exists at the computed path it is
|
|
188
|
+
* reused. The branch is created from the freshly fetched `origin/<baseBranch>`
|
|
189
|
+
* — never the sandbox's possibly stale local ref — so new worktrees always
|
|
190
|
+
* start from the latest remote state.
|
|
191
|
+
*
|
|
192
|
+
* @param sandbox live sandbox containing the base checkout
|
|
193
|
+
* @param repoWorkdir the base repo checkout path inside the sandbox
|
|
194
|
+
* @param branch the feature branch (ref-validated server-side)
|
|
195
|
+
* @param baseBranch the branch to fork from (ref-validated; defaults to the repo's default branch)
|
|
196
|
+
* @param token short-lived installation token used only for the base-branch fetch
|
|
197
|
+
* @param repoFullName `owner/repo` used to build the tokenized remote URL
|
|
198
|
+
*/
|
|
199
|
+
export declare function ensureWorktree(sandbox: MaterializationSandbox, repoWorkdir: string, { branch, baseBranch, token, repoFullName, }: {
|
|
200
|
+
branch: string;
|
|
201
|
+
baseBranch: string;
|
|
202
|
+
token: string;
|
|
203
|
+
repoFullName: string;
|
|
204
|
+
}): Promise<EnsureWorktreeResult>;
|
|
205
|
+
/**
|
|
206
|
+
* Run the project's setup command (e.g. `pnpm i && pnpm build`) inside a
|
|
207
|
+
* freshly created worktree. Called before the worktree is handed to any agent
|
|
208
|
+
* run so the checkout is ready to build/test. A non-zero exit is a hard error —
|
|
209
|
+
* starting agent work in a half-set-up tree is worse than failing the request.
|
|
210
|
+
*
|
|
211
|
+
* Security model: the command is intentionally arbitrary shell — that is the
|
|
212
|
+
* feature (install deps, build, seed fixtures). It is only configurable by
|
|
213
|
+
* authenticated org members (the settings route is gated by
|
|
214
|
+
* `resolveOrgTenant` + org-scoped project lookup, with length and
|
|
215
|
+
* control-character validation), and it executes exclusively inside the
|
|
216
|
+
* project's isolated sandbox — the same environment where org members already
|
|
217
|
+
* run arbitrary shell via the agent's command tool. It never runs on the web
|
|
218
|
+
* server host, so it grants no privilege beyond what sandbox access already
|
|
219
|
+
* provides.
|
|
220
|
+
*
|
|
221
|
+
* @param sandbox live sandbox containing the worktree
|
|
222
|
+
* @param worktreePath the server-computed worktree path the command runs in
|
|
223
|
+
* @param command the org-configured setup shell command
|
|
224
|
+
*/
|
|
225
|
+
export declare function runWorktreeSetup(sandbox: MaterializationSandbox, worktreePath: string, command: string): Promise<void>;
|
|
226
|
+
/**
|
|
227
|
+
* Remove a worktree (and its local feature branch) from the sandbox. The
|
|
228
|
+
* checkout is removed with `--force` — the caller owns confirming that any
|
|
229
|
+
* uncommitted work in it can be discarded. Idempotent: a worktree whose
|
|
230
|
+
* directory is already gone only has its metadata pruned.
|
|
231
|
+
*
|
|
232
|
+
* @param sandbox live sandbox containing the base checkout
|
|
233
|
+
* @param repoWorkdir the base repo checkout path inside the sandbox
|
|
234
|
+
* @param branch the worktree's feature branch (ref-validated)
|
|
235
|
+
* @param worktreePath the persisted, server-computed worktree path
|
|
236
|
+
*/
|
|
237
|
+
export declare function removeWorktree(sandbox: MaterializationSandbox, repoWorkdir: string, { branch, worktreePath }: {
|
|
238
|
+
branch: string;
|
|
239
|
+
worktreePath: string;
|
|
240
|
+
}): Promise<void>;
|
|
241
|
+
export interface CreatePullRequestArgs {
|
|
242
|
+
/** Short-lived installation token, injected only into the `gh` process env. */
|
|
243
|
+
token: string;
|
|
244
|
+
/** Base branch the PR merges into. Ref-validated. */
|
|
245
|
+
base: string;
|
|
246
|
+
/** Head branch the PR is opened from. Ref-validated. */
|
|
247
|
+
head: string;
|
|
248
|
+
/** PR title. */
|
|
249
|
+
title: string;
|
|
250
|
+
/** PR body (optional). */
|
|
251
|
+
body?: string;
|
|
252
|
+
}
|
|
253
|
+
export interface CreatePullRequestResult {
|
|
254
|
+
/** The PR URL parsed from `gh pr create` stdout. */
|
|
255
|
+
url: string;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Open a pull request from inside the sandbox via `gh pr create`. The token is
|
|
259
|
+
* passed only through a per-invocation `GH_TOKEN` env scoped to the single `gh`
|
|
260
|
+
* process (never persisted), all arguments are shell-quoted, and the resulting
|
|
261
|
+
* PR URL is parsed from stdout.
|
|
262
|
+
*
|
|
263
|
+
* @param sandbox live sandbox containing the checkout
|
|
264
|
+
* @param workdir the worktree (or repo) path the PR head branch is checked out in
|
|
265
|
+
*/
|
|
266
|
+
export declare function createPullRequest(sandbox: MaterializationSandbox, workdir: string, { token, base, head, title, body }: CreatePullRequestArgs): Promise<CreatePullRequestResult>;
|
|
267
|
+
export {};
|
|
268
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/sandbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
// src/integrations/github/sandbox.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
|
|
4
|
+
// src/sandbox/fleet.ts
|
|
5
|
+
import path from "path";
|
|
6
|
+
function reportProgress(onProgress, event) {
|
|
7
|
+
if (!onProgress) return;
|
|
8
|
+
try {
|
|
9
|
+
onProgress(event);
|
|
10
|
+
} catch {
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/integrations/github/sandbox.ts
|
|
15
|
+
function bindingStore(row, storage) {
|
|
16
|
+
return {
|
|
17
|
+
sandboxId: row.sandboxId,
|
|
18
|
+
setSandboxId: (id) => id === null ? storage.clearBinding({ id: row.id }) : storage.setSandboxId({ id: row.id, sandboxId: id }),
|
|
19
|
+
clear: () => storage.clearBinding({ id: row.id })
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function ensureProjectSandbox(options) {
|
|
23
|
+
const { fleet, row, storage, onProgress } = options;
|
|
24
|
+
return fleet.ensureSandbox(bindingStore(row, storage), onProgress);
|
|
25
|
+
}
|
|
26
|
+
async function teardownProjectSandbox(options) {
|
|
27
|
+
const { fleet, row, storage, sandbox } = options;
|
|
28
|
+
return fleet.teardownSandbox(bindingStore(row, storage), sandbox);
|
|
29
|
+
}
|
|
30
|
+
function shellQuote(value) {
|
|
31
|
+
return `'` + value.split(`'`).join(`'\\''`) + `'`;
|
|
32
|
+
}
|
|
33
|
+
async function sh(sandbox, script) {
|
|
34
|
+
return sandbox.executeCommand("sh", ["-c", script]);
|
|
35
|
+
}
|
|
36
|
+
var MaterializeError = class extends Error {
|
|
37
|
+
constructor(message, code) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.code = code;
|
|
40
|
+
this.name = "MaterializeError";
|
|
41
|
+
}
|
|
42
|
+
code;
|
|
43
|
+
};
|
|
44
|
+
function tokenUrl(repoFullName, token) {
|
|
45
|
+
return `https://x-access-token:${token}@github.com/${repoFullName}.git`;
|
|
46
|
+
}
|
|
47
|
+
function cleanUrl(repoFullName) {
|
|
48
|
+
return `https://github.com/${repoFullName}.git`;
|
|
49
|
+
}
|
|
50
|
+
async function materializeRepo(options) {
|
|
51
|
+
const { row: sandboxRow, repoInfo, sandbox, token, storage, onProgress } = options;
|
|
52
|
+
const workdir = sandboxRow.sandboxWorkdir;
|
|
53
|
+
const repo = repoInfo.repoFullName;
|
|
54
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(repo)) {
|
|
55
|
+
throw new MaterializeError(`Refusing to materialize: invalid repo full name '${repo}'.`, "clone-failed");
|
|
56
|
+
}
|
|
57
|
+
if (!/^[A-Za-z0-9_./-]+$/.test(repoInfo.defaultBranch)) {
|
|
58
|
+
throw new MaterializeError(
|
|
59
|
+
`Refusing to materialize: invalid default branch '${repoInfo.defaultBranch}'.`,
|
|
60
|
+
"clone-failed"
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const gitVersion = await sh(sandbox, "git --version");
|
|
64
|
+
if (gitVersion.exitCode !== 0) {
|
|
65
|
+
throw new MaterializeError(
|
|
66
|
+
"git is not installed in the sandbox. The sandbox template must include git.",
|
|
67
|
+
"git-missing"
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const authUrl = tokenUrl(repo, token);
|
|
71
|
+
const alreadyMaterialized = Boolean(sandboxRow.materializedAt) || await hasExistingCheckout(sandbox, workdir, repo);
|
|
72
|
+
try {
|
|
73
|
+
if (!alreadyMaterialized) {
|
|
74
|
+
reportProgress(onProgress, {
|
|
75
|
+
phase: "cloning",
|
|
76
|
+
message: `Cloning ${repo} (first open can take a minute)\u2026`
|
|
77
|
+
});
|
|
78
|
+
const clone = await sh(
|
|
79
|
+
sandbox,
|
|
80
|
+
`git clone --depth=1 --single-branch --branch ${shellQuote(repoInfo.defaultBranch)} ${shellQuote(authUrl)} ${shellQuote(workdir)}`
|
|
81
|
+
);
|
|
82
|
+
if (clone.exitCode !== 0) {
|
|
83
|
+
throw classifyGitFailure(clone, "clone-failed");
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
reportProgress(onProgress, { phase: "pulling", message: `Updating ${repo} to the latest changes\u2026` });
|
|
87
|
+
const setUrl = await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(authUrl)}`);
|
|
88
|
+
if (setUrl.exitCode !== 0) {
|
|
89
|
+
throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr}`, "pull-failed");
|
|
90
|
+
}
|
|
91
|
+
const pull = await sh(sandbox, `git -C ${shellQuote(workdir)} pull --ff-only`);
|
|
92
|
+
if (pull.exitCode !== 0) {
|
|
93
|
+
throw classifyGitFailure(pull, "pull-failed");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} finally {
|
|
97
|
+
await scrubRemote(sandbox, workdir, repo, alreadyMaterialized);
|
|
98
|
+
}
|
|
99
|
+
reportProgress(onProgress, { phase: "finalizing", message: "Finalizing workspace\u2026" });
|
|
100
|
+
await storage.markMaterialized({ id: sandboxRow.id });
|
|
101
|
+
}
|
|
102
|
+
async function checkoutSessionBranch(sandbox, workdir, {
|
|
103
|
+
branch,
|
|
104
|
+
baseBranch,
|
|
105
|
+
token,
|
|
106
|
+
repoFullName
|
|
107
|
+
}) {
|
|
108
|
+
if (!isValidGitRef(branch) || !isValidGitRef(baseBranch)) {
|
|
109
|
+
throw new MaterializeError("Refusing to create a session from an invalid branch name.", "clone-failed");
|
|
110
|
+
}
|
|
111
|
+
const current = await sh(sandbox, `git -C ${shellQuote(workdir)} branch --show-current`);
|
|
112
|
+
if (current.exitCode === 0 && current.stdout.trim() === branch) return;
|
|
113
|
+
const local = await sh(
|
|
114
|
+
sandbox,
|
|
115
|
+
`git -C ${shellQuote(workdir)} show-ref --verify --quiet refs/heads/${shellQuote(branch)}`
|
|
116
|
+
);
|
|
117
|
+
if (local.exitCode === 0) {
|
|
118
|
+
const checkout = await sh(sandbox, `git -C ${shellQuote(workdir)} checkout ${shellQuote(branch)}`);
|
|
119
|
+
if (checkout.exitCode !== 0) throw classifyGitFailure(checkout, "clone-failed");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const authUrl = tokenUrl(repoFullName, token);
|
|
123
|
+
try {
|
|
124
|
+
const setUrl = await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(authUrl)}`);
|
|
125
|
+
if (setUrl.exitCode !== 0) throw classifyGitFailure(setUrl, "pull-failed");
|
|
126
|
+
const fetch = await sh(
|
|
127
|
+
sandbox,
|
|
128
|
+
`git -C ${shellQuote(workdir)} fetch origin ${shellQuote(baseBranch)} && git -C ${shellQuote(workdir)} checkout -b ${shellQuote(branch)} FETCH_HEAD`
|
|
129
|
+
);
|
|
130
|
+
if (fetch.exitCode !== 0) throw classifyGitFailure(fetch, "clone-failed");
|
|
131
|
+
} finally {
|
|
132
|
+
await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(cleanUrl(repoFullName))}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function hasExistingCheckout(sandbox, workdir, repoFullName) {
|
|
136
|
+
const result = await sh(sandbox, `git -C ${shellQuote(workdir)} remote get-url origin`);
|
|
137
|
+
if (result.exitCode !== 0) return false;
|
|
138
|
+
const url = result.stdout.trim().toLowerCase();
|
|
139
|
+
const suffix = `github.com/${repoFullName.toLowerCase()}`;
|
|
140
|
+
return url.endsWith(`${suffix}.git`) || url.endsWith(suffix);
|
|
141
|
+
}
|
|
142
|
+
async function scrubRemote(sandbox, workdir, repoFullName, expectGitDir) {
|
|
143
|
+
const result = await sh(
|
|
144
|
+
sandbox,
|
|
145
|
+
`git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(cleanUrl(repoFullName))}`
|
|
146
|
+
);
|
|
147
|
+
if (result.exitCode !== 0 && expectGitDir) {
|
|
148
|
+
throw new MaterializeError(
|
|
149
|
+
`Failed to scrub installation token from git remote: ${result.stderr.trim() || result.stdout.trim()}`,
|
|
150
|
+
"pull-failed"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function classifyGitFailure(result, fallback) {
|
|
155
|
+
const stderr = result.stderr || "";
|
|
156
|
+
if (/could not resolve host|failed to connect|network is unreachable|Connection timed out/i.test(stderr)) {
|
|
157
|
+
return new MaterializeError(
|
|
158
|
+
"The sandbox could not reach github.com. The sandbox network must allow outbound egress to github.com.",
|
|
159
|
+
"egress-blocked"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const verb = fallback === "clone-failed" ? "clone" : fallback === "pull-failed" ? "pull" : "push";
|
|
163
|
+
return new MaterializeError(`git ${verb} failed: ${stderr}`, fallback);
|
|
164
|
+
}
|
|
165
|
+
function isValidGitRef(value) {
|
|
166
|
+
return typeof value === "string" && value.length > 0 && value.length <= 255 && // Reject leading-dash refs (e.g. `--mirror`) so the value can never be
|
|
167
|
+
// parsed as a git option when interpolated into a command.
|
|
168
|
+
!value.startsWith("-") && /^[A-Za-z0-9_./-]+$/.test(value);
|
|
169
|
+
}
|
|
170
|
+
function resolveGitIdentity(identity) {
|
|
171
|
+
const login = (identity.login || "").trim();
|
|
172
|
+
const name = (identity.name || "").trim() || login || "Mastra Code";
|
|
173
|
+
const email = (identity.email || "").trim() || (login ? `${login}@users.noreply.github.com` : "mastra-code@users.noreply.github.com");
|
|
174
|
+
return { name, email };
|
|
175
|
+
}
|
|
176
|
+
async function configureGitIdentity(sandbox, workdir, identity) {
|
|
177
|
+
const { name, email } = resolveGitIdentity(identity);
|
|
178
|
+
const setName = await sh(sandbox, `git -C ${shellQuote(workdir)} config user.name ${shellQuote(name)}`);
|
|
179
|
+
if (setName.exitCode !== 0) {
|
|
180
|
+
throw new MaterializeError(`Failed to set git user.name: ${setName.stderr.trim()}`, "commit-failed");
|
|
181
|
+
}
|
|
182
|
+
const setEmail = await sh(sandbox, `git -C ${shellQuote(workdir)} config user.email ${shellQuote(email)}`);
|
|
183
|
+
if (setEmail.exitCode !== 0) {
|
|
184
|
+
throw new MaterializeError(`Failed to set git user.email: ${setEmail.stderr.trim()}`, "commit-failed");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function withInstallToken(sandbox, workdir, repoFullName, token, fn) {
|
|
188
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(repoFullName)) {
|
|
189
|
+
throw new MaterializeError(`Refusing to push: invalid repo full name '${repoFullName}'.`, "push-failed");
|
|
190
|
+
}
|
|
191
|
+
const setUrl = await sh(
|
|
192
|
+
sandbox,
|
|
193
|
+
`git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(tokenUrl(repoFullName, token))}`
|
|
194
|
+
);
|
|
195
|
+
if (setUrl.exitCode !== 0) {
|
|
196
|
+
await scrubRemote(sandbox, workdir, repoFullName, false);
|
|
197
|
+
throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr.trim()}`, "push-failed");
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
return await fn();
|
|
201
|
+
} finally {
|
|
202
|
+
await scrubRemote(sandbox, workdir, repoFullName, true);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function pushBranch(sandbox, workdir, branch, token, repoFullName) {
|
|
206
|
+
if (!isValidGitRef(branch)) {
|
|
207
|
+
throw new MaterializeError(`Refusing to push: invalid branch name '${branch}'.`, "push-failed");
|
|
208
|
+
}
|
|
209
|
+
await withInstallToken(sandbox, workdir, repoFullName, token, async () => {
|
|
210
|
+
const push = await sh(sandbox, `git -C ${shellQuote(workdir)} push -u origin ${shellQuote(branch)}`);
|
|
211
|
+
if (push.exitCode !== 0) {
|
|
212
|
+
throw classifyGitFailure(push, "push-failed");
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async function commitAll(sandbox, workdir, message, identity) {
|
|
217
|
+
await configureGitIdentity(sandbox, workdir, identity);
|
|
218
|
+
const add = await sh(sandbox, `git -C ${shellQuote(workdir)} add -A`);
|
|
219
|
+
if (add.exitCode !== 0) {
|
|
220
|
+
throw new MaterializeError(`git add failed: ${add.stderr.trim() || add.stdout.trim()}`, "commit-failed");
|
|
221
|
+
}
|
|
222
|
+
const staged = await sh(sandbox, `git -C ${shellQuote(workdir)} diff --cached --quiet`);
|
|
223
|
+
if (staged.exitCode === 0) {
|
|
224
|
+
return { committed: false };
|
|
225
|
+
}
|
|
226
|
+
const commit = await sh(sandbox, `git -C ${shellQuote(workdir)} commit -m ${shellQuote(message)}`);
|
|
227
|
+
if (commit.exitCode !== 0) {
|
|
228
|
+
throw new MaterializeError(`git commit failed: ${commit.stderr.trim() || commit.stdout.trim()}`, "commit-failed");
|
|
229
|
+
}
|
|
230
|
+
return { committed: true };
|
|
231
|
+
}
|
|
232
|
+
var WorktreeError = class extends Error {
|
|
233
|
+
constructor(message, code) {
|
|
234
|
+
super(message);
|
|
235
|
+
this.code = code;
|
|
236
|
+
this.name = "WorktreeError";
|
|
237
|
+
}
|
|
238
|
+
code;
|
|
239
|
+
};
|
|
240
|
+
function safeBranchDir(branch) {
|
|
241
|
+
const sanitized = branch.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/\/+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 100) || "work";
|
|
242
|
+
if (sanitized === branch) return sanitized;
|
|
243
|
+
const hash = createHash("sha256").update(branch).digest("hex").slice(0, 8);
|
|
244
|
+
return `${sanitized}-${hash}`;
|
|
245
|
+
}
|
|
246
|
+
function computeWorktreePath(repoWorkdir, branch) {
|
|
247
|
+
const parent = repoWorkdir.replace(/\/+$/, "").split("/").slice(0, -1).join("/") || "";
|
|
248
|
+
return `${parent}/worktrees/${safeBranchDir(branch)}`;
|
|
249
|
+
}
|
|
250
|
+
async function ensureWorktree(sandbox, repoWorkdir, {
|
|
251
|
+
branch,
|
|
252
|
+
baseBranch,
|
|
253
|
+
token,
|
|
254
|
+
repoFullName
|
|
255
|
+
}) {
|
|
256
|
+
if (!isValidGitRef(branch)) {
|
|
257
|
+
throw new WorktreeError(`Invalid branch name '${branch}'.`, "invalid-branch");
|
|
258
|
+
}
|
|
259
|
+
if (!isValidGitRef(baseBranch)) {
|
|
260
|
+
throw new WorktreeError(`Invalid base branch name '${baseBranch}'.`, "invalid-branch");
|
|
261
|
+
}
|
|
262
|
+
const worktreePath = computeWorktreePath(repoWorkdir, branch);
|
|
263
|
+
const exists = await sh(sandbox, `test -e ${shellQuote(`${worktreePath}/.git`)}`);
|
|
264
|
+
if (exists.exitCode === 0) {
|
|
265
|
+
return { worktreePath, branch, baseBranch, reused: true };
|
|
266
|
+
}
|
|
267
|
+
const baseRef = `origin/${baseBranch}`;
|
|
268
|
+
await withInstallToken(sandbox, repoWorkdir, repoFullName, token, async () => {
|
|
269
|
+
const fetch = await sh(
|
|
270
|
+
sandbox,
|
|
271
|
+
`git -C ${shellQuote(repoWorkdir)} fetch origin ${shellQuote(`+refs/heads/${baseBranch}:refs/remotes/${baseRef}`)}`
|
|
272
|
+
);
|
|
273
|
+
if (fetch.exitCode !== 0) {
|
|
274
|
+
throw classifyGitFailure(fetch, "pull-failed");
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
const add = await sh(
|
|
278
|
+
sandbox,
|
|
279
|
+
`git -C ${shellQuote(repoWorkdir)} worktree add --no-track -B ${shellQuote(branch)} ${shellQuote(worktreePath)} ${shellQuote(baseRef)}`
|
|
280
|
+
);
|
|
281
|
+
if (add.exitCode !== 0) {
|
|
282
|
+
throw new WorktreeError(`git worktree add failed: ${add.stderr.trim() || add.stdout.trim()}`, "worktree-failed");
|
|
283
|
+
}
|
|
284
|
+
return { worktreePath, branch, baseBranch, reused: false };
|
|
285
|
+
}
|
|
286
|
+
async function runWorktreeSetup(sandbox, worktreePath, command) {
|
|
287
|
+
const result = await sh(sandbox, `cd ${shellQuote(worktreePath)} && { ${command}
|
|
288
|
+
}`);
|
|
289
|
+
if (result.exitCode !== 0) {
|
|
290
|
+
const detail = (result.stderr.trim() || result.stdout.trim()).slice(-2e3);
|
|
291
|
+
throw new WorktreeError(`Setup command failed (exit ${result.exitCode}): ${detail}`, "setup-failed");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async function removeWorktree(sandbox, repoWorkdir, { branch, worktreePath }) {
|
|
295
|
+
if (!isValidGitRef(branch)) {
|
|
296
|
+
throw new WorktreeError(`Invalid branch name '${branch}'.`, "invalid-branch");
|
|
297
|
+
}
|
|
298
|
+
const remove = await sh(
|
|
299
|
+
sandbox,
|
|
300
|
+
`git -C ${shellQuote(repoWorkdir)} worktree remove --force ${shellQuote(worktreePath)}`
|
|
301
|
+
);
|
|
302
|
+
if (remove.exitCode !== 0) {
|
|
303
|
+
await sh(sandbox, `git -C ${shellQuote(repoWorkdir)} worktree prune`);
|
|
304
|
+
const exists = await sh(sandbox, `test -e ${shellQuote(worktreePath)}`);
|
|
305
|
+
if (exists.exitCode === 0) {
|
|
306
|
+
throw new WorktreeError(
|
|
307
|
+
`git worktree remove failed: ${remove.stderr.trim() || remove.stdout.trim()}`,
|
|
308
|
+
"worktree-failed"
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
await sh(sandbox, `git -C ${shellQuote(repoWorkdir)} branch -D ${shellQuote(branch)}`);
|
|
313
|
+
}
|
|
314
|
+
async function assertGhAvailable(sandbox) {
|
|
315
|
+
const version = await sh(sandbox, "gh --version");
|
|
316
|
+
if (version.exitCode !== 0) {
|
|
317
|
+
throw new MaterializeError(
|
|
318
|
+
"The GitHub CLI (gh) is not installed in the sandbox. The sandbox template must include gh to open pull requests.",
|
|
319
|
+
"gh-missing"
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function parsePullRequestUrl(stdout) {
|
|
324
|
+
const match = stdout.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/);
|
|
325
|
+
return match?.[0];
|
|
326
|
+
}
|
|
327
|
+
async function createPullRequest(sandbox, workdir, { token, base, head, title, body }) {
|
|
328
|
+
if (!isValidGitRef(base)) {
|
|
329
|
+
throw new MaterializeError(`Refusing to open PR: invalid base branch '${base}'.`, "pr-failed");
|
|
330
|
+
}
|
|
331
|
+
if (!isValidGitRef(head)) {
|
|
332
|
+
throw new MaterializeError(`Refusing to open PR: invalid head branch '${head}'.`, "pr-failed");
|
|
333
|
+
}
|
|
334
|
+
await assertGhAvailable(sandbox);
|
|
335
|
+
const ghCommand = [
|
|
336
|
+
`GH_TOKEN=${shellQuote(token)} gh pr create`,
|
|
337
|
+
`--base ${shellQuote(base)}`,
|
|
338
|
+
`--head ${shellQuote(head)}`,
|
|
339
|
+
`--title ${shellQuote(title)}`,
|
|
340
|
+
`--body ${shellQuote(body ?? "")}`
|
|
341
|
+
].join(" ");
|
|
342
|
+
const script = `cd ${shellQuote(workdir)} && ${ghCommand}`;
|
|
343
|
+
const result = await sh(sandbox, script);
|
|
344
|
+
if (result.exitCode !== 0) {
|
|
345
|
+
const classified = classifyGitFailure(result, "push-failed");
|
|
346
|
+
if (classified.code === "egress-blocked") {
|
|
347
|
+
throw classified;
|
|
348
|
+
}
|
|
349
|
+
throw new MaterializeError(`gh pr create failed: ${result.stderr.trim() || result.stdout.trim()}`, "pr-failed");
|
|
350
|
+
}
|
|
351
|
+
const url = parsePullRequestUrl(result.stdout);
|
|
352
|
+
if (!url) {
|
|
353
|
+
throw new MaterializeError(
|
|
354
|
+
`gh pr create succeeded but no PR URL was found in its output: ${result.stdout.trim()}`,
|
|
355
|
+
"pr-failed"
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
return { url };
|
|
359
|
+
}
|
|
360
|
+
export {
|
|
361
|
+
MaterializeError,
|
|
362
|
+
WorktreeError,
|
|
363
|
+
checkoutSessionBranch,
|
|
364
|
+
commitAll,
|
|
365
|
+
computeWorktreePath,
|
|
366
|
+
configureGitIdentity,
|
|
367
|
+
createPullRequest,
|
|
368
|
+
ensureProjectSandbox,
|
|
369
|
+
ensureWorktree,
|
|
370
|
+
isValidGitRef,
|
|
371
|
+
materializeRepo,
|
|
372
|
+
pushBranch,
|
|
373
|
+
removeWorktree,
|
|
374
|
+
resolveGitIdentity,
|
|
375
|
+
runWorktreeSetup,
|
|
376
|
+
safeBranchDir,
|
|
377
|
+
shellQuote,
|
|
378
|
+
teardownProjectSandbox,
|
|
379
|
+
withInstallToken
|
|
380
|
+
};
|
|
381
|
+
//# sourceMappingURL=sandbox.js.map
|