@mastra/factory 0.2.2-alpha.0 → 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.
Files changed (59) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/auth.d.ts +2 -2
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +22 -2
  5. package/dist/auth.js.map +1 -1
  6. package/dist/factory.d.ts.map +1 -1
  7. package/dist/factory.js +411 -244
  8. package/dist/factory.js.map +1 -1
  9. package/dist/index.js +411 -244
  10. package/dist/index.js.map +1 -1
  11. package/dist/integrations/github/integration.js +85 -37
  12. package/dist/integrations/github/integration.js.map +1 -1
  13. package/dist/integrations/github/routes.d.ts.map +1 -1
  14. package/dist/integrations/github/routes.js +85 -37
  15. package/dist/integrations/github/routes.js.map +1 -1
  16. package/dist/integrations/github/sandbox.d.ts +2 -0
  17. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  18. package/dist/integrations/github/sandbox.js +28 -8
  19. package/dist/integrations/github/sandbox.js.map +1 -1
  20. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  21. package/dist/integrations/platform/github/integration.js +119 -39
  22. package/dist/integrations/platform/github/integration.js.map +1 -1
  23. package/dist/routes/config.d.ts.map +1 -1
  24. package/dist/routes/config.js +1 -4
  25. package/dist/routes/config.js.map +1 -1
  26. package/dist/routes/surface.js +1 -4
  27. package/dist/routes/surface.js.map +1 -1
  28. package/dist/routes/work-items.js.map +1 -1
  29. package/dist/rules/binding-context.js.map +1 -1
  30. package/dist/rules/dispatcher.d.ts.map +1 -1
  31. package/dist/rules/dispatcher.js +7 -1
  32. package/dist/rules/dispatcher.js.map +1 -1
  33. package/dist/rules/processor.js.map +1 -1
  34. package/dist/rules/tools.js.map +1 -1
  35. package/dist/spa-static.d.ts.map +1 -1
  36. package/dist/spa-static.js +1 -0
  37. package/dist/spa-static.js.map +1 -1
  38. package/dist/storage/domains/source-control/base.d.ts +9 -0
  39. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  40. package/dist/storage/domains/source-control/base.js +4 -0
  41. package/dist/storage/domains/source-control/base.js.map +1 -1
  42. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  43. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  44. package/dist/storage/domains/source-control/inmemory.js +4 -0
  45. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  46. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  47. package/dist/storage/domains/work-items/base.js +20 -4
  48. package/dist/storage/domains/work-items/base.js.map +1 -1
  49. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  50. package/dist/timing.d.ts +15 -0
  51. package/dist/timing.d.ts.map +1 -0
  52. package/dist/timing.js +27 -0
  53. package/dist/timing.js.map +1 -0
  54. package/dist/workspace.d.ts +2 -3
  55. package/dist/workspace.d.ts.map +1 -1
  56. package/dist/workspace.js +104 -65
  57. package/dist/workspace.js.map +1 -1
  58. package/factory-skills/factory-review/SKILL.md +9 -2
  59. package/package.json +8 -8
@@ -192,8 +192,23 @@ async function teardownProjectSandbox(options) {
192
192
  function shellQuote(value) {
193
193
  return `'` + value.split(`'`).join(`'\\''`) + `'`;
194
194
  }
195
- async function sh(sandbox, script) {
196
- return sandbox.executeCommand("sh", ["-c", script]);
195
+ var DEFAULT_COMMAND_TIMEOUT_MS = 15 * 6e4;
196
+ var CHECKOUT_COMMAND_TIMEOUT_MS = 5 * 6e4;
197
+ async function sh(sandbox, script, options = {}) {
198
+ const timeoutMs = options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
199
+ let timer;
200
+ const hangGuard = new Promise((_, reject) => {
201
+ timer = setTimeout(() => {
202
+ const phase = options.phase ? ` during ${options.phase}` : "";
203
+ reject(new Error(`Sandbox command timed out after ${Math.round(timeoutMs / 1e3)}s${phase}.`));
204
+ }, timeoutMs);
205
+ timer.unref?.();
206
+ });
207
+ try {
208
+ return await Promise.race([sandbox.executeCommand("sh", ["-c", script], { timeout: timeoutMs }), hangGuard]);
209
+ } finally {
210
+ clearTimeout(timer);
211
+ }
197
212
  }
198
213
  var MaterializeError = class extends Error {
199
214
  constructor(message, code) {
@@ -230,7 +245,8 @@ async function materializeRepo(options) {
230
245
  );
231
246
  }
232
247
  const authUrl = tokenUrl(repo, token);
233
- const alreadyMaterialized = Boolean(sandboxRow.materializedAt) || await hasExistingCheckout(sandbox, workdir, repo);
248
+ const alreadyMaterialized = await hasExistingCheckout(sandbox, workdir, repo);
249
+ let succeeded = false;
234
250
  try {
235
251
  if (!alreadyMaterialized) {
236
252
  reportProgress(onProgress, {
@@ -239,7 +255,8 @@ async function materializeRepo(options) {
239
255
  });
240
256
  const clone = await sh(
241
257
  sandbox,
242
- `git clone --depth=1 --single-branch --branch ${shellQuote(repoInfo.defaultBranch)} ${shellQuote(authUrl)} ${shellQuote(workdir)}`
258
+ `git clone --depth=1 --single-branch --branch ${shellQuote(repoInfo.defaultBranch)} ${shellQuote(authUrl)} ${shellQuote(workdir)}`,
259
+ { phase: "repository clone" }
243
260
  );
244
261
  if (clone.exitCode !== 0) {
245
262
  throw classifyGitFailure(clone, "clone-failed");
@@ -250,13 +267,14 @@ async function materializeRepo(options) {
250
267
  if (setUrl.exitCode !== 0) {
251
268
  throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr}`, "pull-failed");
252
269
  }
253
- const pull = await sh(sandbox, `git -C ${shellQuote(workdir)} pull --ff-only`);
270
+ const pull = await sh(sandbox, `git -C ${shellQuote(workdir)} pull --ff-only`, { phase: "repository pull" });
254
271
  if (pull.exitCode !== 0) {
255
272
  throw classifyGitFailure(pull, "pull-failed");
256
273
  }
257
274
  }
275
+ succeeded = true;
258
276
  } finally {
259
- await scrubRemote(sandbox, workdir, repo, alreadyMaterialized);
277
+ await scrubRemote(sandbox, workdir, repo, succeeded);
260
278
  }
261
279
  reportProgress(onProgress, { phase: "finalizing", message: "Finalizing workspace\u2026" });
262
280
  await storage.markMaterialized({ id: sandboxRow.id });
@@ -1155,43 +1173,56 @@ function buildGithubRoutes(options) {
1155
1173
  handler: async (c) => {
1156
1174
  const resolved = await resolveOrgTenant(loose(c), auth);
1157
1175
  if ("response" in resolved) return resolved.response;
1158
- const installs = await github.sourceControlStorage.installations.list({ orgId: resolved.tenant.orgId });
1176
+ const orgId = resolved.tenant.orgId;
1177
+ const installs = await github.sourceControlStorage.installations.list({ orgId });
1159
1178
  const query = (c.req.query("q") ?? "").toLowerCase();
1160
- const repos = [];
1179
+ const listed = await Promise.all(
1180
+ installs.map(async (inst) => {
1181
+ try {
1182
+ return { inst, list: await github.listInstallationRepos(Number(inst.externalId)) };
1183
+ } catch (err) {
1184
+ if (err.status !== 404) throw err;
1185
+ console.error(`[Mastra Factory] pruning stale GitHub installation ${inst.externalId} (404 from GitHub)`);
1186
+ await github.sourceControlStorage.installations.delete({ orgId, id: inst.id });
1187
+ return { inst, list: [] };
1188
+ }
1189
+ })
1190
+ );
1191
+ const matches = [];
1161
1192
  const seenRepositoryIds = /* @__PURE__ */ new Set();
1162
- for (const inst of installs) {
1163
- let list;
1164
- try {
1165
- list = await github.listInstallationRepos(Number(inst.externalId));
1166
- } catch (err) {
1167
- if (err.status !== 404) throw err;
1168
- console.error(`[Mastra Factory] pruning stale GitHub installation ${inst.externalId} (404 from GitHub)`);
1169
- await github.sourceControlStorage.installations.delete({ orgId: resolved.tenant.orgId, id: inst.id });
1170
- continue;
1171
- }
1193
+ for (const { inst, list } of listed) {
1172
1194
  for (const repo of list) {
1173
1195
  if (query && !repo.fullName.toLowerCase().includes(query)) continue;
1174
1196
  if (seenRepositoryIds.has(repo.id)) continue;
1175
1197
  seenRepositoryIds.add(repo.id);
1176
- const repository = await github.sourceControlStorage.repositories.upsert({
1177
- orgId: resolved.tenant.orgId,
1178
- input: {
1179
- installationId: inst.id,
1180
- externalId: repo.id.toString(),
1181
- slug: repo.fullName,
1182
- defaultBranch: isValidGitRef2(repo.defaultBranch) ? repo.defaultBranch : "main",
1183
- providerMetadata: { private: repo.private, owner: repo.owner }
1184
- }
1185
- });
1186
- repos.push({
1187
- ...repo,
1188
- installationStorageId: inst.id,
1189
- repositoryStorageId: repository.id,
1190
- sandboxProvider: fleet.provider,
1191
- sandboxWorkdir: fleet.computeWorkdir(repo.fullName)
1192
- });
1198
+ matches.push({ inst, repo });
1193
1199
  }
1194
1200
  }
1201
+ const repos = new Array(matches.length);
1202
+ const upsertConcurrency = 10;
1203
+ for (let start = 0; start < matches.length; start += upsertConcurrency) {
1204
+ await Promise.all(
1205
+ matches.slice(start, start + upsertConcurrency).map(async ({ inst, repo }, offset) => {
1206
+ const repository = await github.sourceControlStorage.repositories.upsert({
1207
+ orgId,
1208
+ input: {
1209
+ installationId: inst.id,
1210
+ externalId: repo.id.toString(),
1211
+ slug: repo.fullName,
1212
+ defaultBranch: isValidGitRef2(repo.defaultBranch) ? repo.defaultBranch : "main",
1213
+ providerMetadata: { private: repo.private, owner: repo.owner }
1214
+ }
1215
+ });
1216
+ repos[start + offset] = {
1217
+ ...repo,
1218
+ installationStorageId: inst.id,
1219
+ repositoryStorageId: repository.id,
1220
+ sandboxProvider: fleet.provider,
1221
+ sandboxWorkdir: fleet.computeWorkdir(repo.fullName)
1222
+ };
1223
+ })
1224
+ );
1225
+ }
1195
1226
  return c.json({ repos });
1196
1227
  }
1197
1228
  })
@@ -1522,8 +1553,25 @@ async function loadOrCreateSandboxRow(github, project, userId) {
1522
1553
  return github.sourceControlStorage.sandboxes.getOrCreate({ projectRepository: project, userId });
1523
1554
  }
1524
1555
  async function prepareProject(options) {
1525
- const { github, fleet, project, userId, onProgress } = options;
1526
- const sandboxRow = await loadOrCreateSandboxRow(github, project, userId);
1556
+ const { github, fleet, userId, onProgress } = options;
1557
+ let project = options.project;
1558
+ if (project.sandboxProvider !== fleet.provider) {
1559
+ const sandboxWorkdir = fleet.computeWorkdir(project.repository.slug);
1560
+ await github.sourceControlStorage.projectRepositories.update({
1561
+ orgId: project.installation.orgId,
1562
+ id: project.id,
1563
+ input: { sandboxProvider: fleet.provider, sandboxWorkdir }
1564
+ });
1565
+ project = { ...project, sandboxProvider: fleet.provider, sandboxWorkdir };
1566
+ }
1567
+ let sandboxRow = await loadOrCreateSandboxRow(github, project, userId);
1568
+ if (sandboxRow.sandboxWorkdir !== project.sandboxWorkdir) {
1569
+ await github.sourceControlStorage.sandboxes.setWorkdir({
1570
+ id: sandboxRow.id,
1571
+ sandboxWorkdir: project.sandboxWorkdir
1572
+ });
1573
+ sandboxRow = { ...sandboxRow, sandboxWorkdir: project.sandboxWorkdir, materializedAt: null };
1574
+ }
1527
1575
  const access = await github.versionControl.getRepositoryAccess({
1528
1576
  orgId: project.installation.orgId,
1529
1577
  repositoryId: project.repository.id