@mastra/e2b 0.12.4-alpha.0 → 0.13.0-alpha.2
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 +168 -53
- 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 +168 -53
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/utils/repo-template.d.ts +65 -2
- package/dist/utils/repo-template.d.ts.map +1 -1
- package/dist/utils/template.d.ts +7 -2
- package/dist/utils/template.d.ts.map +1 -1
- package/package.json +4 -4
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 });
|
|
@@ -861,10 +861,15 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
861
861
|
if (!isTemplateUnusable(createError)) throw createError;
|
|
862
862
|
const namedSpec = this.templateSpec && isNamedTemplateSpec(this.templateSpec) ? this.templateSpec : this._resolvedNamedSpec;
|
|
863
863
|
if (namedSpec) {
|
|
864
|
+
const { fallbackTemplate } = namedSpec;
|
|
865
|
+
if (fallbackTemplate === false) {
|
|
866
|
+
this._resolvedTemplateId = void 0;
|
|
867
|
+
this.logger.error(`${LOG_PREFIX} Creating from '${resolvedTemplateId}' failed (fallback disabled): ${createError}`);
|
|
868
|
+
throw createError;
|
|
869
|
+
}
|
|
864
870
|
this.logger.warn(`${LOG_PREFIX} Creating from '${resolvedTemplateId}' failed, retrying on fallback: ${createError}`);
|
|
865
871
|
this._resolvedTemplateId = void 0;
|
|
866
|
-
const
|
|
867
|
-
const fallbackId = resolvedTemplateId === spec.ref ? await this.resolveFallbackTemplate(spec.fallbackTemplate) : await this.buildOrReuseDefaultTemplate();
|
|
872
|
+
const fallbackId = resolvedTemplateId === namedSpec.ref ? await this.resolveFallbackTemplate(fallbackTemplate) : await this.buildOrReuseDefaultTemplate();
|
|
868
873
|
try {
|
|
869
874
|
sdkSandbox = await createFromTemplate(fallbackId);
|
|
870
875
|
this._resolvedTemplateId = fallbackId;
|
|
@@ -1385,6 +1390,11 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
1385
1390
|
this._resolvedTemplateId = ref;
|
|
1386
1391
|
return ref;
|
|
1387
1392
|
} catch (error) {
|
|
1393
|
+
if (fallbackTemplate === false) {
|
|
1394
|
+
this._resolvedTemplateId = void 0;
|
|
1395
|
+
this.logger.error(`${LOG_PREFIX} Template '${ref}' resolution failed (fallback disabled): ${error}`);
|
|
1396
|
+
throw error;
|
|
1397
|
+
}
|
|
1388
1398
|
this.logger.warn(`${LOG_PREFIX} Template '${ref}' resolution failed, falling back: ${error}`);
|
|
1389
1399
|
return await this.resolveFallbackTemplate(fallbackTemplate);
|
|
1390
1400
|
}
|
|
@@ -1593,6 +1603,8 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
1593
1603
|
};
|
|
1594
1604
|
//#endregion
|
|
1595
1605
|
//#region ../../packages/_internals/workspace/dist/index.js
|
|
1606
|
+
/** Directory, relative to the template's build cwd, that holds every marker below. */
|
|
1607
|
+
const SETUP_MARKER_DIR = ".mastra-sandbox";
|
|
1596
1608
|
/**
|
|
1597
1609
|
* Setup completion marker shared by repo templates and their consumers.
|
|
1598
1610
|
*
|
|
@@ -1602,8 +1614,29 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
1602
1614
|
* booted from the image tell whether the setup it is about to run already
|
|
1603
1615
|
* happened. Relative to the template's build cwd, which is also the runtime
|
|
1604
1616
|
* working directory the repo was cloned into.
|
|
1617
|
+
*
|
|
1618
|
+
* This is the single-repository marker. Templates built from a repository
|
|
1619
|
+
* list write one marker per repository instead ({@link repoSetupMarkerPath}).
|
|
1605
1620
|
*/
|
|
1606
1621
|
const SETUP_MARKER_PATH = ".mastra-sandbox/setup";
|
|
1622
|
+
/**
|
|
1623
|
+
* Marker for the workspace-level setup of a multi-repository template: the
|
|
1624
|
+
* commands that ran at the working directory after every repository was set
|
|
1625
|
+
* up. Written even when there were no such commands, so a consumer can tell
|
|
1626
|
+
* "ran nothing" from "never built".
|
|
1627
|
+
*/
|
|
1628
|
+
const WORKSPACE_SETUP_MARKER_PATH = ".mastra-sandbox/workspace-setup";
|
|
1629
|
+
/**
|
|
1630
|
+
* One line per repository directory whose setup failed while the build was
|
|
1631
|
+
* told to continue. Absent when nothing failed. Consumers check this before
|
|
1632
|
+
* trusting a per-repository marker: that marker means every step ran, not
|
|
1633
|
+
* that every step succeeded.
|
|
1634
|
+
*/
|
|
1635
|
+
const SETUP_FAILED_MARKER_PATH = ".mastra-sandbox/setup-failed";
|
|
1636
|
+
/** Per-repository marker of a multi-repository template, keyed by the clone directory name. */
|
|
1637
|
+
function repoSetupMarkerPath(repoDir) {
|
|
1638
|
+
return `${SETUP_MARKER_DIR}/repos/${repoDir}`;
|
|
1639
|
+
}
|
|
1607
1640
|
/** Blank entries never become build steps, so they never count toward the digest either. */
|
|
1608
1641
|
function normalizeSetupCommands$1(setupCommand) {
|
|
1609
1642
|
return (setupCommand === void 0 ? [] : Array.isArray(setupCommand) ? setupCommand : [setupCommand]).filter((command) => command.trim() !== "");
|
|
@@ -1613,8 +1646,26 @@ function setupMarkerContent(setupCommand) {
|
|
|
1613
1646
|
return `sha256:${createHash("sha256").update(normalizeSetupCommands$1(setupCommand).join("\n")).digest("hex")}`;
|
|
1614
1647
|
}
|
|
1615
1648
|
/** 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 "${
|
|
1649
|
+
function setupMarkerCommand(content, markerPath = SETUP_MARKER_PATH) {
|
|
1650
|
+
return `mkdir -p "$(dirname "${markerPath}")" && printf '%s' '${content}' > "${markerPath}"`;
|
|
1651
|
+
}
|
|
1652
|
+
/** A clone directory name is one path segment of word characters, dots and dashes, so it never needs quoting. */
|
|
1653
|
+
const REPO_DIR_PATTERN = /^[\w.-]+$/;
|
|
1654
|
+
/**
|
|
1655
|
+
* Shell step that runs one setup command inside a repository clone.
|
|
1656
|
+
*
|
|
1657
|
+
* Without the guard this is the plain `cd "<dir>" && <cmd>` step. With it,
|
|
1658
|
+
* the `cd` sits inside the parenthesized group so that the failure branch
|
|
1659
|
+
* runs back at the build cwd and the failure list lands at the workspace
|
|
1660
|
+
* level, never inside the repository.
|
|
1661
|
+
*/
|
|
1662
|
+
function guardedSetupCommand({ repoDir, command, continueOnFailure }) {
|
|
1663
|
+
if (!REPO_DIR_PATTERN.test(repoDir)) throw new TypeError(`Repository directory "${repoDir}" must match ${REPO_DIR_PATTERN}.`);
|
|
1664
|
+
if (!continueOnFailure) return `cd "${repoDir}" && ${command}`;
|
|
1665
|
+
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}"; }`;
|
|
1666
|
+
}
|
|
1667
|
+
function shellQuote$1(value) {
|
|
1668
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1618
1669
|
}
|
|
1619
1670
|
function repoCloneCommand({ cloneUrl, destination, branch, tokenEnv }) {
|
|
1620
1671
|
return `git ${tokenEnv ? `${gitAuthFlag$1(tokenEnv)} ` : ""}clone --depth=1 --single-branch ${branch ? `--branch ${shellQuote(branch)} ` : ""}${shellQuote(cloneUrl)} ${shellQuote(destination)}`;
|
|
@@ -1706,8 +1757,16 @@ function isValidCloneUrl(cloneUrl) {
|
|
|
1706
1757
|
*/
|
|
1707
1758
|
function repoTemplateRef(identity) {
|
|
1708
1759
|
const name = repoTemplateName(identity);
|
|
1760
|
+
if (identity.repos) {
|
|
1761
|
+
const shas = identity.repos.map((repo) => repo.sha);
|
|
1762
|
+
return shas.every(Boolean) ? `${name}:${shaTag(listSha(shas))}` : `${name}:${CURRENT_TAG}`;
|
|
1763
|
+
}
|
|
1709
1764
|
return identity.sha ? `${name}:${shaTag(identity.sha)}` : `${name}:${CURRENT_TAG}`;
|
|
1710
1765
|
}
|
|
1766
|
+
/** One digest over every repository's pinned sha, in caller order. */
|
|
1767
|
+
function listSha(shas) {
|
|
1768
|
+
return createHash("sha256").update(shas.map((sha) => sha.toLowerCase()).join(",")).digest("hex");
|
|
1769
|
+
}
|
|
1711
1770
|
function repoTemplateName(identity) {
|
|
1712
1771
|
const cloneUrl = normalizeCloneUrl(identity.cloneUrl);
|
|
1713
1772
|
const config = [
|
|
@@ -1717,7 +1776,13 @@ function repoTemplateName(identity) {
|
|
|
1717
1776
|
identity.buildEnv ? Object.entries(identity.buildEnv).sort(([a], [b]) => a.localeCompare(b)) : null,
|
|
1718
1777
|
identity.cpuCount ?? 2,
|
|
1719
1778
|
identity.memoryMB ?? 1024,
|
|
1720
|
-
...identity.workingDirectory !== void 0 ? [identity.workingDirectory] : []
|
|
1779
|
+
...identity.workingDirectory !== void 0 ? [identity.workingDirectory] : [],
|
|
1780
|
+
...identity.repos ? [
|
|
1781
|
+
"repos",
|
|
1782
|
+
identity.repos.map((repo) => [normalizeCloneUrl(repo.cloneUrl), repo.setupCommand ?? null]),
|
|
1783
|
+
normalizeSetupCommands(identity.workspaceSetupCommand),
|
|
1784
|
+
identity.continueOnSetupFailure ?? false
|
|
1785
|
+
] : []
|
|
1721
1786
|
];
|
|
1722
1787
|
const hash = createHash("sha256").update(JSON.stringify(config)).digest("hex").slice(0, 8);
|
|
1723
1788
|
const { owner, repo } = parseCloneUrl(cloneUrl);
|
|
@@ -1749,7 +1814,9 @@ function shaTag(sha) {
|
|
|
1749
1814
|
* setup performs the full clone, so a broken build never wedges a session.
|
|
1750
1815
|
*/
|
|
1751
1816
|
function createRepoTemplate(options) {
|
|
1752
|
-
if (
|
|
1817
|
+
if (options.getRepositoryAccess && options.repos) throw new TypeError("createRepoTemplate: pass either getRepositoryAccess or repos, not both");
|
|
1818
|
+
if (!options.repos && (options.workspaceSetupCommand !== void 0 || options.continueOnSetupFailure !== void 0)) throw new TypeError("createRepoTemplate: workspaceSetupCommand and continueOnSetupFailure require repos");
|
|
1819
|
+
if (!options.getRepositoryAccess && !options.repos?.length) return void 0;
|
|
1753
1820
|
return { resolveSpec: async () => (await resolveSpecAtHead(options)).spec };
|
|
1754
1821
|
}
|
|
1755
1822
|
/**
|
|
@@ -1761,24 +1828,58 @@ function createRepoTemplate(options) {
|
|
|
1761
1828
|
* turns into its default-template fallback rather than a failed start.
|
|
1762
1829
|
*/
|
|
1763
1830
|
async function resolveSpecAtHead(options) {
|
|
1764
|
-
const
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1831
|
+
const isList = options.repos !== void 0;
|
|
1832
|
+
const entries = options.repos ? options.repos : options.getRepositoryAccess ? [{
|
|
1833
|
+
getRepositoryAccess: options.getRepositoryAccess,
|
|
1834
|
+
setupCommand: options.setupCommand
|
|
1835
|
+
}] : [];
|
|
1836
|
+
if (entries.length === 0) throw new Error("Repo template has no clone URL: repository access returned none.");
|
|
1769
1837
|
const buildEnv = typeof options.buildEnv === "function" ? await options.buildEnv() : options.buildEnv;
|
|
1770
|
-
const
|
|
1771
|
-
const
|
|
1772
|
-
|
|
1773
|
-
|
|
1838
|
+
const repos = [];
|
|
1839
|
+
const seenDirs = /* @__PURE__ */ new Map();
|
|
1840
|
+
for (const [index, entry] of entries.entries()) {
|
|
1841
|
+
const access = await entry.getRepositoryAccess().catch(() => void 0);
|
|
1842
|
+
const cloneUrl = access?.cloneUrl;
|
|
1843
|
+
if (!cloneUrl) throw new Error(`Repo template has no clone URL: repository access returned none${isList ? ` (repos[${index}])` : ""}.`);
|
|
1844
|
+
assertCloneUrl(cloneUrl);
|
|
1845
|
+
const repoDir = repoDirName(normalizeCloneUrl(cloneUrl));
|
|
1846
|
+
const other = seenDirs.get(repoDir);
|
|
1847
|
+
if (other) throw new Error(`Repo template: repositories ${other} and ${cloneUrl} would both clone into "${repoDir}"`);
|
|
1848
|
+
seenDirs.set(repoDir, cloneUrl);
|
|
1849
|
+
const token = access?.authorization?.token;
|
|
1850
|
+
const resolved = await resolveDefaultBranchHead(cloneUrl, token).catch(() => void 0);
|
|
1851
|
+
const sha = resolved && SHA_PATTERN.test(resolved) ? resolved : void 0;
|
|
1852
|
+
repos.push({
|
|
1774
1853
|
cloneUrl,
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1854
|
+
repoDir,
|
|
1855
|
+
token,
|
|
1856
|
+
tokenEnv: token ? isList ? `${BUILD_TOKEN_ENV}_${index}` : BUILD_TOKEN_ENV : void 0,
|
|
1857
|
+
sha,
|
|
1858
|
+
setupCommand: entry.setupCommand
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
const first = repos[0];
|
|
1862
|
+
const spec = buildRepoTemplateSpec({
|
|
1863
|
+
cloneUrl: first.cloneUrl,
|
|
1864
|
+
...first.sha ? { sha: first.sha } : {},
|
|
1865
|
+
...normalizeSetupCommands(first.setupCommand).length > 0 ? { setupCommand: first.setupCommand } : {},
|
|
1866
|
+
...buildEnv ? { buildEnv } : {},
|
|
1867
|
+
...options.cpuCount !== void 0 ? { cpuCount: options.cpuCount } : {},
|
|
1868
|
+
...options.memoryMB !== void 0 ? { memoryMB: options.memoryMB } : {},
|
|
1869
|
+
...options.workingDirectory !== void 0 ? { workingDirectory: trimTrailingSlashes(assertWorkingDirectory(options.workingDirectory)) } : {},
|
|
1870
|
+
...isList ? {
|
|
1871
|
+
repos: repos.map((repo) => ({
|
|
1872
|
+
cloneUrl: repo.cloneUrl,
|
|
1873
|
+
...repo.sha ? { sha: repo.sha } : {},
|
|
1874
|
+
...normalizeSetupCommands(repo.setupCommand).length > 0 ? { setupCommand: repo.setupCommand } : {}
|
|
1875
|
+
})),
|
|
1876
|
+
...options.workspaceSetupCommand !== void 0 ? { workspaceSetupCommand: options.workspaceSetupCommand } : {},
|
|
1877
|
+
...options.continueOnSetupFailure !== void 0 ? { continueOnSetupFailure: options.continueOnSetupFailure } : {}
|
|
1878
|
+
} : {}
|
|
1879
|
+
}, repos, options.continueOnSetupFailure ?? false);
|
|
1880
|
+
const sha = isList ? repos.every((repo) => repo.sha) ? listSha(repos.map((repo) => repo.sha)) : void 0 : first.sha;
|
|
1881
|
+
return {
|
|
1882
|
+
spec,
|
|
1782
1883
|
...sha ? { sha } : {}
|
|
1783
1884
|
};
|
|
1784
1885
|
}
|
|
@@ -1828,31 +1929,45 @@ function assertCloneUrl(cloneUrl) {
|
|
|
1828
1929
|
* REFERENCE — the token value never appears in the command string, and no
|
|
1829
1930
|
* credential is written to the build filesystem.
|
|
1830
1931
|
*/
|
|
1831
|
-
function gitAuthFlag() {
|
|
1832
|
-
return `-c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$${
|
|
1932
|
+
function gitAuthFlag(tokenEnv) {
|
|
1933
|
+
return `-c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$${tokenEnv}" | base64 -w0)"`;
|
|
1833
1934
|
}
|
|
1834
|
-
function buildRepoTemplateSpec(identity,
|
|
1835
|
-
const {
|
|
1836
|
-
const
|
|
1837
|
-
const repoDir = repoDirName(cloneUrl);
|
|
1838
|
-
const auth = token ? `${gitAuthFlag()} ` : "";
|
|
1935
|
+
function buildRepoTemplateSpec(identity, repos, continueOnFailure) {
|
|
1936
|
+
const { buildEnv, workingDirectory } = identity;
|
|
1937
|
+
const isList = identity.repos !== void 0;
|
|
1839
1938
|
let template = createDefaultMountableTemplate().template;
|
|
1840
1939
|
const env = { ...buildEnv };
|
|
1841
|
-
if (token) env[
|
|
1940
|
+
for (const repo of repos) if (repo.tokenEnv && repo.token) env[repo.tokenEnv] = repo.token;
|
|
1842
1941
|
if (Object.keys(env).length > 0) template = template.setEnvs(env);
|
|
1843
1942
|
if (workingDirectory) {
|
|
1844
1943
|
const dir = trimTrailingSlashes(workingDirectory);
|
|
1845
1944
|
template = template.runCmd(`mkdir -p "${dir}"`).setWorkdir(dir);
|
|
1846
1945
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1946
|
+
const ordered = [...repos.filter((repo) => !repo.token), ...repos.filter((repo) => repo.token)];
|
|
1947
|
+
for (const repo of ordered) {
|
|
1948
|
+
const { repoDir, sha, tokenEnv } = repo;
|
|
1949
|
+
const cloneUrl = normalizeCloneUrl(repo.cloneUrl);
|
|
1950
|
+
const auth = tokenEnv ? `${gitAuthFlag(tokenEnv)} ` : "";
|
|
1951
|
+
const setupCommands = normalizeSetupCommands(repo.setupCommand);
|
|
1952
|
+
template = template.runCmd(repoCloneCommand({
|
|
1953
|
+
cloneUrl,
|
|
1954
|
+
destination: repoDir,
|
|
1955
|
+
...tokenEnv ? { tokenEnv } : {}
|
|
1956
|
+
}));
|
|
1957
|
+
if (sha) template = template.runCmd(`git -C "${repoDir}" ${auth}fetch origin ${sha}`).runCmd(`git -C "${repoDir}" checkout ${sha}`);
|
|
1958
|
+
for (const command of setupCommands) template = template.runCmd(guardedSetupCommand({
|
|
1959
|
+
repoDir,
|
|
1960
|
+
command,
|
|
1961
|
+
continueOnFailure
|
|
1962
|
+
}));
|
|
1963
|
+
const content = setupMarkerContent(setupCommands);
|
|
1964
|
+
template = template.runCmd(isList ? setupMarkerCommand(content, repoSetupMarkerPath(repoDir)) : setupMarkerCommand(content));
|
|
1965
|
+
}
|
|
1966
|
+
if (isList) {
|
|
1967
|
+
const workspaceCommands = normalizeSetupCommands(identity.workspaceSetupCommand);
|
|
1968
|
+
for (const command of workspaceCommands) template = template.runCmd(command);
|
|
1969
|
+
template = template.runCmd(setupMarkerCommand(setupMarkerContent(workspaceCommands), WORKSPACE_SETUP_MARKER_PATH));
|
|
1970
|
+
}
|
|
1856
1971
|
return {
|
|
1857
1972
|
ref: repoTemplateRef(identity),
|
|
1858
1973
|
template,
|