@mastra/factory 0.2.1 → 0.2.2-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/CHANGELOG.md +34 -0
- package/dist/auth.d.ts +2 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +22 -2
- package/dist/auth.js.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +411 -244
- package/dist/factory.js.map +1 -1
- package/dist/index.js +411 -244
- package/dist/index.js.map +1 -1
- package/dist/integrations/github/integration.js +85 -37
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +85 -37
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +28 -8
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +119 -39
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +1 -4
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/surface.js +1 -4
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +7 -1
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/tools.js.map +1 -1
- package/dist/spa-static.d.ts.map +1 -1
- package/dist/spa-static.js +1 -0
- package/dist/spa-static.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +9 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +4 -0
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +4 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +20 -4
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/storage/domains/work-items/metrics.js.map +1 -1
- package/dist/timing.d.ts +15 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +27 -0
- package/dist/timing.js.map +1 -0
- package/dist/workspace.d.ts +2 -3
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +104 -65
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-review/SKILL.md +9 -2
- package/package.json +7 -7
|
@@ -113,8 +113,23 @@ async function teardownProjectSandbox(options) {
|
|
|
113
113
|
function shellQuote(value) {
|
|
114
114
|
return `'` + value.split(`'`).join(`'\\''`) + `'`;
|
|
115
115
|
}
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
var DEFAULT_COMMAND_TIMEOUT_MS = 15 * 6e4;
|
|
117
|
+
var CHECKOUT_COMMAND_TIMEOUT_MS = 5 * 6e4;
|
|
118
|
+
async function sh(sandbox, script, options = {}) {
|
|
119
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
|
|
120
|
+
let timer;
|
|
121
|
+
const hangGuard = new Promise((_, reject) => {
|
|
122
|
+
timer = setTimeout(() => {
|
|
123
|
+
const phase = options.phase ? ` during ${options.phase}` : "";
|
|
124
|
+
reject(new Error(`Sandbox command timed out after ${Math.round(timeoutMs / 1e3)}s${phase}.`));
|
|
125
|
+
}, timeoutMs);
|
|
126
|
+
timer.unref?.();
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
return await Promise.race([sandbox.executeCommand("sh", ["-c", script], { timeout: timeoutMs }), hangGuard]);
|
|
130
|
+
} finally {
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
}
|
|
118
133
|
}
|
|
119
134
|
var MaterializeError = class extends Error {
|
|
120
135
|
constructor(message, code) {
|
|
@@ -151,7 +166,8 @@ async function materializeRepo(options) {
|
|
|
151
166
|
);
|
|
152
167
|
}
|
|
153
168
|
const authUrl = tokenUrl(repo, token);
|
|
154
|
-
const alreadyMaterialized =
|
|
169
|
+
const alreadyMaterialized = await hasExistingCheckout(sandbox, workdir, repo);
|
|
170
|
+
let succeeded = false;
|
|
155
171
|
try {
|
|
156
172
|
if (!alreadyMaterialized) {
|
|
157
173
|
reportProgress(onProgress, {
|
|
@@ -160,7 +176,8 @@ async function materializeRepo(options) {
|
|
|
160
176
|
});
|
|
161
177
|
const clone = await sh(
|
|
162
178
|
sandbox,
|
|
163
|
-
`git clone --depth=1 --single-branch --branch ${shellQuote(repoInfo.defaultBranch)} ${shellQuote(authUrl)} ${shellQuote(workdir)}
|
|
179
|
+
`git clone --depth=1 --single-branch --branch ${shellQuote(repoInfo.defaultBranch)} ${shellQuote(authUrl)} ${shellQuote(workdir)}`,
|
|
180
|
+
{ phase: "repository clone" }
|
|
164
181
|
);
|
|
165
182
|
if (clone.exitCode !== 0) {
|
|
166
183
|
throw classifyGitFailure(clone, "clone-failed");
|
|
@@ -171,13 +188,14 @@ async function materializeRepo(options) {
|
|
|
171
188
|
if (setUrl.exitCode !== 0) {
|
|
172
189
|
throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr}`, "pull-failed");
|
|
173
190
|
}
|
|
174
|
-
const pull = await sh(sandbox, `git -C ${shellQuote(workdir)} pull --ff-only
|
|
191
|
+
const pull = await sh(sandbox, `git -C ${shellQuote(workdir)} pull --ff-only`, { phase: "repository pull" });
|
|
175
192
|
if (pull.exitCode !== 0) {
|
|
176
193
|
throw classifyGitFailure(pull, "pull-failed");
|
|
177
194
|
}
|
|
178
195
|
}
|
|
196
|
+
succeeded = true;
|
|
179
197
|
} finally {
|
|
180
|
-
await scrubRemote(sandbox, workdir, repo,
|
|
198
|
+
await scrubRemote(sandbox, workdir, repo, succeeded);
|
|
181
199
|
}
|
|
182
200
|
reportProgress(onProgress, { phase: "finalizing", message: "Finalizing workspace\u2026" });
|
|
183
201
|
await storage.markMaterialized({ id: sandboxRow.id });
|
|
@@ -1076,43 +1094,56 @@ function buildGithubRoutes(options) {
|
|
|
1076
1094
|
handler: async (c) => {
|
|
1077
1095
|
const resolved = await resolveOrgTenant(loose(c), auth);
|
|
1078
1096
|
if ("response" in resolved) return resolved.response;
|
|
1079
|
-
const
|
|
1097
|
+
const orgId = resolved.tenant.orgId;
|
|
1098
|
+
const installs = await github.sourceControlStorage.installations.list({ orgId });
|
|
1080
1099
|
const query = (c.req.query("q") ?? "").toLowerCase();
|
|
1081
|
-
const
|
|
1100
|
+
const listed = await Promise.all(
|
|
1101
|
+
installs.map(async (inst) => {
|
|
1102
|
+
try {
|
|
1103
|
+
return { inst, list: await github.listInstallationRepos(Number(inst.externalId)) };
|
|
1104
|
+
} catch (err) {
|
|
1105
|
+
if (err.status !== 404) throw err;
|
|
1106
|
+
console.error(`[Mastra Factory] pruning stale GitHub installation ${inst.externalId} (404 from GitHub)`);
|
|
1107
|
+
await github.sourceControlStorage.installations.delete({ orgId, id: inst.id });
|
|
1108
|
+
return { inst, list: [] };
|
|
1109
|
+
}
|
|
1110
|
+
})
|
|
1111
|
+
);
|
|
1112
|
+
const matches = [];
|
|
1082
1113
|
const seenRepositoryIds = /* @__PURE__ */ new Set();
|
|
1083
|
-
for (const inst of
|
|
1084
|
-
let list;
|
|
1085
|
-
try {
|
|
1086
|
-
list = await github.listInstallationRepos(Number(inst.externalId));
|
|
1087
|
-
} catch (err) {
|
|
1088
|
-
if (err.status !== 404) throw err;
|
|
1089
|
-
console.error(`[Mastra Factory] pruning stale GitHub installation ${inst.externalId} (404 from GitHub)`);
|
|
1090
|
-
await github.sourceControlStorage.installations.delete({ orgId: resolved.tenant.orgId, id: inst.id });
|
|
1091
|
-
continue;
|
|
1092
|
-
}
|
|
1114
|
+
for (const { inst, list } of listed) {
|
|
1093
1115
|
for (const repo of list) {
|
|
1094
1116
|
if (query && !repo.fullName.toLowerCase().includes(query)) continue;
|
|
1095
1117
|
if (seenRepositoryIds.has(repo.id)) continue;
|
|
1096
1118
|
seenRepositoryIds.add(repo.id);
|
|
1097
|
-
|
|
1098
|
-
orgId: resolved.tenant.orgId,
|
|
1099
|
-
input: {
|
|
1100
|
-
installationId: inst.id,
|
|
1101
|
-
externalId: repo.id.toString(),
|
|
1102
|
-
slug: repo.fullName,
|
|
1103
|
-
defaultBranch: isValidGitRef2(repo.defaultBranch) ? repo.defaultBranch : "main",
|
|
1104
|
-
providerMetadata: { private: repo.private, owner: repo.owner }
|
|
1105
|
-
}
|
|
1106
|
-
});
|
|
1107
|
-
repos.push({
|
|
1108
|
-
...repo,
|
|
1109
|
-
installationStorageId: inst.id,
|
|
1110
|
-
repositoryStorageId: repository.id,
|
|
1111
|
-
sandboxProvider: fleet.provider,
|
|
1112
|
-
sandboxWorkdir: fleet.computeWorkdir(repo.fullName)
|
|
1113
|
-
});
|
|
1119
|
+
matches.push({ inst, repo });
|
|
1114
1120
|
}
|
|
1115
1121
|
}
|
|
1122
|
+
const repos = new Array(matches.length);
|
|
1123
|
+
const upsertConcurrency = 10;
|
|
1124
|
+
for (let start = 0; start < matches.length; start += upsertConcurrency) {
|
|
1125
|
+
await Promise.all(
|
|
1126
|
+
matches.slice(start, start + upsertConcurrency).map(async ({ inst, repo }, offset) => {
|
|
1127
|
+
const repository = await github.sourceControlStorage.repositories.upsert({
|
|
1128
|
+
orgId,
|
|
1129
|
+
input: {
|
|
1130
|
+
installationId: inst.id,
|
|
1131
|
+
externalId: repo.id.toString(),
|
|
1132
|
+
slug: repo.fullName,
|
|
1133
|
+
defaultBranch: isValidGitRef2(repo.defaultBranch) ? repo.defaultBranch : "main",
|
|
1134
|
+
providerMetadata: { private: repo.private, owner: repo.owner }
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
repos[start + offset] = {
|
|
1138
|
+
...repo,
|
|
1139
|
+
installationStorageId: inst.id,
|
|
1140
|
+
repositoryStorageId: repository.id,
|
|
1141
|
+
sandboxProvider: fleet.provider,
|
|
1142
|
+
sandboxWorkdir: fleet.computeWorkdir(repo.fullName)
|
|
1143
|
+
};
|
|
1144
|
+
})
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1116
1147
|
return c.json({ repos });
|
|
1117
1148
|
}
|
|
1118
1149
|
})
|
|
@@ -1443,8 +1474,25 @@ async function loadOrCreateSandboxRow(github, project, userId) {
|
|
|
1443
1474
|
return github.sourceControlStorage.sandboxes.getOrCreate({ projectRepository: project, userId });
|
|
1444
1475
|
}
|
|
1445
1476
|
async function prepareProject(options) {
|
|
1446
|
-
const { github, fleet,
|
|
1447
|
-
|
|
1477
|
+
const { github, fleet, userId, onProgress } = options;
|
|
1478
|
+
let project = options.project;
|
|
1479
|
+
if (project.sandboxProvider !== fleet.provider) {
|
|
1480
|
+
const sandboxWorkdir = fleet.computeWorkdir(project.repository.slug);
|
|
1481
|
+
await github.sourceControlStorage.projectRepositories.update({
|
|
1482
|
+
orgId: project.installation.orgId,
|
|
1483
|
+
id: project.id,
|
|
1484
|
+
input: { sandboxProvider: fleet.provider, sandboxWorkdir }
|
|
1485
|
+
});
|
|
1486
|
+
project = { ...project, sandboxProvider: fleet.provider, sandboxWorkdir };
|
|
1487
|
+
}
|
|
1488
|
+
let sandboxRow = await loadOrCreateSandboxRow(github, project, userId);
|
|
1489
|
+
if (sandboxRow.sandboxWorkdir !== project.sandboxWorkdir) {
|
|
1490
|
+
await github.sourceControlStorage.sandboxes.setWorkdir({
|
|
1491
|
+
id: sandboxRow.id,
|
|
1492
|
+
sandboxWorkdir: project.sandboxWorkdir
|
|
1493
|
+
});
|
|
1494
|
+
sandboxRow = { ...sandboxRow, sandboxWorkdir: project.sandboxWorkdir, materializedAt: null };
|
|
1495
|
+
}
|
|
1448
1496
|
const access = await github.versionControl.getRepositoryAccess({
|
|
1449
1497
|
orgId: project.installation.orgId,
|
|
1450
1498
|
repositoryId: project.repository.id
|
|
@@ -3015,6 +3063,17 @@ function retryDelay(error, fallbackMs) {
|
|
|
3015
3063
|
// src/integrations/platform/github/integration.ts
|
|
3016
3064
|
var PAGE_SIZE = 30;
|
|
3017
3065
|
var API_PREFIX2 = "/v1/server";
|
|
3066
|
+
var INSTALLATION_REPOS_CACHE_TTL_MS = 3e4;
|
|
3067
|
+
var REPOSITORY_ACCESS_CACHE_TTL_MS = 5 * 6e4;
|
|
3068
|
+
var MAX_CACHE_ENTRIES = 1e3;
|
|
3069
|
+
function setBounded(cache, key, value) {
|
|
3070
|
+
cache.delete(key);
|
|
3071
|
+
cache.set(key, value);
|
|
3072
|
+
if (cache.size > MAX_CACHE_ENTRIES) {
|
|
3073
|
+
const oldest = cache.keys().next().value;
|
|
3074
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3018
3077
|
var REPOSITORY_TOKEN_PERMISSIONS = {
|
|
3019
3078
|
contents: "write",
|
|
3020
3079
|
issues: "write",
|
|
@@ -3034,6 +3093,10 @@ var PlatformGithubIntegration = class {
|
|
|
3034
3093
|
#pollingIntervalMs;
|
|
3035
3094
|
#storage;
|
|
3036
3095
|
#integrationStorage;
|
|
3096
|
+
/** installationId → cached repository listing (TTL-bounded). */
|
|
3097
|
+
#installationReposCache = /* @__PURE__ */ new Map();
|
|
3098
|
+
/** `orgId:repositoryId` → cached repository access (TTL-bounded). */
|
|
3099
|
+
#repositoryAccessCache = /* @__PURE__ */ new Map();
|
|
3037
3100
|
intake = {
|
|
3038
3101
|
resolveIntakeDispatch: (input) => this.#resolveIntakeDispatch(input),
|
|
3039
3102
|
listSources: async ({ orgId, userId }) => {
|
|
@@ -3164,6 +3227,10 @@ var PlatformGithubIntegration = class {
|
|
|
3164
3227
|
)
|
|
3165
3228
|
),
|
|
3166
3229
|
getRepositoryAccess: async ({ orgId, repositoryId }) => {
|
|
3230
|
+
const cacheKey = `${orgId}:${repositoryId}`;
|
|
3231
|
+
const cached = this.#repositoryAccessCache.get(cacheKey);
|
|
3232
|
+
if (cached && cached.expiresAt > Date.now()) return cached.access;
|
|
3233
|
+
this.#repositoryAccessCache.delete(cacheKey);
|
|
3167
3234
|
const repository = await this.storage.repositories.get({ orgId, id: repositoryId });
|
|
3168
3235
|
if (!repository) throw new Error("Version-control repository not found.");
|
|
3169
3236
|
const cloneUrl = `https://github.com/${repository.slug}.git`;
|
|
@@ -3177,10 +3244,15 @@ var PlatformGithubIntegration = class {
|
|
|
3177
3244
|
`${API_PREFIX2}/github-app/installations/${installationId}/token`,
|
|
3178
3245
|
{ repositories: [repositoryName], permissions: REPOSITORY_TOKEN_PERMISSIONS }
|
|
3179
3246
|
);
|
|
3180
|
-
|
|
3247
|
+
const access = {
|
|
3181
3248
|
cloneUrl,
|
|
3182
3249
|
authorization: { scheme: "bearer", token: token.token }
|
|
3183
3250
|
};
|
|
3251
|
+
setBounded(this.#repositoryAccessCache, cacheKey, {
|
|
3252
|
+
access,
|
|
3253
|
+
expiresAt: Date.now() + REPOSITORY_ACCESS_CACHE_TTL_MS
|
|
3254
|
+
});
|
|
3255
|
+
return access;
|
|
3184
3256
|
},
|
|
3185
3257
|
listPullRequests: (input) => this.#listPullRequests(input),
|
|
3186
3258
|
getPullRequest: (input) => this.#getPullRequest(input),
|
|
@@ -3475,8 +3547,16 @@ var PlatformGithubIntegration = class {
|
|
|
3475
3547
|
}
|
|
3476
3548
|
}
|
|
3477
3549
|
async listInstallationRepos(installationId) {
|
|
3550
|
+
const cached = this.#installationReposCache.get(installationId);
|
|
3551
|
+
if (cached && cached.expiresAt > Date.now()) return cached.repos;
|
|
3552
|
+
this.#installationReposCache.delete(installationId);
|
|
3478
3553
|
const result = await this.#client.request("GET", `${API_PREFIX2}/github-app/installations/${installationId}/repositories`);
|
|
3479
|
-
|
|
3554
|
+
const repos = result.repositories.map((repository) => ({ ...repository, installationId }));
|
|
3555
|
+
setBounded(this.#installationReposCache, installationId, {
|
|
3556
|
+
repos,
|
|
3557
|
+
expiresAt: Date.now() + INSTALLATION_REPOS_CACHE_TTL_MS
|
|
3558
|
+
});
|
|
3559
|
+
return repos;
|
|
3480
3560
|
}
|
|
3481
3561
|
async mintInstallationToken(installationId) {
|
|
3482
3562
|
const repositories = await this.listInstallationRepos(installationId);
|