@mastra/e2b 0.12.4-alpha.0 → 0.13.0-alpha.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 +156 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +156 -51
- package/dist/index.js.map +1 -1
- package/dist/utils/repo-template.d.ts +65 -2
- package/dist/utils/repo-template.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { E2BSandbox, type E2BSandboxOptions } from './sandbox/index.js';
|
|
2
2
|
export { E2BProcessManager } from './sandbox/process-manager.js';
|
|
3
3
|
export { createDefaultMountableTemplate, isNamedTemplateSpec, isDeferredNamedTemplateSpec, DEFAULT_NODE_VERSION, type TemplateSpec, type NamedTemplateSpec, type DeferredNamedTemplateSpec, type MountableTemplateResult, type MountableTemplateOptions, } from './utils/template.js';
|
|
4
|
-
export { createRepoTemplate, refreshRepoTemplate, repoTemplateRef, type RepoTemplateOptions, type RepoTemplateIdentity, type RepositoryAccess, type RefreshRepoTemplateResult, } from './utils/repo-template.js';
|
|
4
|
+
export { createRepoTemplate, refreshRepoTemplate, repoTemplateRef, type RepoTemplateOptions, type RepoTemplateRepository, type RepoTemplateIdentity, type RepositoryAccess, type RefreshRepoTemplateResult, } from './utils/repo-template.js';
|
|
5
5
|
export { type E2BS3MountConfig, type E2BGCSMountConfig, type E2BAzureBlobMountConfig, type E2BMountConfig, } from './sandbox/mounts/index.js';
|
|
6
6
|
export { e2bSandboxProvider } from './provider.js';
|
|
7
7
|
export { E2BCodeModeTransport } from './code-mode/transport.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,EACnB,2BAA2B,EAC3B,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,EACnB,2BAA2B,EAC3B,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -126,7 +126,7 @@ function validatePrefix(prefix) {
|
|
|
126
126
|
* Arguments containing only safe characters are returned as-is.
|
|
127
127
|
* All others are wrapped in single quotes with embedded single quotes escaped.
|
|
128
128
|
*/
|
|
129
|
-
function shellQuote$
|
|
129
|
+
function shellQuote$2(arg) {
|
|
130
130
|
if (/^[a-zA-Z0-9._\-/@:=]+$/.test(arg)) return arg;
|
|
131
131
|
return "'" + arg.replace(/'/g, "'\\''") + "'";
|
|
132
132
|
}
|
|
@@ -179,12 +179,12 @@ Error details: ${installResult.stderr || installResult.stdout}`);
|
|
|
179
179
|
};
|
|
180
180
|
if (hasCredentials) {
|
|
181
181
|
const credentialsContent = useSessionToken ? [
|
|
182
|
-
`export AWSACCESSKEYID=${shellQuote$
|
|
183
|
-
`export AWSSECRETACCESSKEY=${shellQuote$
|
|
184
|
-
`export AWSSESSIONTOKEN=${shellQuote$
|
|
185
|
-
`export AWS_ACCESS_KEY_ID=${shellQuote$
|
|
186
|
-
`export AWS_SECRET_ACCESS_KEY=${shellQuote$
|
|
187
|
-
`export AWS_SESSION_TOKEN=${shellQuote$
|
|
182
|
+
`export AWSACCESSKEYID=${shellQuote$2(config.accessKeyId)}`,
|
|
183
|
+
`export AWSSECRETACCESSKEY=${shellQuote$2(config.secretAccessKey)}`,
|
|
184
|
+
`export AWSSESSIONTOKEN=${shellQuote$2(config.sessionToken)}`,
|
|
185
|
+
`export AWS_ACCESS_KEY_ID=${shellQuote$2(config.accessKeyId)}`,
|
|
186
|
+
`export AWS_SECRET_ACCESS_KEY=${shellQuote$2(config.secretAccessKey)}`,
|
|
187
|
+
`export AWS_SESSION_TOKEN=${shellQuote$2(config.sessionToken)}`
|
|
188
188
|
].join("\n") : `${config.accessKeyId}:${config.secretAccessKey}`;
|
|
189
189
|
await sandbox.commands.run(`sudo rm -f ${credentialsPath}`);
|
|
190
190
|
try {
|
|
@@ -212,8 +212,8 @@ Error details: ${installResult.stderr || installResult.stdout}`);
|
|
|
212
212
|
mountOptions.push("ro");
|
|
213
213
|
logger.debug(`${LOG_PREFIX} Mounting as read-only`);
|
|
214
214
|
}
|
|
215
|
-
const s3fsCmd = `s3fs ${shellQuote$
|
|
216
|
-
const mountCmd = useSessionToken ? `sudo sh -c ${shellQuote$
|
|
215
|
+
const s3fsCmd = `s3fs ${shellQuote$2(bucketArg)} ${shellQuote$2(mountPath)} -o ${mountOptions.join(" -o ")}`;
|
|
216
|
+
const mountCmd = useSessionToken ? `sudo sh -c ${shellQuote$2(`. ${credentialsPath} && exec ${s3fsCmd}`)}` : `sudo ${s3fsCmd}`;
|
|
217
217
|
logger.debug(`${LOG_PREFIX} Mounting S3:`, hasCredentials ? mountCmd.replace(credentialsPath, "***") : mountCmd);
|
|
218
218
|
try {
|
|
219
219
|
const result = await sandbox.commands.run(mountCmd, { timeoutMs: 6e4 });
|
|
@@ -236,7 +236,7 @@ Error details: ${installResult.stderr || installResult.stdout}`);
|
|
|
236
236
|
} finally {
|
|
237
237
|
if (useSessionToken) await removeSessionCredentials();
|
|
238
238
|
}
|
|
239
|
-
if ((await sandbox.commands.run(`mountpoint -q ${shellQuote$
|
|
239
|
+
if ((await sandbox.commands.run(`mountpoint -q ${shellQuote$2(mountPath)}`)).exitCode !== 0) throw new Error(`s3fs returned exit 0 but ${mountPath} is not a mountpoint. The s3fs daemon likely failed during FUSE init (common causes: region mismatch, invalid credentials, or an S3-compatible endpoint that rejects the signature). Re-run inside the sandbox with '-f -o dbglevel=info' to see the underlying error.`);
|
|
240
240
|
}
|
|
241
241
|
//#endregion
|
|
242
242
|
//#region src/sandbox/mounts/gcs.ts
|
|
@@ -256,7 +256,7 @@ async function mountGCS(mountPath, config, ctx) {
|
|
|
256
256
|
}
|
|
257
257
|
const [uid, gid] = (await sandbox.commands.run("id -u && id -g")).stdout.trim().split("\n");
|
|
258
258
|
const uidGidFlags = uid && gid ? `--uid=${uid} --gid=${gid}` : "";
|
|
259
|
-
const onlyDirFlag = config.prefix ? ` --only-dir=${shellQuote$
|
|
259
|
+
const onlyDirFlag = config.prefix ? ` --only-dir=${shellQuote$2(validatePrefix(config.prefix))}` : "";
|
|
260
260
|
const hasCredentials = !!config.serviceAccountKey;
|
|
261
261
|
let mountCmd;
|
|
262
262
|
if (hasCredentials) {
|
|
@@ -464,9 +464,9 @@ Error details: ${verifyResult.stderr || verifyResult.stdout || installResult?.st
|
|
|
464
464
|
await sandbox.commands.run(`sudo rm -f ${configPath}`);
|
|
465
465
|
await sandbox.files.write(configPath, yaml);
|
|
466
466
|
await sandbox.commands.run(`sudo chown root:root ${configPath} && sudo chmod 600 ${configPath}`);
|
|
467
|
-
await sandbox.commands.run(`sudo rm -rf ${shellQuote$
|
|
468
|
-
const prefixFlags = prefix ? ` --virtual-directory=true --subdirectory=${shellQuote$
|
|
469
|
-
const mountCmd = `sudo blobfuse2 mount ${shellQuote$
|
|
467
|
+
await sandbox.commands.run(`sudo rm -rf ${shellQuote$2(cachePath)} && sudo mkdir -p ${shellQuote$2(cachePath)}`);
|
|
468
|
+
const prefixFlags = prefix ? ` --virtual-directory=true --subdirectory=${shellQuote$2(prefix)}` : "";
|
|
469
|
+
const mountCmd = `sudo blobfuse2 mount ${shellQuote$2(mountPath)} --config-file=${shellQuote$2(configPath)}${prefixFlags}`;
|
|
470
470
|
logger.debug(`${LOG_PREFIX} Mounting Azure Blob:`, mountCmd);
|
|
471
471
|
try {
|
|
472
472
|
const result = await sandbox.commands.run(mountCmd, { timeoutMs: 6e4 });
|
|
@@ -1593,6 +1593,8 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
1593
1593
|
};
|
|
1594
1594
|
//#endregion
|
|
1595
1595
|
//#region ../../packages/_internals/workspace/dist/index.js
|
|
1596
|
+
/** Directory, relative to the template's build cwd, that holds every marker below. */
|
|
1597
|
+
const SETUP_MARKER_DIR = ".mastra-sandbox";
|
|
1596
1598
|
/**
|
|
1597
1599
|
* Setup completion marker shared by repo templates and their consumers.
|
|
1598
1600
|
*
|
|
@@ -1602,8 +1604,29 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
1602
1604
|
* booted from the image tell whether the setup it is about to run already
|
|
1603
1605
|
* happened. Relative to the template's build cwd, which is also the runtime
|
|
1604
1606
|
* working directory the repo was cloned into.
|
|
1607
|
+
*
|
|
1608
|
+
* This is the single-repository marker. Templates built from a repository
|
|
1609
|
+
* list write one marker per repository instead ({@link repoSetupMarkerPath}).
|
|
1605
1610
|
*/
|
|
1606
1611
|
const SETUP_MARKER_PATH = ".mastra-sandbox/setup";
|
|
1612
|
+
/**
|
|
1613
|
+
* Marker for the workspace-level setup of a multi-repository template: the
|
|
1614
|
+
* commands that ran at the working directory after every repository was set
|
|
1615
|
+
* up. Written even when there were no such commands, so a consumer can tell
|
|
1616
|
+
* "ran nothing" from "never built".
|
|
1617
|
+
*/
|
|
1618
|
+
const WORKSPACE_SETUP_MARKER_PATH = ".mastra-sandbox/workspace-setup";
|
|
1619
|
+
/**
|
|
1620
|
+
* One line per repository directory whose setup failed while the build was
|
|
1621
|
+
* told to continue. Absent when nothing failed. Consumers check this before
|
|
1622
|
+
* trusting a per-repository marker: that marker means every step ran, not
|
|
1623
|
+
* that every step succeeded.
|
|
1624
|
+
*/
|
|
1625
|
+
const SETUP_FAILED_MARKER_PATH = ".mastra-sandbox/setup-failed";
|
|
1626
|
+
/** Per-repository marker of a multi-repository template, keyed by the clone directory name. */
|
|
1627
|
+
function repoSetupMarkerPath(repoDir) {
|
|
1628
|
+
return `${SETUP_MARKER_DIR}/repos/${repoDir}`;
|
|
1629
|
+
}
|
|
1607
1630
|
/** Blank entries never become build steps, so they never count toward the digest either. */
|
|
1608
1631
|
function normalizeSetupCommands$1(setupCommand) {
|
|
1609
1632
|
return (setupCommand === void 0 ? [] : Array.isArray(setupCommand) ? setupCommand : [setupCommand]).filter((command) => command.trim() !== "");
|
|
@@ -1613,8 +1636,26 @@ function setupMarkerContent(setupCommand) {
|
|
|
1613
1636
|
return `sha256:${createHash("sha256").update(normalizeSetupCommands$1(setupCommand).join("\n")).digest("hex")}`;
|
|
1614
1637
|
}
|
|
1615
1638
|
/** Shell step that writes the marker relative to the cwd. `content` is a digest, so it is shell-safe. */
|
|
1616
|
-
function setupMarkerCommand(content) {
|
|
1617
|
-
return `mkdir -p "$(dirname "${
|
|
1639
|
+
function setupMarkerCommand(content, markerPath = SETUP_MARKER_PATH) {
|
|
1640
|
+
return `mkdir -p "$(dirname "${markerPath}")" && printf '%s' '${content}' > "${markerPath}"`;
|
|
1641
|
+
}
|
|
1642
|
+
/** A clone directory name is one path segment of word characters, dots and dashes, so it never needs quoting. */
|
|
1643
|
+
const REPO_DIR_PATTERN = /^[\w.-]+$/;
|
|
1644
|
+
/**
|
|
1645
|
+
* Shell step that runs one setup command inside a repository clone.
|
|
1646
|
+
*
|
|
1647
|
+
* Without the guard this is the plain `cd "<dir>" && <cmd>` step. With it,
|
|
1648
|
+
* the `cd` sits inside the parenthesized group so that the failure branch
|
|
1649
|
+
* runs back at the build cwd and the failure list lands at the workspace
|
|
1650
|
+
* level, never inside the repository.
|
|
1651
|
+
*/
|
|
1652
|
+
function guardedSetupCommand({ repoDir, command, continueOnFailure }) {
|
|
1653
|
+
if (!REPO_DIR_PATTERN.test(repoDir)) throw new TypeError(`Repository directory "${repoDir}" must match ${REPO_DIR_PATTERN}.`);
|
|
1654
|
+
if (!continueOnFailure) return `cd "${repoDir}" && ${command}`;
|
|
1655
|
+
return `( cd "${repoDir}" && sh -c ${shellQuote$1(command)} ) || { mkdir -p "${SETUP_MARKER_DIR}" && grep -qxF -- '${repoDir}' "${SETUP_FAILED_MARKER_PATH}" 2>/dev/null || printf '%s\\n' '${repoDir}' >> "${SETUP_FAILED_MARKER_PATH}"; }`;
|
|
1656
|
+
}
|
|
1657
|
+
function shellQuote$1(value) {
|
|
1658
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1618
1659
|
}
|
|
1619
1660
|
function repoCloneCommand({ cloneUrl, destination, branch, tokenEnv }) {
|
|
1620
1661
|
return `git ${tokenEnv ? `${gitAuthFlag$1(tokenEnv)} ` : ""}clone --depth=1 --single-branch ${branch ? `--branch ${shellQuote(branch)} ` : ""}${shellQuote(cloneUrl)} ${shellQuote(destination)}`;
|
|
@@ -1706,8 +1747,16 @@ function isValidCloneUrl(cloneUrl) {
|
|
|
1706
1747
|
*/
|
|
1707
1748
|
function repoTemplateRef(identity) {
|
|
1708
1749
|
const name = repoTemplateName(identity);
|
|
1750
|
+
if (identity.repos) {
|
|
1751
|
+
const shas = identity.repos.map((repo) => repo.sha);
|
|
1752
|
+
return shas.every(Boolean) ? `${name}:${shaTag(listSha(shas))}` : `${name}:${CURRENT_TAG}`;
|
|
1753
|
+
}
|
|
1709
1754
|
return identity.sha ? `${name}:${shaTag(identity.sha)}` : `${name}:${CURRENT_TAG}`;
|
|
1710
1755
|
}
|
|
1756
|
+
/** One digest over every repository's pinned sha, in caller order. */
|
|
1757
|
+
function listSha(shas) {
|
|
1758
|
+
return createHash("sha256").update(shas.map((sha) => sha.toLowerCase()).join(",")).digest("hex");
|
|
1759
|
+
}
|
|
1711
1760
|
function repoTemplateName(identity) {
|
|
1712
1761
|
const cloneUrl = normalizeCloneUrl(identity.cloneUrl);
|
|
1713
1762
|
const config = [
|
|
@@ -1717,7 +1766,13 @@ function repoTemplateName(identity) {
|
|
|
1717
1766
|
identity.buildEnv ? Object.entries(identity.buildEnv).sort(([a], [b]) => a.localeCompare(b)) : null,
|
|
1718
1767
|
identity.cpuCount ?? 2,
|
|
1719
1768
|
identity.memoryMB ?? 1024,
|
|
1720
|
-
...identity.workingDirectory !== void 0 ? [identity.workingDirectory] : []
|
|
1769
|
+
...identity.workingDirectory !== void 0 ? [identity.workingDirectory] : [],
|
|
1770
|
+
...identity.repos ? [
|
|
1771
|
+
"repos",
|
|
1772
|
+
identity.repos.map((repo) => [normalizeCloneUrl(repo.cloneUrl), repo.setupCommand ?? null]),
|
|
1773
|
+
normalizeSetupCommands(identity.workspaceSetupCommand),
|
|
1774
|
+
identity.continueOnSetupFailure ?? false
|
|
1775
|
+
] : []
|
|
1721
1776
|
];
|
|
1722
1777
|
const hash = createHash("sha256").update(JSON.stringify(config)).digest("hex").slice(0, 8);
|
|
1723
1778
|
const { owner, repo } = parseCloneUrl(cloneUrl);
|
|
@@ -1749,7 +1804,9 @@ function shaTag(sha) {
|
|
|
1749
1804
|
* setup performs the full clone, so a broken build never wedges a session.
|
|
1750
1805
|
*/
|
|
1751
1806
|
function createRepoTemplate(options) {
|
|
1752
|
-
if (
|
|
1807
|
+
if (options.getRepositoryAccess && options.repos) throw new TypeError("createRepoTemplate: pass either getRepositoryAccess or repos, not both");
|
|
1808
|
+
if (!options.repos && (options.workspaceSetupCommand !== void 0 || options.continueOnSetupFailure !== void 0)) throw new TypeError("createRepoTemplate: workspaceSetupCommand and continueOnSetupFailure require repos");
|
|
1809
|
+
if (!options.getRepositoryAccess && !options.repos?.length) return void 0;
|
|
1753
1810
|
return { resolveSpec: async () => (await resolveSpecAtHead(options)).spec };
|
|
1754
1811
|
}
|
|
1755
1812
|
/**
|
|
@@ -1761,24 +1818,58 @@ function createRepoTemplate(options) {
|
|
|
1761
1818
|
* turns into its default-template fallback rather than a failed start.
|
|
1762
1819
|
*/
|
|
1763
1820
|
async function resolveSpecAtHead(options) {
|
|
1764
|
-
const
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1821
|
+
const isList = options.repos !== void 0;
|
|
1822
|
+
const entries = options.repos ? options.repos : options.getRepositoryAccess ? [{
|
|
1823
|
+
getRepositoryAccess: options.getRepositoryAccess,
|
|
1824
|
+
setupCommand: options.setupCommand
|
|
1825
|
+
}] : [];
|
|
1826
|
+
if (entries.length === 0) throw new Error("Repo template has no clone URL: repository access returned none.");
|
|
1769
1827
|
const buildEnv = typeof options.buildEnv === "function" ? await options.buildEnv() : options.buildEnv;
|
|
1770
|
-
const
|
|
1771
|
-
const
|
|
1772
|
-
|
|
1773
|
-
|
|
1828
|
+
const repos = [];
|
|
1829
|
+
const seenDirs = /* @__PURE__ */ new Map();
|
|
1830
|
+
for (const [index, entry] of entries.entries()) {
|
|
1831
|
+
const access = await entry.getRepositoryAccess().catch(() => void 0);
|
|
1832
|
+
const cloneUrl = access?.cloneUrl;
|
|
1833
|
+
if (!cloneUrl) throw new Error(`Repo template has no clone URL: repository access returned none${isList ? ` (repos[${index}])` : ""}.`);
|
|
1834
|
+
assertCloneUrl(cloneUrl);
|
|
1835
|
+
const repoDir = repoDirName(normalizeCloneUrl(cloneUrl));
|
|
1836
|
+
const other = seenDirs.get(repoDir);
|
|
1837
|
+
if (other) throw new Error(`Repo template: repositories ${other} and ${cloneUrl} would both clone into "${repoDir}"`);
|
|
1838
|
+
seenDirs.set(repoDir, cloneUrl);
|
|
1839
|
+
const token = access?.authorization?.token;
|
|
1840
|
+
const resolved = await resolveDefaultBranchHead(cloneUrl, token).catch(() => void 0);
|
|
1841
|
+
const sha = resolved && SHA_PATTERN.test(resolved) ? resolved : void 0;
|
|
1842
|
+
repos.push({
|
|
1774
1843
|
cloneUrl,
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1844
|
+
repoDir,
|
|
1845
|
+
token,
|
|
1846
|
+
tokenEnv: token ? isList ? `${BUILD_TOKEN_ENV}_${index}` : BUILD_TOKEN_ENV : void 0,
|
|
1847
|
+
sha,
|
|
1848
|
+
setupCommand: entry.setupCommand
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
const first = repos[0];
|
|
1852
|
+
const spec = buildRepoTemplateSpec({
|
|
1853
|
+
cloneUrl: first.cloneUrl,
|
|
1854
|
+
...first.sha ? { sha: first.sha } : {},
|
|
1855
|
+
...normalizeSetupCommands(first.setupCommand).length > 0 ? { setupCommand: first.setupCommand } : {},
|
|
1856
|
+
...buildEnv ? { buildEnv } : {},
|
|
1857
|
+
...options.cpuCount !== void 0 ? { cpuCount: options.cpuCount } : {},
|
|
1858
|
+
...options.memoryMB !== void 0 ? { memoryMB: options.memoryMB } : {},
|
|
1859
|
+
...options.workingDirectory !== void 0 ? { workingDirectory: trimTrailingSlashes(assertWorkingDirectory(options.workingDirectory)) } : {},
|
|
1860
|
+
...isList ? {
|
|
1861
|
+
repos: repos.map((repo) => ({
|
|
1862
|
+
cloneUrl: repo.cloneUrl,
|
|
1863
|
+
...repo.sha ? { sha: repo.sha } : {},
|
|
1864
|
+
...normalizeSetupCommands(repo.setupCommand).length > 0 ? { setupCommand: repo.setupCommand } : {}
|
|
1865
|
+
})),
|
|
1866
|
+
...options.workspaceSetupCommand !== void 0 ? { workspaceSetupCommand: options.workspaceSetupCommand } : {},
|
|
1867
|
+
...options.continueOnSetupFailure !== void 0 ? { continueOnSetupFailure: options.continueOnSetupFailure } : {}
|
|
1868
|
+
} : {}
|
|
1869
|
+
}, repos, options.continueOnSetupFailure ?? false);
|
|
1870
|
+
const sha = isList ? repos.every((repo) => repo.sha) ? listSha(repos.map((repo) => repo.sha)) : void 0 : first.sha;
|
|
1871
|
+
return {
|
|
1872
|
+
spec,
|
|
1782
1873
|
...sha ? { sha } : {}
|
|
1783
1874
|
};
|
|
1784
1875
|
}
|
|
@@ -1828,31 +1919,45 @@ function assertCloneUrl(cloneUrl) {
|
|
|
1828
1919
|
* REFERENCE — the token value never appears in the command string, and no
|
|
1829
1920
|
* credential is written to the build filesystem.
|
|
1830
1921
|
*/
|
|
1831
|
-
function gitAuthFlag() {
|
|
1832
|
-
return `-c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$${
|
|
1922
|
+
function gitAuthFlag(tokenEnv) {
|
|
1923
|
+
return `-c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$${tokenEnv}" | base64 -w0)"`;
|
|
1833
1924
|
}
|
|
1834
|
-
function buildRepoTemplateSpec(identity,
|
|
1835
|
-
const {
|
|
1836
|
-
const
|
|
1837
|
-
const repoDir = repoDirName(cloneUrl);
|
|
1838
|
-
const auth = token ? `${gitAuthFlag()} ` : "";
|
|
1925
|
+
function buildRepoTemplateSpec(identity, repos, continueOnFailure) {
|
|
1926
|
+
const { buildEnv, workingDirectory } = identity;
|
|
1927
|
+
const isList = identity.repos !== void 0;
|
|
1839
1928
|
let template = createDefaultMountableTemplate().template;
|
|
1840
1929
|
const env = { ...buildEnv };
|
|
1841
|
-
if (token) env[
|
|
1930
|
+
for (const repo of repos) if (repo.tokenEnv && repo.token) env[repo.tokenEnv] = repo.token;
|
|
1842
1931
|
if (Object.keys(env).length > 0) template = template.setEnvs(env);
|
|
1843
1932
|
if (workingDirectory) {
|
|
1844
1933
|
const dir = trimTrailingSlashes(workingDirectory);
|
|
1845
1934
|
template = template.runCmd(`mkdir -p "${dir}"`).setWorkdir(dir);
|
|
1846
1935
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1936
|
+
const ordered = [...repos.filter((repo) => !repo.token), ...repos.filter((repo) => repo.token)];
|
|
1937
|
+
for (const repo of ordered) {
|
|
1938
|
+
const { repoDir, sha, tokenEnv } = repo;
|
|
1939
|
+
const cloneUrl = normalizeCloneUrl(repo.cloneUrl);
|
|
1940
|
+
const auth = tokenEnv ? `${gitAuthFlag(tokenEnv)} ` : "";
|
|
1941
|
+
const setupCommands = normalizeSetupCommands(repo.setupCommand);
|
|
1942
|
+
template = template.runCmd(repoCloneCommand({
|
|
1943
|
+
cloneUrl,
|
|
1944
|
+
destination: repoDir,
|
|
1945
|
+
...tokenEnv ? { tokenEnv } : {}
|
|
1946
|
+
}));
|
|
1947
|
+
if (sha) template = template.runCmd(`git -C "${repoDir}" ${auth}fetch origin ${sha}`).runCmd(`git -C "${repoDir}" checkout ${sha}`);
|
|
1948
|
+
for (const command of setupCommands) template = template.runCmd(guardedSetupCommand({
|
|
1949
|
+
repoDir,
|
|
1950
|
+
command,
|
|
1951
|
+
continueOnFailure
|
|
1952
|
+
}));
|
|
1953
|
+
const content = setupMarkerContent(setupCommands);
|
|
1954
|
+
template = template.runCmd(isList ? setupMarkerCommand(content, repoSetupMarkerPath(repoDir)) : setupMarkerCommand(content));
|
|
1955
|
+
}
|
|
1956
|
+
if (isList) {
|
|
1957
|
+
const workspaceCommands = normalizeSetupCommands(identity.workspaceSetupCommand);
|
|
1958
|
+
for (const command of workspaceCommands) template = template.runCmd(command);
|
|
1959
|
+
template = template.runCmd(setupMarkerCommand(setupMarkerContent(workspaceCommands), WORKSPACE_SETUP_MARKER_PATH));
|
|
1960
|
+
}
|
|
1856
1961
|
return {
|
|
1857
1962
|
ref: repoTemplateRef(identity),
|
|
1858
1963
|
template,
|