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