@mastra/factory 0.21.1-alpha.2 → 0.22.0-alpha.4
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/capabilities/version-control.d.ts +13 -8
- package/dist/capabilities/version-control.d.ts.map +1 -1
- package/dist/environment/build-runner.d.ts +72 -0
- package/dist/environment/build-runner.d.ts.map +1 -0
- package/dist/environment/build-runner.js +131 -0
- package/dist/environment/build-runner.js.map +1 -0
- package/dist/environment/build-schedule.d.ts +40 -0
- package/dist/environment/build-schedule.d.ts.map +1 -0
- package/dist/environment/build-schedule.js +94 -0
- package/dist/environment/build-schedule.js.map +1 -0
- package/dist/environment/build-workflow.d.ts +62 -0
- package/dist/environment/build-workflow.d.ts.map +1 -0
- package/dist/environment/build-workflow.js +149 -0
- package/dist/environment/build-workflow.js.map +1 -0
- package/dist/environment/build.d.ts +111 -0
- package/dist/environment/build.d.ts.map +1 -0
- package/dist/environment/build.js +344 -0
- package/dist/environment/build.js.map +1 -0
- package/dist/environment/heads.d.ts +36 -0
- package/dist/environment/heads.d.ts.map +1 -0
- package/dist/environment/heads.js +60 -0
- package/dist/environment/heads.js.map +1 -0
- package/dist/factory.d.ts +18 -4
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +89 -15
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/integrations/base.d.ts +17 -2
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/commits.d.ts +11 -0
- package/dist/integrations/github/commits.d.ts.map +1 -1
- package/dist/integrations/github/commits.js +20 -1
- package/dist/integrations/github/commits.js.map +1 -1
- package/dist/integrations/github/config.d.ts +3 -3
- package/dist/integrations/github/config.d.ts.map +1 -1
- package/dist/integrations/github/config.js +1 -1
- package/dist/integrations/github/config.js.map +1 -1
- package/dist/integrations/github/routes.d.ts +2 -2
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +6 -5
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts +3 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +31 -1
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +35 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +203 -2
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
- package/dist/integrations/github/session-subscriptions.js +117 -62
- package/dist/integrations/github/session-subscriptions.js.map +1 -1
- package/dist/integrations/github/token-refresh.d.ts +7 -0
- package/dist/integrations/github/token-refresh.d.ts.map +1 -1
- package/dist/integrations/github/token-refresh.js.map +1 -1
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/gitlab/agent-tools.js +1 -1
- package/dist/integrations/gitlab/routes.d.ts +2 -2
- package/dist/integrations/gitlab/routes.d.ts.map +1 -1
- package/dist/integrations/gitlab/routes.js.map +1 -1
- package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -1
- package/dist/integrations/gitlab/session-subscriptions.js +8 -7
- package/dist/integrations/gitlab/session-subscriptions.js.map +1 -1
- package/dist/integrations/incidentio/agent-tools.js +1 -1
- package/dist/integrations/jira/agent-tools.js +1 -1
- package/dist/integrations/linear/agent-tools.js +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +46 -2
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/jira/agent-tools.js +1 -1
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +15 -16
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/integrations/subscription-session.js +1 -1
- package/dist/packages/_internals/workspace/dist/index.js +21 -1
- package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
- package/dist/routes/contracts.d.ts +386 -2
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/contracts.js +166 -1
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/fs.d.ts +25 -0
- package/dist/routes/fs.d.ts.map +1 -1
- package/dist/routes/fs.js +142 -46
- package/dist/routes/fs.js.map +1 -1
- package/dist/routes/projects.d.ts +6 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +344 -1
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/source-control-sessions.d.ts +2 -0
- package/dist/routes/source-control-sessions.d.ts.map +1 -1
- package/dist/routes/source-control-sessions.js +36 -1
- package/dist/routes/source-control-sessions.js.map +1 -1
- package/dist/routes/supervisor.js +1 -1
- package/dist/routes/surface.d.ts +6 -3
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +21 -34
- package/dist/routes/surface.js.map +1 -1
- package/dist/rules/binding-context.d.ts +1 -1
- package/dist/rules/binding-context.d.ts.map +1 -1
- package/dist/rules/binding-context.js +1 -1
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/review-source-tool.js +1 -1
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +7 -2
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/tools.js +1 -1
- package/dist/sandbox/callback-factory-sandbox.d.ts +15 -0
- package/dist/sandbox/callback-factory-sandbox.d.ts.map +1 -0
- package/dist/sandbox/callback-factory-sandbox.js +22 -0
- package/dist/sandbox/callback-factory-sandbox.js.map +1 -0
- package/dist/sandbox/local-factory-sandbox.d.ts +20 -0
- package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -0
- package/dist/sandbox/local-factory-sandbox.js +27 -0
- package/dist/sandbox/local-factory-sandbox.js.map +1 -0
- package/dist/sandbox/session-retirement.d.ts.map +1 -1
- package/dist/sandbox/session-retirement.js +23 -3
- package/dist/sandbox/session-retirement.js.map +1 -1
- package/dist/sandbox/session-sandbox.d.ts +82 -40
- package/dist/sandbox/session-sandbox.d.ts.map +1 -1
- package/dist/sandbox/session-sandbox.js +130 -13
- package/dist/sandbox/session-sandbox.js.map +1 -1
- package/dist/sandbox/workdir.d.ts +10 -0
- package/dist/sandbox/workdir.d.ts.map +1 -1
- package/dist/sandbox/workdir.js +10 -1
- package/dist/sandbox/workdir.js.map +1 -1
- package/dist/secret-encryption.d.ts.map +1 -1
- package/dist/secret-encryption.js +12 -1
- package/dist/secret-encryption.js.map +1 -1
- package/dist/session/environment-repositories.d.ts +31 -0
- package/dist/session/environment-repositories.d.ts.map +1 -0
- package/dist/session/environment-repositories.js +89 -0
- package/dist/session/environment-repositories.js.map +1 -0
- package/dist/session/environment-state-processor.d.ts +59 -0
- package/dist/session/environment-state-processor.d.ts.map +1 -0
- package/dist/session/environment-state-processor.js +105 -0
- package/dist/session/environment-state-processor.js.map +1 -0
- package/dist/session/factory-session.d.ts +26 -35
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +34 -71
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/filesystem-capture.d.ts +13 -0
- package/dist/session/filesystem-capture.d.ts.map +1 -1
- package/dist/session/filesystem-capture.js +73 -14
- package/dist/session/filesystem-capture.js.map +1 -1
- package/dist/session/source-control-tools.d.ts +1 -1
- package/dist/session/source-control-tools.d.ts.map +1 -1
- package/dist/session/source-control-tools.js +144 -71
- package/dist/session/source-control-tools.js.map +1 -1
- package/dist/spa-static.js +2 -2
- package/dist/storage/domains/audit/agent-audit.d.ts +6 -1
- package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
- package/dist/storage/domains/audit/agent-audit.js +31 -13
- package/dist/storage/domains/audit/agent-audit.js.map +1 -1
- package/dist/storage/domains/custom-providers/base.d.ts.map +1 -1
- package/dist/storage/domains/custom-providers/base.js +13 -2
- package/dist/storage/domains/custom-providers/base.js.map +1 -1
- package/dist/storage/domains/projects/base.d.ts +115 -2
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +219 -12
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +123 -7
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +406 -33
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +19 -6
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +90 -2
- 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 +12 -5
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/supervisor/read-tools.js +1 -1
- package/dist/supervisor/write-tools.js +1 -1
- package/dist/workspace.d.ts +50 -7
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +344 -24
- package/dist/workspace.js.map +1 -1
- package/package.json +7 -7
package/dist/routes/projects.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { redactCredentials } from "../environment/build.js";
|
|
2
|
+
import { ensureSchedule, invalidCronMessage, readSchedule, scheduleIdFor } from "../environment/build-schedule.js";
|
|
1
3
|
import { ACTIVE_RUN_BINDING_STAGES } from "../storage/domains/work-items/base.js";
|
|
2
4
|
import { Route } from "./route.js";
|
|
3
5
|
import { FACTORY_ROUTE_CONTRACTS } from "./contracts.js";
|
|
4
6
|
import { registerApiRoute } from "@mastra/core/server";
|
|
7
|
+
import { describeFactorySandbox, normalizeFactorySandboxSettings } from "@mastra/core/workspace";
|
|
8
|
+
import { MastraError } from "@mastra/core/error";
|
|
5
9
|
//#region src/routes/projects.ts
|
|
6
10
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7
11
|
const MAX_NAME_LENGTH = 200;
|
|
@@ -107,12 +111,66 @@ function parseRepositoryUpdateInput(value) {
|
|
|
107
111
|
if (teardownCommand !== void 0) patch.teardownCommand = teardownCommand;
|
|
108
112
|
return Object.keys(patch).length > 0 ? patch : null;
|
|
109
113
|
}
|
|
114
|
+
/** Provider output is shown to the user; strip any clone credential from it first. */
|
|
115
|
+
function redactBuild(build) {
|
|
116
|
+
return {
|
|
117
|
+
...build,
|
|
118
|
+
...build.error !== void 0 ? { error: redactCredentials(build.error) } : {},
|
|
119
|
+
...build.logs ? { logs: build.logs.map(redactCredentials) } : {}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/** What the environment reports when the factory has no sandbox configured. */
|
|
123
|
+
const NO_SANDBOX = {
|
|
124
|
+
provider: "none",
|
|
125
|
+
settingsSchema: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {},
|
|
128
|
+
additionalProperties: false
|
|
129
|
+
},
|
|
130
|
+
capabilities: {
|
|
131
|
+
template: false,
|
|
132
|
+
builds: {
|
|
133
|
+
available: false,
|
|
134
|
+
history: false
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
110
138
|
var ProjectRoutes = class extends Route {
|
|
111
139
|
#versionControlIntegrationIds;
|
|
140
|
+
#sandboxDescription;
|
|
141
|
+
#settingsSchema;
|
|
112
142
|
constructor(deps) {
|
|
113
143
|
super(deps);
|
|
114
144
|
this.#versionControlIntegrationIds = new Set(deps.versionControlIntegrationIds ?? []);
|
|
115
145
|
}
|
|
146
|
+
#describeSandbox() {
|
|
147
|
+
if (!this.deps.sandbox) return NO_SANDBOX;
|
|
148
|
+
this.#sandboxDescription ??= describeFactorySandbox(this.deps.sandbox);
|
|
149
|
+
return this.#sandboxDescription;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Merge a settings patch onto the stored document (null removes a key) and
|
|
153
|
+
* validate the result through the sandbox's schema. Returns the issues when
|
|
154
|
+
* the merged document is rejected.
|
|
155
|
+
*
|
|
156
|
+
* A stored key the current schema does not declare is left over from another
|
|
157
|
+
* provider or an older schema. When the merged document fails validation, the
|
|
158
|
+
* merge retries without those keys so one stale value cannot lock every later
|
|
159
|
+
* update; keys the patch itself sets are never pruned.
|
|
160
|
+
*/
|
|
161
|
+
async #mergeSettings(stored, patch) {
|
|
162
|
+
const merged = { ...stored };
|
|
163
|
+
for (const [key, value] of Object.entries(patch)) if (value === null) delete merged[key];
|
|
164
|
+
else merged[key] = value;
|
|
165
|
+
this.#settingsSchema ??= normalizeFactorySandboxSettings(this.deps.sandbox);
|
|
166
|
+
const result = await this.#settingsSchema["~standard"].validate(merged);
|
|
167
|
+
if (!result.issues) return { merged: Object.keys(merged).length > 0 ? merged : null };
|
|
168
|
+
const declared = this.#describeSandbox().settingsSchema.properties ?? {};
|
|
169
|
+
const pruned = Object.fromEntries(Object.entries(merged).filter(([key]) => key in patch || Object.hasOwn(declared, key)));
|
|
170
|
+
if (Object.keys(pruned).length === Object.keys(merged).length) return { issues: result.issues };
|
|
171
|
+
if ((await this.#settingsSchema["~standard"].validate(pruned)).issues) return { issues: result.issues };
|
|
172
|
+
return { merged: Object.keys(pruned).length > 0 ? pruned : null };
|
|
173
|
+
}
|
|
116
174
|
async #projects() {
|
|
117
175
|
await this.deps.projects.ensureReady();
|
|
118
176
|
return this.deps.projects;
|
|
@@ -173,6 +231,166 @@ var ProjectRoutes = class extends Route {
|
|
|
173
231
|
repository
|
|
174
232
|
};
|
|
175
233
|
}
|
|
234
|
+
/** Every link of the project across every handle, with the handle that owns it, in position order. */
|
|
235
|
+
async #environmentLinks(orgId, projectId) {
|
|
236
|
+
const links = [];
|
|
237
|
+
for (const handle of await this.#handles()) for (const projectRepository of await handle.projectRepositories.listByProject({
|
|
238
|
+
orgId,
|
|
239
|
+
factoryProjectId: projectId
|
|
240
|
+
})) links.push({
|
|
241
|
+
handle,
|
|
242
|
+
projectRepository
|
|
243
|
+
});
|
|
244
|
+
return links.sort((a, b) => a.projectRepository.position - b.projectRepository.position || a.projectRepository.createdAt.getTime() - b.projectRepository.createdAt.getTime());
|
|
245
|
+
}
|
|
246
|
+
async #environmentPayload(orgId, project) {
|
|
247
|
+
const repositories = [];
|
|
248
|
+
for (const { handle, projectRepository } of await this.#environmentLinks(orgId, project.id)) {
|
|
249
|
+
const repository = await handle.repositories.get({
|
|
250
|
+
orgId,
|
|
251
|
+
id: projectRepository.repositoryId
|
|
252
|
+
});
|
|
253
|
+
repositories.push({
|
|
254
|
+
projectRepositoryId: projectRepository.id,
|
|
255
|
+
connectionId: projectRepository.connectionId,
|
|
256
|
+
repositoryId: projectRepository.repositoryId,
|
|
257
|
+
slug: repository?.slug ?? null,
|
|
258
|
+
defaultBranch: repository?.defaultBranch ?? null,
|
|
259
|
+
position: projectRepository.position,
|
|
260
|
+
inEnvironment: projectRepository.inEnvironment,
|
|
261
|
+
setupCommand: projectRepository.setupCommand,
|
|
262
|
+
teardownCommand: projectRepository.teardownCommand,
|
|
263
|
+
lastBuildStatus: projectRepository.lastBuildStatus,
|
|
264
|
+
lastBuildError: projectRepository.lastBuildError,
|
|
265
|
+
lastBuiltAt: projectRepository.lastBuiltAt
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return { environment: {
|
|
269
|
+
sandbox: this.#describeSandbox(),
|
|
270
|
+
settings: project.sandboxSettings ?? {},
|
|
271
|
+
sandboxWorkingDirectory: project.sandboxWorkingDirectory,
|
|
272
|
+
workspaceSetupCommand: project.workspaceSetupCommand,
|
|
273
|
+
activeTemplateId: project.activeTemplateId,
|
|
274
|
+
activeTemplateHeads: project.activeTemplateHeads,
|
|
275
|
+
repositories,
|
|
276
|
+
...await this.#buildPayload(project)
|
|
277
|
+
} };
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The build keys of the environment payload, present only when the
|
|
281
|
+
* sandbox can build. Reads the project row and the schedule row; never
|
|
282
|
+
* the provider (live status is the builds routes' job).
|
|
283
|
+
*/
|
|
284
|
+
async #buildPayload(project) {
|
|
285
|
+
const builds = this.deps.environmentBuilds;
|
|
286
|
+
if (!builds) return {};
|
|
287
|
+
const schedules = builds.scheduleAvailable ? builds.schedules() : void 0;
|
|
288
|
+
return {
|
|
289
|
+
buildTriggers: {
|
|
290
|
+
schedule: {
|
|
291
|
+
...schedules ? await readSchedule(schedules, project.id) : {
|
|
292
|
+
enabled: false,
|
|
293
|
+
cron: null,
|
|
294
|
+
timezone: null
|
|
295
|
+
},
|
|
296
|
+
scheduleAvailable: builds.scheduleAvailable
|
|
297
|
+
},
|
|
298
|
+
push: {
|
|
299
|
+
enabled: project.buildOnPushEnabled,
|
|
300
|
+
debounceMinutes: project.buildPushDebounceMinutes
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
build: project.lastBuildId ? {
|
|
304
|
+
buildId: project.lastBuildId,
|
|
305
|
+
attemptedAt: project.lastBuildAttemptedAt?.toISOString() ?? null
|
|
306
|
+
} : null
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/** The builds capability behind the routes, or the 404 to answer with. */
|
|
310
|
+
#builds(context) {
|
|
311
|
+
const builds = this.deps.sandbox?.builds;
|
|
312
|
+
const runner = this.deps.environmentBuilds;
|
|
313
|
+
if (!builds || !runner) return { response: context.json({ error: "no_builds" }, 404) };
|
|
314
|
+
return {
|
|
315
|
+
builds,
|
|
316
|
+
runner
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
#buildRoutes() {
|
|
320
|
+
return [
|
|
321
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.projectEnvironmentBuild.path, {
|
|
322
|
+
method: FACTORY_ROUTE_CONTRACTS.projectEnvironmentBuild.method,
|
|
323
|
+
requiresAuth: false,
|
|
324
|
+
handler: async (routeContext) => {
|
|
325
|
+
const context = loose(routeContext);
|
|
326
|
+
const tenant = await this.#resolveTenant(context);
|
|
327
|
+
if ("response" in tenant) return tenant.response;
|
|
328
|
+
const projectId = context.req.param("id");
|
|
329
|
+
if (!projectId || !UUID_RE.test(projectId)) return context.json({ error: "Project not found" }, 404);
|
|
330
|
+
const project = await this.#project(tenant.orgId, projectId);
|
|
331
|
+
if (!project) return context.json({ error: "Project not found" }, 404);
|
|
332
|
+
const capability = this.#builds(context);
|
|
333
|
+
if ("response" in capability) return capability.response;
|
|
334
|
+
const { heads: _heads, ...outcome } = await capability.runner.start(project.id, "manual");
|
|
335
|
+
return context.json(outcome);
|
|
336
|
+
}
|
|
337
|
+
}),
|
|
338
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.projectEnvironmentBuilds.path, {
|
|
339
|
+
method: FACTORY_ROUTE_CONTRACTS.projectEnvironmentBuilds.method,
|
|
340
|
+
requiresAuth: false,
|
|
341
|
+
handler: async (routeContext) => {
|
|
342
|
+
const context = loose(routeContext);
|
|
343
|
+
const tenant = await this.#resolveTenant(context);
|
|
344
|
+
if ("response" in tenant) return tenant.response;
|
|
345
|
+
const projectId = context.req.param("id");
|
|
346
|
+
if (!projectId || !UUID_RE.test(projectId)) return context.json({ error: "Project not found" }, 404);
|
|
347
|
+
const project = await this.#project(tenant.orgId, projectId);
|
|
348
|
+
if (!project) return context.json({ error: "Project not found" }, 404);
|
|
349
|
+
const capability = this.#builds(context);
|
|
350
|
+
if ("response" in capability) return capability.response;
|
|
351
|
+
if (!capability.builds.list) return context.json({ error: "no_history" }, 404);
|
|
352
|
+
const read = await capability.runner.readContext(project.id);
|
|
353
|
+
if (!read) return context.json({ error: "no_environment" }, 404);
|
|
354
|
+
const builds = await capability.builds.list(read.ctx, read.settings);
|
|
355
|
+
return context.json({ builds: builds.map(redactBuild) });
|
|
356
|
+
}
|
|
357
|
+
}),
|
|
358
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.projectEnvironmentBuildGet.path, {
|
|
359
|
+
method: FACTORY_ROUTE_CONTRACTS.projectEnvironmentBuildGet.method,
|
|
360
|
+
requiresAuth: false,
|
|
361
|
+
handler: async (routeContext) => {
|
|
362
|
+
const context = loose(routeContext);
|
|
363
|
+
const tenant = await this.#resolveTenant(context);
|
|
364
|
+
if ("response" in tenant) return tenant.response;
|
|
365
|
+
const projectId = context.req.param("id");
|
|
366
|
+
if (!projectId || !UUID_RE.test(projectId)) return context.json({ error: "Project not found" }, 404);
|
|
367
|
+
const project = await this.#project(tenant.orgId, projectId);
|
|
368
|
+
if (!project) return context.json({ error: "Project not found" }, 404);
|
|
369
|
+
const capability = this.#builds(context);
|
|
370
|
+
if ("response" in capability) return capability.response;
|
|
371
|
+
const buildId = context.req.param("buildId") ?? "";
|
|
372
|
+
if (!buildId) return context.json({ error: "Build not found" }, 404);
|
|
373
|
+
const read = await capability.runner.readContext(project.id);
|
|
374
|
+
if (!read) return context.json({ error: "no_environment" }, 404);
|
|
375
|
+
if (buildId !== project.lastBuildId) {
|
|
376
|
+
if (!(capability.builds.list ? await capability.builds.list(read.ctx, read.settings) : []).some((build) => build.buildId === buildId)) return context.json({ error: "Build not found" }, 404);
|
|
377
|
+
}
|
|
378
|
+
return context.json({ build: redactBuild(await capability.builds.get(read.ctx, read.settings, buildId)) });
|
|
379
|
+
}
|
|
380
|
+
})
|
|
381
|
+
];
|
|
382
|
+
}
|
|
383
|
+
/** Best effort: a deleted project's cron schedule must not keep firing. */
|
|
384
|
+
async #dropSchedule(projectId) {
|
|
385
|
+
const runner = this.deps.environmentBuilds;
|
|
386
|
+
if (!runner?.scheduleAvailable) return;
|
|
387
|
+
try {
|
|
388
|
+
await runner.schedules().delete(scheduleIdFor(projectId));
|
|
389
|
+
} catch (error) {
|
|
390
|
+
if (error instanceof MastraError && error.id === "SCHEDULES_NOT_FOUND") return;
|
|
391
|
+
console.warn("[factory] could not delete the environment build schedule of a deleted project:", error);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
176
394
|
async #retireProjectRepositorySessions(handle, orgId, projectRepositoryId) {
|
|
177
395
|
if ((await handle.sessions.listByProjectRepository({ projectRepositoryId })).length === 0) return true;
|
|
178
396
|
if (!this.deps.sessionRetirement) return false;
|
|
@@ -372,6 +590,7 @@ var ProjectRoutes = class extends Route {
|
|
|
372
590
|
orgId: tenant.orgId,
|
|
373
591
|
id
|
|
374
592
|
});
|
|
593
|
+
await this.#dropSchedule(id);
|
|
375
594
|
return context.body(null, 204);
|
|
376
595
|
}
|
|
377
596
|
}),
|
|
@@ -508,6 +727,129 @@ var ProjectRoutes = class extends Route {
|
|
|
508
727
|
return context.json({ projectRepository: await this.#repositoryPayload(found.handle, tenant.orgId, projectRepository) }, 201);
|
|
509
728
|
}
|
|
510
729
|
}),
|
|
730
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.projectEnvironmentGet.path, {
|
|
731
|
+
method: FACTORY_ROUTE_CONTRACTS.projectEnvironmentGet.method,
|
|
732
|
+
requiresAuth: false,
|
|
733
|
+
handler: async (routeContext) => {
|
|
734
|
+
const context = loose(routeContext);
|
|
735
|
+
const tenant = await this.#resolveTenant(context);
|
|
736
|
+
if ("response" in tenant) return tenant.response;
|
|
737
|
+
const projectId = context.req.param("id");
|
|
738
|
+
if (!projectId || !UUID_RE.test(projectId)) return context.json({ error: "Project not found" }, 404);
|
|
739
|
+
const project = await this.#project(tenant.orgId, projectId);
|
|
740
|
+
if (!project) return context.json({ error: "Project not found" }, 404);
|
|
741
|
+
return context.json(await this.#environmentPayload(tenant.orgId, project));
|
|
742
|
+
}
|
|
743
|
+
}),
|
|
744
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.projectEnvironmentUpdate.path, {
|
|
745
|
+
method: FACTORY_ROUTE_CONTRACTS.projectEnvironmentUpdate.method,
|
|
746
|
+
requiresAuth: false,
|
|
747
|
+
handler: async (routeContext) => {
|
|
748
|
+
const context = loose(routeContext);
|
|
749
|
+
const tenant = await this.#resolveTenant(context);
|
|
750
|
+
if ("response" in tenant) return tenant.response;
|
|
751
|
+
const projectId = context.req.param("id");
|
|
752
|
+
if (!projectId || !UUID_RE.test(projectId)) return context.json({ error: "Project not found" }, 404);
|
|
753
|
+
const project = await this.#project(tenant.orgId, projectId);
|
|
754
|
+
if (!project) return context.json({ error: "Project not found" }, 404);
|
|
755
|
+
const parsed = FACTORY_ROUTE_CONTRACTS.projectEnvironmentUpdate.bodySchema.safeParse(await readJson(context));
|
|
756
|
+
if (!parsed.success) return context.json({ error: "invalid_environment" }, 400);
|
|
757
|
+
const { repositories: repositoryPatches, settings: settingsPatch, buildTriggers, ...projectInput } = parsed.data;
|
|
758
|
+
const input = projectInput;
|
|
759
|
+
const runner = this.deps.environmentBuilds;
|
|
760
|
+
if (buildTriggers !== void 0) {
|
|
761
|
+
if (!runner) return context.json({ error: "no_builds" }, 400);
|
|
762
|
+
if (buildTriggers.push?.enabled !== void 0) input.buildOnPushEnabled = buildTriggers.push.enabled;
|
|
763
|
+
if (buildTriggers.push?.debounceMinutes !== void 0) input.buildPushDebounceMinutes = buildTriggers.push.debounceMinutes;
|
|
764
|
+
if (buildTriggers.schedule) if (!runner.scheduleAvailable) {
|
|
765
|
+
if (buildTriggers.schedule.enabled) return context.json({ error: "schedules_unavailable" }, 400);
|
|
766
|
+
} else {
|
|
767
|
+
const { cron, timezone } = buildTriggers.schedule;
|
|
768
|
+
const stored = await readSchedule(runner.schedules(), project.id);
|
|
769
|
+
const nextCron = cron ?? stored.cron;
|
|
770
|
+
if (!nextCron && buildTriggers.schedule.enabled) return context.json({
|
|
771
|
+
error: "invalid_environment",
|
|
772
|
+
issues: [{
|
|
773
|
+
message: "cron is required",
|
|
774
|
+
path: [
|
|
775
|
+
"buildTriggers",
|
|
776
|
+
"schedule",
|
|
777
|
+
"cron"
|
|
778
|
+
]
|
|
779
|
+
}]
|
|
780
|
+
}, 400);
|
|
781
|
+
const nextTimezone = timezone ?? stored.timezone ?? void 0;
|
|
782
|
+
const message = nextCron ? invalidCronMessage(nextCron, nextTimezone) : void 0;
|
|
783
|
+
if (message) return context.json({
|
|
784
|
+
error: "invalid_environment",
|
|
785
|
+
issues: [{
|
|
786
|
+
message,
|
|
787
|
+
path: [
|
|
788
|
+
"buildTriggers",
|
|
789
|
+
"schedule",
|
|
790
|
+
"cron"
|
|
791
|
+
]
|
|
792
|
+
}]
|
|
793
|
+
}, 400);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (settingsPatch !== void 0 && !this.deps.sandbox) return context.json({ error: "no_sandbox" }, 400);
|
|
797
|
+
let updated = project;
|
|
798
|
+
if (Object.keys(input).length > 0 || settingsPatch !== void 0 || repositoryPatches !== void 0) {
|
|
799
|
+
let issues;
|
|
800
|
+
let repositoryOutcome;
|
|
801
|
+
const sourceControl = await this.#sourceControl();
|
|
802
|
+
const result = await (await this.#projects()).update({
|
|
803
|
+
orgId: tenant.orgId,
|
|
804
|
+
id: projectId,
|
|
805
|
+
input: async (current, ops) => {
|
|
806
|
+
let sandboxSettings;
|
|
807
|
+
if (settingsPatch !== void 0) {
|
|
808
|
+
const settings = await this.#mergeSettings(current.sandboxSettings ?? {}, settingsPatch);
|
|
809
|
+
if ("issues" in settings) {
|
|
810
|
+
issues = settings.issues;
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
sandboxSettings = settings.merged;
|
|
814
|
+
}
|
|
815
|
+
if (repositoryPatches !== void 0) {
|
|
816
|
+
repositoryOutcome = await sourceControl.updateEnvironmentRepositories(ops, {
|
|
817
|
+
factoryProjectId: current.id,
|
|
818
|
+
integrationIds: [...this.#versionControlIntegrationIds],
|
|
819
|
+
patches: repositoryPatches
|
|
820
|
+
});
|
|
821
|
+
if (repositoryOutcome !== "updated") return null;
|
|
822
|
+
}
|
|
823
|
+
return {
|
|
824
|
+
...input,
|
|
825
|
+
...sandboxSettings !== void 0 ? { sandboxSettings } : {}
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
if (repositoryOutcome === "not_found") return context.json({ error: "Project repository not found" }, 404);
|
|
830
|
+
if (repositoryOutcome === "incomplete_reorder") return context.json({ error: "invalid_environment" }, 400);
|
|
831
|
+
if (issues) return context.json({
|
|
832
|
+
error: "invalid_environment",
|
|
833
|
+
issues: issues.map((issue) => ({
|
|
834
|
+
message: issue.message,
|
|
835
|
+
path: Array.isArray(issue.path) ? issue.path.map((segment) => typeof segment === "object" && segment !== null && "key" in segment ? segment.key : segment) : []
|
|
836
|
+
}))
|
|
837
|
+
}, 400);
|
|
838
|
+
if (!result) return context.json({ error: "Project not found" }, 404);
|
|
839
|
+
updated = result;
|
|
840
|
+
}
|
|
841
|
+
if (buildTriggers?.schedule && runner?.scheduleAvailable) {
|
|
842
|
+
const stored = await readSchedule(runner.schedules(), project.id);
|
|
843
|
+
const cron = buildTriggers.schedule.cron ?? stored.cron;
|
|
844
|
+
if (cron) await ensureSchedule(runner.schedules(), project.id, {
|
|
845
|
+
enabled: buildTriggers.schedule.enabled,
|
|
846
|
+
cron,
|
|
847
|
+
...buildTriggers.schedule.timezone ?? stored.timezone ? { timezone: buildTriggers.schedule.timezone ?? stored.timezone } : {}
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
return context.json(await this.#environmentPayload(tenant.orgId, updated));
|
|
851
|
+
}
|
|
852
|
+
}),
|
|
511
853
|
registerApiRoute("/web/factory/projects/:id/repositories/:projectRepositoryId", {
|
|
512
854
|
method: "PATCH",
|
|
513
855
|
requiresAuth: false,
|
|
@@ -557,7 +899,8 @@ var ProjectRoutes = class extends Route {
|
|
|
557
899
|
});
|
|
558
900
|
return context.body(null, 204);
|
|
559
901
|
}
|
|
560
|
-
})
|
|
902
|
+
}),
|
|
903
|
+
...this.#buildRoutes()
|
|
561
904
|
];
|
|
562
905
|
}
|
|
563
906
|
};
|