@mastra/e2b 0.11.0-alpha.1 → 0.11.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/dist/index.cjs +74 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +74 -16
- package/dist/index.js.map +1 -1
- package/dist/utils/repo-template.d.ts +12 -5
- package/dist/utils/repo-template.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1562,7 +1562,7 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
1562
1562
|
* need `git fetch` + checkout of their actual ref plus setup drift, instead
|
|
1563
1563
|
* of a cold clone + full install.
|
|
1564
1564
|
*
|
|
1565
|
-
* There is exactly ONE template per (repo, setup command,
|
|
1565
|
+
* There is exactly ONE template per (repo, setup command, repoDir): the
|
|
1566
1566
|
* template name is a deterministic `mastra-repo-<hash>` over those inputs,
|
|
1567
1567
|
* and the commit sha rides as a docker-style TAG on that name
|
|
1568
1568
|
* (`mastra-repo-<hash>:sha-<sha>`). A moved default branch produces a new
|
|
@@ -1642,7 +1642,8 @@ function repoTemplateName(identity) {
|
|
|
1642
1642
|
identity.setupCommand ?? null,
|
|
1643
1643
|
identity.buildEnv ? Object.entries(identity.buildEnv).sort(([a], [b]) => a.localeCompare(b)) : null,
|
|
1644
1644
|
identity.cpuCount ?? 2,
|
|
1645
|
-
identity.memoryMB ?? 1024
|
|
1645
|
+
identity.memoryMB ?? 1024,
|
|
1646
|
+
...identity.workingDirectory !== void 0 ? [identity.workingDirectory] : []
|
|
1646
1647
|
];
|
|
1647
1648
|
const hash = createHash("sha256").update(JSON.stringify(config)).digest("hex").slice(0, 8);
|
|
1648
1649
|
const { owner, repo } = parseCloneUrl(cloneUrl);
|
|
@@ -1698,10 +1699,11 @@ async function resolveSpecAtHead(options) {
|
|
|
1698
1699
|
spec: buildRepoTemplateSpec({
|
|
1699
1700
|
cloneUrl,
|
|
1700
1701
|
...sha ? { sha } : {},
|
|
1701
|
-
...options.setupCommand ? { setupCommand: options.setupCommand } : {},
|
|
1702
|
+
...normalizeSetupCommands(options.setupCommand).length > 0 ? { setupCommand: options.setupCommand } : {},
|
|
1702
1703
|
...buildEnv ? { buildEnv } : {},
|
|
1703
1704
|
...options.cpuCount !== void 0 ? { cpuCount: options.cpuCount } : {},
|
|
1704
|
-
...options.memoryMB !== void 0 ? { memoryMB: options.memoryMB } : {}
|
|
1705
|
+
...options.memoryMB !== void 0 ? { memoryMB: options.memoryMB } : {},
|
|
1706
|
+
...options.workingDirectory !== void 0 ? { workingDirectory: trimTrailingSlashes(assertWorkingDirectory(options.workingDirectory)) } : {}
|
|
1705
1707
|
}, token),
|
|
1706
1708
|
...sha ? { sha } : {}
|
|
1707
1709
|
};
|
|
@@ -1739,7 +1741,7 @@ async function refreshRepoTemplate(options, connection) {
|
|
|
1739
1741
|
}
|
|
1740
1742
|
/**
|
|
1741
1743
|
* The clone URL is the only untrusted input that reaches a build command,
|
|
1742
|
-
* so it is checked before it can be interpolated into one. The
|
|
1744
|
+
* so it is checked before it can be interpolated into one. The repoDir is
|
|
1743
1745
|
* derived from it rather than supplied, so it needs no separate guard.
|
|
1744
1746
|
*/
|
|
1745
1747
|
function assertCloneUrl(cloneUrl) {
|
|
@@ -1756,18 +1758,21 @@ function gitAuthFlag() {
|
|
|
1756
1758
|
return `-c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$${BUILD_TOKEN_ENV}" | base64 -w0)"`;
|
|
1757
1759
|
}
|
|
1758
1760
|
function buildRepoTemplateSpec(identity, token) {
|
|
1759
|
-
const { sha, setupCommand, buildEnv } = identity;
|
|
1761
|
+
const { sha, setupCommand, buildEnv, workingDirectory } = identity;
|
|
1760
1762
|
const cloneUrl = normalizeCloneUrl(identity.cloneUrl);
|
|
1761
|
-
const
|
|
1763
|
+
const repoDir = repoDirName(cloneUrl);
|
|
1762
1764
|
const auth = token ? `${gitAuthFlag()} ` : "";
|
|
1763
|
-
const steps = [`git ${auth}clone ${cloneUrl} "${workdir}"`];
|
|
1764
|
-
if (sha) steps.push(`git -C "${workdir}" ${auth}fetch origin ${sha}`, `git -C "${workdir}" checkout ${sha}`);
|
|
1765
|
-
if (setupCommand) steps.push(`cd "${workdir}" && ${setupCommand}`);
|
|
1766
1765
|
let template = createDefaultMountableTemplate().template;
|
|
1767
1766
|
const env = { ...buildEnv };
|
|
1768
1767
|
if (token) env[BUILD_TOKEN_ENV] = token;
|
|
1769
1768
|
if (Object.keys(env).length > 0) template = template.setEnvs(env);
|
|
1770
|
-
|
|
1769
|
+
if (workingDirectory) {
|
|
1770
|
+
const dir = trimTrailingSlashes(workingDirectory);
|
|
1771
|
+
template = template.runCmd(`mkdir -p "${dir}"`).setWorkdir(dir);
|
|
1772
|
+
}
|
|
1773
|
+
template = template.runCmd(`git ${auth}clone ${cloneUrl} "${repoDir}"`);
|
|
1774
|
+
if (sha) template = template.runCmd(`git -C "${repoDir}" ${auth}fetch origin ${sha}`).runCmd(`git -C "${repoDir}" checkout ${sha}`);
|
|
1775
|
+
for (const command of normalizeSetupCommands(setupCommand)) template = template.runCmd(`cd "${repoDir}" && ${command}`);
|
|
1771
1776
|
return {
|
|
1772
1777
|
ref: repoTemplateRef(identity),
|
|
1773
1778
|
template,
|
|
@@ -1780,13 +1785,52 @@ function buildRepoTemplateSpec(identity, token) {
|
|
|
1780
1785
|
};
|
|
1781
1786
|
}
|
|
1782
1787
|
/**
|
|
1783
|
-
*
|
|
1784
|
-
*
|
|
1785
|
-
*
|
|
1788
|
+
* `owner/repo` for a github.com clone URL, else undefined. Only the public
|
|
1789
|
+
* host is API-resolvable: GitHub Enterprise and other forges keep the git
|
|
1790
|
+
* path.
|
|
1791
|
+
*/
|
|
1792
|
+
function parseGithubRepo(cloneUrl) {
|
|
1793
|
+
let url;
|
|
1794
|
+
try {
|
|
1795
|
+
url = new URL(cloneUrl);
|
|
1796
|
+
} catch {
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
if (url.hostname.toLowerCase() !== "github.com") return void 0;
|
|
1800
|
+
const [owner, repo, ...rest] = url.pathname.split("/").filter(Boolean);
|
|
1801
|
+
if (!owner || !repo || rest.length > 0) return void 0;
|
|
1802
|
+
return {
|
|
1803
|
+
owner,
|
|
1804
|
+
repo: repo.replace(/\.git$/i, "")
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Resolve the repository's current default-branch head without cloning.
|
|
1809
|
+
* github.com repositories go through the REST API so resolution works
|
|
1810
|
+
* wherever the host runs, including images without a git binary; other
|
|
1811
|
+
* hosts use `git ls-remote <url> HEAD`, authenticated via an in-process
|
|
1812
|
+
* `http.extraheader` when a token is provided. Returns undefined when the
|
|
1786
1813
|
* head cannot be resolved (inaccessible repo, offline, no git binary);
|
|
1787
1814
|
* callers degrade to the untagged template ref.
|
|
1788
1815
|
*/
|
|
1789
1816
|
async function resolveDefaultBranchHead(cloneUrl, token) {
|
|
1817
|
+
const github = parseGithubRepo(cloneUrl);
|
|
1818
|
+
if (github) try {
|
|
1819
|
+
const response = await fetch(`https://api.github.com/repos/${github.owner}/${github.repo}/commits/HEAD`, {
|
|
1820
|
+
headers: {
|
|
1821
|
+
Accept: "application/vnd.github.sha",
|
|
1822
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
1823
|
+
"User-Agent": "mastra-e2b",
|
|
1824
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
1825
|
+
},
|
|
1826
|
+
signal: AbortSignal.timeout(1e4)
|
|
1827
|
+
});
|
|
1828
|
+
if (!response.ok) return void 0;
|
|
1829
|
+
const sha = (await response.text()).trim();
|
|
1830
|
+
return SHA_PATTERN.test(sha) ? sha : void 0;
|
|
1831
|
+
} catch {
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1790
1834
|
try {
|
|
1791
1835
|
const authArgs = token ? ["-c", `http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`] : [];
|
|
1792
1836
|
const { stdout } = await execFileAsync("git", [
|
|
@@ -1834,9 +1878,23 @@ function parseCloneUrl(cloneUrl) {
|
|
|
1834
1878
|
repo
|
|
1835
1879
|
};
|
|
1836
1880
|
}
|
|
1837
|
-
|
|
1881
|
+
/** Normalize setup commands and drop blank entries that would produce invalid shell steps. */
|
|
1882
|
+
function normalizeSetupCommands(setupCommand) {
|
|
1883
|
+
return (setupCommand === void 0 ? [] : Array.isArray(setupCommand) ? setupCommand : [setupCommand]).filter((command) => command.trim() !== "");
|
|
1884
|
+
}
|
|
1885
|
+
function repoDirName(cloneUrl) {
|
|
1838
1886
|
const { repo } = parseCloneUrl(cloneUrl);
|
|
1839
|
-
return
|
|
1887
|
+
return repo.replace(/[^\w.-]/g, "-").replace(/^\.+/, "") || "repo";
|
|
1888
|
+
}
|
|
1889
|
+
function trimTrailingSlashes(path) {
|
|
1890
|
+
let end = path.length;
|
|
1891
|
+
while (end > 1 && path[end - 1] === "/") end--;
|
|
1892
|
+
return path.slice(0, end);
|
|
1893
|
+
}
|
|
1894
|
+
/** Validate a literal absolute path before embedding it in shell build steps. */
|
|
1895
|
+
function assertWorkingDirectory(dir) {
|
|
1896
|
+
if (!(/^\/[A-Za-z0-9._/-]*$/.test(dir) && !dir.split("/").includes(".."))) throw new Error(`Repo template workingDirectory must be an absolute path of plain path characters (got ${JSON.stringify(dir)}); ~ and $HOME are not expanded.`);
|
|
1897
|
+
return dir;
|
|
1840
1898
|
}
|
|
1841
1899
|
//#endregion
|
|
1842
1900
|
//#region src/provider.ts
|