@treeseed/sdk 0.10.11 → 0.10.13

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 (52) hide show
  1. package/README.md +2 -2
  2. package/dist/api/auth/d1-store.js +20 -1
  3. package/dist/capacity-provider.d.ts +53 -1
  4. package/dist/capacity.d.ts +80 -1
  5. package/dist/capacity.js +687 -8
  6. package/dist/db/d1.d.ts +109 -3227
  7. package/dist/db/index.d.ts +1 -0
  8. package/dist/db/index.js +1 -0
  9. package/dist/db/market-schema.d.ts +43769 -0
  10. package/dist/db/market-schema.js +1878 -0
  11. package/dist/db/node-sqlite.d.ts +109 -3227
  12. package/dist/db/schema.d.ts +226 -5757
  13. package/dist/db/schema.js +35 -226
  14. package/dist/index.d.ts +6 -4
  15. package/dist/index.js +32 -0
  16. package/dist/market-client.d.ts +135 -0
  17. package/dist/market-client.js +134 -1
  18. package/dist/operations/services/commit-message-provider.js +1 -1
  19. package/dist/operations/services/d1-migration.js +0 -59
  20. package/dist/operations/services/deploy.js +5 -1
  21. package/dist/operations/services/github-api.d.ts +83 -0
  22. package/dist/operations/services/github-api.js +167 -0
  23. package/dist/operations/services/local-dev.js +3 -3
  24. package/dist/operations/services/mailpit-runtime.d.ts +13 -2
  25. package/dist/operations/services/mailpit-runtime.js +19 -14
  26. package/dist/operations/services/project-web-monitor.d.ts +15 -0
  27. package/dist/operations/services/project-web-monitor.js +260 -0
  28. package/dist/operations/services/railway-api.js +2 -2
  29. package/dist/operations/services/release-candidate.js +9 -9
  30. package/dist/operations/services/runtime-paths.d.ts +1 -1
  31. package/dist/operations/services/runtime-paths.js +2 -2
  32. package/dist/operations/services/template-registry.js +10 -1
  33. package/dist/operations.d.ts +1 -0
  34. package/dist/operations.js +11 -1
  35. package/dist/platform-operation-store.d.ts +4 -0
  36. package/dist/platform-operation-store.js +29 -3
  37. package/dist/platform-operations.d.ts +8 -0
  38. package/dist/platform-operations.js +19 -0
  39. package/dist/remote.js +6 -6
  40. package/dist/scripts/tenant-d1-migrate-local.js +2 -3
  41. package/dist/scripts/test-cloudflare-local.js +1 -1
  42. package/dist/scripts/workspace-command-e2e.js +3 -1
  43. package/dist/sdk-types.d.ts +281 -3
  44. package/dist/sdk-types.js +5 -1
  45. package/dist/seeds/normalize.js +6 -0
  46. package/dist/seeds/schema.js +61 -1
  47. package/dist/seeds/types.d.ts +32 -0
  48. package/drizzle/d1/0000_treeseed_d1.sql +37 -0
  49. package/drizzle/market/0000_market_control_plane.sql +2929 -0
  50. package/drizzle/market/0001_capacity_budget_mode_default.sql +4 -0
  51. package/drizzle/market/0002_user_email_addresses.sql +26 -0
  52. package/package.json +8 -1
@@ -272,7 +272,8 @@ class MarketClient {
272
272
  });
273
273
  const payload = await response.json().catch(() => ({}));
274
274
  if (!response.ok) {
275
- const error = typeof payload.error === "string" ? String(payload.error) : `Market request failed with ${response.status}.`;
275
+ const payloadError = payload.error;
276
+ const error = typeof payloadError === "string" ? String(payloadError) : payloadError && typeof payloadError === "object" && typeof payloadError.message === "string" ? String(payloadError.message) : `Market request failed with ${response.status}.`;
276
277
  throw new MarketApiError(error, response.status, payload);
277
278
  }
278
279
  return payload;
@@ -331,11 +332,45 @@ class MarketClient {
331
332
  body
332
333
  });
333
334
  }
335
+ confirmWebEmail(body) {
336
+ return this.request("/v1/auth/web/confirm-email", {
337
+ method: "POST",
338
+ body
339
+ });
340
+ }
334
341
  checkWebUsername(username) {
335
342
  return this.request(
336
343
  `/v1/auth/web/username/check?username=${encodeURIComponent(username)}`
337
344
  );
338
345
  }
346
+ webEmails() {
347
+ return this.request("/v1/auth/web/emails", { requireAuth: true });
348
+ }
349
+ addWebEmail(body) {
350
+ return this.request("/v1/auth/web/emails", {
351
+ method: "POST",
352
+ body,
353
+ requireAuth: true
354
+ });
355
+ }
356
+ verifyWebEmail(emailId) {
357
+ return this.request(
358
+ `/v1/auth/web/emails/${encodeURIComponent(emailId)}/verify`,
359
+ { method: "POST", requireAuth: true }
360
+ );
361
+ }
362
+ setPrimaryWebEmail(emailId) {
363
+ return this.request(
364
+ `/v1/auth/web/emails/${encodeURIComponent(emailId)}/primary`,
365
+ { method: "POST", requireAuth: true }
366
+ );
367
+ }
368
+ deleteWebEmail(emailId) {
369
+ return this.request(
370
+ `/v1/auth/web/emails/${encodeURIComponent(emailId)}`,
371
+ { method: "DELETE", requireAuth: true }
372
+ );
373
+ }
339
374
  webSessions() {
340
375
  return this.request("/v1/auth/web/sessions", { requireAuth: true });
341
376
  }
@@ -426,12 +461,73 @@ class MarketClient {
426
461
  { requireAuth: true }
427
462
  );
428
463
  }
464
+ projectDeploymentState(projectId) {
465
+ return this.request(
466
+ `/v1/projects/${encodeURIComponent(projectId)}/deployment-state`,
467
+ { requireAuth: true }
468
+ );
469
+ }
470
+ projectDeployments(projectId, filters = {}) {
471
+ const query = new URLSearchParams();
472
+ if (filters.environment) query.set("environment", String(filters.environment));
473
+ if (filters.action) query.set("action", String(filters.action));
474
+ if (filters.status) query.set("status", String(filters.status));
475
+ if (filters.limit) query.set("limit", String(filters.limit));
476
+ const suffix = query.size > 0 ? `?${query.toString()}` : "";
477
+ return this.request(
478
+ `/v1/projects/${encodeURIComponent(projectId)}/deployments${suffix}`,
479
+ { requireAuth: true }
480
+ );
481
+ }
482
+ projectDeployment(projectId, deploymentId) {
483
+ return this.request(
484
+ `/v1/projects/${encodeURIComponent(projectId)}/deployments/${encodeURIComponent(deploymentId)}`,
485
+ { requireAuth: true }
486
+ );
487
+ }
488
+ projectDeploymentEvents(projectId, deploymentId, options = {}) {
489
+ const query = options.limit ? `?limit=${encodeURIComponent(String(options.limit))}` : "";
490
+ return this.request(
491
+ `/v1/projects/${encodeURIComponent(projectId)}/deployments/${encodeURIComponent(deploymentId)}/events${query}`,
492
+ { requireAuth: true }
493
+ );
494
+ }
495
+ createProjectWebDeployment(projectId, body) {
496
+ return this.request(
497
+ `/v1/projects/${encodeURIComponent(projectId)}/deployments/web`,
498
+ { method: "POST", body, requireAuth: true }
499
+ );
500
+ }
501
+ retryProjectDeployment(projectId, deploymentId, body = {}) {
502
+ return this.request(
503
+ `/v1/projects/${encodeURIComponent(projectId)}/deployments/${encodeURIComponent(deploymentId)}/retry`,
504
+ { method: "POST", body, requireAuth: true }
505
+ );
506
+ }
507
+ resumeProjectDeployment(projectId, deploymentId, body = {}) {
508
+ return this.request(
509
+ `/v1/projects/${encodeURIComponent(projectId)}/deployments/${encodeURIComponent(deploymentId)}/resume`,
510
+ { method: "POST", body, requireAuth: true }
511
+ );
512
+ }
513
+ cancelProjectDeployment(projectId, deploymentId, body = {}) {
514
+ return this.request(
515
+ `/v1/projects/${encodeURIComponent(projectId)}/deployments/${encodeURIComponent(deploymentId)}/cancel`,
516
+ { method: "POST", body, requireAuth: true }
517
+ );
518
+ }
429
519
  teamCapacity(teamId) {
430
520
  return this.request(
431
521
  `/v1/teams/${encodeURIComponent(teamId)}/capacity`,
432
522
  { requireAuth: true }
433
523
  );
434
524
  }
525
+ teamCapacityProviders(teamId) {
526
+ return this.request(
527
+ `/v1/teams/${encodeURIComponent(teamId)}/capacity-providers`,
528
+ { requireAuth: true }
529
+ );
530
+ }
435
531
  launchManagedCapacityProvider(teamId, body = {}) {
436
532
  return this.request(
437
533
  `/v1/teams/${encodeURIComponent(teamId)}/capacity/providers/managed`,
@@ -456,12 +552,49 @@ class MarketClient {
456
552
  { requireAuth: true }
457
553
  );
458
554
  }
555
+ updateCapacityProvider(teamId, providerId, body) {
556
+ return this.request(
557
+ `/v1/teams/${encodeURIComponent(teamId)}/capacity-providers/${encodeURIComponent(providerId)}`,
558
+ { method: "PATCH", body, requireAuth: true }
559
+ );
560
+ }
459
561
  createCapacityGrant(teamId, body) {
460
562
  return this.request(
461
563
  `/v1/teams/${encodeURIComponent(teamId)}/capacity-grants`,
462
564
  { method: "POST", body, requireAuth: true }
463
565
  );
464
566
  }
567
+ executionProviders(teamId, providerId) {
568
+ return this.request(
569
+ `/v1/teams/${encodeURIComponent(teamId)}/capacity-providers/${encodeURIComponent(providerId)}/execution-providers`,
570
+ { requireAuth: true }
571
+ );
572
+ }
573
+ createExecutionProvider(teamId, providerId, body) {
574
+ return this.request(
575
+ `/v1/teams/${encodeURIComponent(teamId)}/capacity-providers/${encodeURIComponent(providerId)}/execution-providers`,
576
+ { method: "POST", body, requireAuth: true }
577
+ );
578
+ }
579
+ updateExecutionProvider(teamId, providerId, executionProviderId, body) {
580
+ return this.request(
581
+ `/v1/teams/${encodeURIComponent(teamId)}/capacity-providers/${encodeURIComponent(providerId)}/execution-providers/${encodeURIComponent(executionProviderId)}`,
582
+ { method: "PATCH", body, requireAuth: true }
583
+ );
584
+ }
585
+ createExecutionProviderNativeLimit(teamId, providerId, executionProviderId, body) {
586
+ return this.request(
587
+ `/v1/teams/${encodeURIComponent(teamId)}/capacity-providers/${encodeURIComponent(providerId)}/execution-providers/${encodeURIComponent(executionProviderId)}/native-limits`,
588
+ { method: "POST", body, requireAuth: true }
589
+ );
590
+ }
591
+ projectCapacityPlan(projectId, environment) {
592
+ const query = environment ? `?environment=${encodeURIComponent(environment)}` : "";
593
+ return this.request(
594
+ `/v1/projects/${encodeURIComponent(projectId)}/capacity-plan${query}`,
595
+ { requireAuth: true }
596
+ );
597
+ }
465
598
  planSeed(seedName, body) {
466
599
  return this.request(
467
600
  `/v1/seeds/${encodeURIComponent(seedName)}/plan`,
@@ -62,7 +62,7 @@ function changedPaths(changedFiles) {
62
62
  function changedFileGroups(paths) {
63
63
  const counts = /* @__PURE__ */ new Map();
64
64
  for (const path of paths) {
65
- const group = path.startsWith(".github/") || path.includes("/workflows/") ? "ci" : /(^|\/)(test|tests|__tests__)\/|\.test\.|\.spec\./u.test(path) ? "tests" : path.startsWith("docs/") || /\.(md|mdx|txt)$/u.test(path) ? "docs" : path.includes("workflow") || path.includes("repository-save-orchestrator") ? "workflow" : path.includes("package-reference") || path.includes("release") || path.includes("publish") ? "release" : /(^|\/)(package|package-lock)\.json$/u.test(path) || path.includes("build") || path.includes("scripts/") ? "build" : path.includes("config") || path.endsWith(".yaml") || path.endsWith(".yml") ? "config" : path.startsWith("migrations/") || path.includes("/db/") ? "database" : path.startsWith("src/pages/") || path.startsWith("src/layouts/") || path.includes("/ui/") ? "ui" : path.startsWith("src/api/") || path.includes("/api/") ? "api" : "source";
65
+ const group = path.startsWith(".github/") || path.includes("/workflows/") ? "ci" : /(^|\/)(test|tests|__tests__)\/|\.test\.|\.spec\./u.test(path) ? "tests" : path.startsWith("docs/") || /\.(md|mdx|txt)$/u.test(path) ? "docs" : path.includes("workflow") || path.includes("repository-save-orchestrator") ? "workflow" : path.includes("package-reference") || path.includes("release") || path.includes("publish") ? "release" : /(^|\/)(package|package-lock)\.json$/u.test(path) || path.includes("build") || path.includes("scripts/") ? "build" : path.includes("config") || path.endsWith(".yaml") || path.endsWith(".yml") ? "config" : path.startsWith("packages/sdk/drizzle/") || path.includes("/db/") ? "database" : path.startsWith("src/pages/") || path.startsWith("src/layouts/") || path.includes("/ui/") ? "ui" : path.startsWith("src/api/") || path.includes("/api/") ? "api" : "source";
66
66
  counts.set(group, (counts.get(group) ?? 0) + 1);
67
67
  }
68
68
  return [...counts.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]));
@@ -1,8 +1,6 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
- import { readFileSync } from "node:fs";
3
2
  import { resolve } from "node:path";
4
3
  import { spawnSync } from "node:child_process";
5
- import { NodeSqliteD1Database } from "../../db/node-sqlite.js";
6
4
  import { resolveWranglerBin } from "./runtime-tools.js";
7
5
  const DATABASE_BINDING = "SITE_DATA_DB";
8
6
  const WRANGLER_D1_TIMEOUT_MS = 12e4;
@@ -100,58 +98,6 @@ function markMigrationApplied({ cwd, wranglerConfig, persistTo, migration }) {
100
98
  command: `INSERT OR REPLACE INTO treeseed_schema_migrations (name, applied_at) VALUES ('${migration.replace(/'/g, "''")}', datetime('now'));`
101
99
  });
102
100
  }
103
- function tableColumns({ cwd, wranglerConfig, persistTo, tableName }) {
104
- const result = executeSqlCommand({
105
- cwd,
106
- wranglerConfig,
107
- persistTo,
108
- capture: true,
109
- command: `PRAGMA table_info(${tableName.replace(/[^A-Za-z0-9_]/g, "")});`
110
- });
111
- const parsed = parseWranglerJsonOutput(result.stdout);
112
- const rows = (Array.isArray(parsed) ? parsed : [parsed]).flatMap((entry) => entry.results ?? []);
113
- return new Set(rows.map((row) => row.name).filter(Boolean));
114
- }
115
- function migrationAlreadySatisfied({ cwd, wranglerConfig, persistTo, filePath }) {
116
- const sql = readFileSync(filePath, "utf8");
117
- if (!sql.includes("execution_profile_id") || !sql.includes("task_estimate_profiles")) {
118
- return false;
119
- }
120
- const estimateColumns = tableColumns({ cwd, wranglerConfig, persistTo, tableName: "task_estimates" });
121
- const actualColumns = tableColumns({ cwd, wranglerConfig, persistTo, tableName: "task_usage_actuals" });
122
- const profileColumns = tableColumns({ cwd, wranglerConfig, persistTo, tableName: "task_estimate_profiles" });
123
- return estimateColumns.has("execution_profile_id") && actualColumns.has("execution_profile_id") && profileColumns.has("execution_profile_id") && profileColumns.has("completed_sample_count") && profileColumns.has("interrupted_sample_count") && profileColumns.has("confidence_score");
124
- }
125
- function ensureLocalSdkRuntimeState({ migrationsRoot, persistTo }) {
126
- if (!persistTo) {
127
- return;
128
- }
129
- const migrationPath = resolve(migrationsRoot, "0025_agent_runtime_state.sql");
130
- if (!existsSync(migrationPath)) {
131
- return;
132
- }
133
- const sql = readFileSync(migrationPath, "utf8");
134
- const targets = /* @__PURE__ */ new Set();
135
- const sdkDb = new NodeSqliteD1Database(persistTo);
136
- targets.add(sdkDb.path);
137
- sdkDb.close();
138
- const miniflareRoot = resolve(persistTo, "miniflare-D1DatabaseObject");
139
- if (existsSync(miniflareRoot)) {
140
- for (const entry of readdirSync(miniflareRoot)) {
141
- if (entry.endsWith(".sqlite") && entry !== "metadata.sqlite") {
142
- targets.add(resolve(miniflareRoot, entry));
143
- }
144
- }
145
- }
146
- for (const target of targets) {
147
- const db = new NodeSqliteD1Database(target);
148
- try {
149
- db.client.exec(sql);
150
- } finally {
151
- db.close();
152
- }
153
- }
154
- }
155
101
  function runLocalD1Migrations({ cwd, wranglerConfig, migrationsRoot, persistTo }) {
156
102
  ensureSchemaMigrationsTable({ cwd, wranglerConfig, persistTo });
157
103
  const appliedMigrations = loadAppliedMigrations({ cwd, wranglerConfig, persistTo });
@@ -165,14 +111,9 @@ function runLocalD1Migrations({ cwd, wranglerConfig, migrationsRoot, persistTo }
165
111
  console.error(`Unable to find migration file at ${filePath}.`);
166
112
  process.exit(1);
167
113
  }
168
- if (migrationAlreadySatisfied({ cwd, wranglerConfig, persistTo, filePath })) {
169
- markMigrationApplied({ cwd, wranglerConfig, persistTo, migration });
170
- continue;
171
- }
172
114
  executeSqlFile({ cwd, wranglerConfig, filePath, persistTo });
173
115
  markMigrationApplied({ cwd, wranglerConfig, persistTo, migration });
174
116
  }
175
- ensureLocalSdkRuntimeState({ migrationsRoot, persistTo });
176
117
  }
177
118
  export {
178
119
  runLocalD1Migrations
@@ -6,6 +6,7 @@ import { createInterface } from "node:readline/promises";
6
6
  import { resolveTreeseedWebCachePolicy } from "../../platform/deploy-config.js";
7
7
  import { normalizeRailwayEnvironmentName } from "./railway-api.js";
8
8
  import { loadCliDeployConfig, resolveWranglerBin } from "./runtime-tools.js";
9
+ import { sdkD1MigrationsRoot } from "./runtime-paths.js";
9
10
  const DEFAULT_COMPATIBILITY_DATE = "2026-04-05";
10
11
  const DEFAULT_COMPATIBILITY_FLAGS = ["nodejs_compat"];
11
12
  const DEFAULT_TREESEED_MARKET_BASE_URL = "https://api.treeseed.ai";
@@ -339,7 +340,10 @@ const LOCAL_RUNTIME_AUTH_ENV_KEYS = [
339
340
  "TREESEED_API_DEVICE_CODE_TTL",
340
341
  "TREESEED_API_DEVICE_ACCESS_TOKEN_TTL",
341
342
  "TREESEED_API_DEVICE_POLL_INTERVAL",
343
+ "TREESEED_API_WEB_SERVICE_ID",
344
+ "TREESEED_API_WEB_SERVICE_SECRET",
342
345
  "TREESEED_API_WEB_EXCHANGE_TTL",
346
+ "TREESEED_PLATFORM_RUNNER_SECRET",
343
347
  "TREESEED_AUTH_GITHUB_CLIENT_ID",
344
348
  "TREESEED_AUTH_GITHUB_CLIENT_SECRET",
345
349
  "TREESEED_AUTH_GOOGLE_CLIENT_ID",
@@ -737,7 +741,7 @@ function buildWranglerConfigContents(tenantRoot, deployConfig, state, options =
737
741
  const workerName = state.workerName ?? targetWorkerName(deployConfig, target);
738
742
  const mainPath = relativeFromGeneratedRoot(resolve(tenantRoot, "dist/_worker.js/index.js"), generatedRoot);
739
743
  const assetsDirectory = relativeFromGeneratedRoot(resolve(tenantRoot, "dist"), generatedRoot);
740
- const migrationsDir = relativeFromGeneratedRoot(resolve(tenantRoot, "migrations"), generatedRoot);
744
+ const migrationsDir = relativeFromGeneratedRoot(sdkD1MigrationsRoot, generatedRoot);
741
745
  const vars = {
742
746
  ...buildPublicVars(deployConfig),
743
747
  ...buildLocalRuntimeVars(deployConfig, state, target, options.env)
@@ -58,6 +58,59 @@ export type GitHubWorkflowProgressEvent = {
58
58
  completedJobs?: GitHubWorkflowJobSummary[];
59
59
  failedJobs?: GitHubWorkflowJobSummary[];
60
60
  };
61
+ export interface GitHubWorkflowDispatchResult {
62
+ repository: string;
63
+ workflow: string;
64
+ branch: string;
65
+ inputs: Record<string, string> | undefined;
66
+ status: number | null;
67
+ dispatchedAt: string;
68
+ }
69
+ export interface GitHubWorkflowCancellationResult {
70
+ ok: boolean;
71
+ supported: boolean;
72
+ repository: string | null;
73
+ runId: number | null;
74
+ url?: string | null;
75
+ message: string;
76
+ cancelledAt?: string | null;
77
+ }
78
+ export interface GitHubWorkflowFileStatus {
79
+ ok: boolean;
80
+ exists: boolean | null;
81
+ repository: string;
82
+ workflow: string;
83
+ url: string | null;
84
+ message: string;
85
+ }
86
+ export interface GitHubWorkflowFailureSummaryInput {
87
+ repository?: string | null;
88
+ workflow?: string | null;
89
+ runId?: number | string | null;
90
+ runUrl?: string | null;
91
+ conclusion?: string | null;
92
+ failedJobName?: string | null;
93
+ lastActiveStep?: string | null;
94
+ message?: string | null;
95
+ blockerCode?: string | null;
96
+ retrySafe?: boolean;
97
+ resumeSafe?: boolean;
98
+ }
99
+ export interface GitHubWorkflowFailureSummary {
100
+ summary: string;
101
+ provider: 'github';
102
+ repository: string | null;
103
+ workflow: string | null;
104
+ runId: number | null;
105
+ runUrl: string | null;
106
+ inspectCommand: string | null;
107
+ failedJobName: string | null;
108
+ lastActiveStep: string | null;
109
+ conclusion: string | null;
110
+ retrySafe: boolean;
111
+ resumeSafe: boolean;
112
+ blockerCode: string;
113
+ }
61
114
  export declare function resolveGitHubApiToken(env?: NodeJS.ProcessEnv | Record<string, string | undefined>): string;
62
115
  export declare function parseGitHubRepositorySlug(value: string): {
63
116
  owner: string;
@@ -147,6 +200,36 @@ export declare function upsertGitHubRepositoryVariableWithGhCli(repository: stri
147
200
  }, name: string, value: string, { env, }?: {
148
201
  env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
149
202
  }): void;
203
+ export declare function formatGitHubWorkflowFailure(input?: GitHubWorkflowFailureSummaryInput): GitHubWorkflowFailureSummary;
204
+ export declare function dispatchGitHubWorkflowRun(repository: string | {
205
+ owner: string;
206
+ name: string;
207
+ }, { client, workflow, branch, inputs, }: {
208
+ client?: GitHubApiClient;
209
+ workflow?: string;
210
+ branch: string;
211
+ inputs?: Record<string, string>;
212
+ }): Promise<GitHubWorkflowDispatchResult>;
213
+ export declare function cancelGitHubWorkflowRun(repository: string | {
214
+ owner: string;
215
+ name: string;
216
+ } | null | undefined, runId: number | string | null | undefined, { client }?: {
217
+ client?: GitHubApiClient;
218
+ }): Promise<GitHubWorkflowCancellationResult>;
219
+ export declare function getGitHubWorkflowFileStatus(repository: string | {
220
+ owner: string;
221
+ name: string;
222
+ }, workflow?: string, { client }?: {
223
+ client?: GitHubApiClient;
224
+ }): Promise<GitHubWorkflowFileStatus>;
225
+ export declare function getLatestGitHubWorkflowRun(repository: string | {
226
+ owner: string;
227
+ name: string;
228
+ }, { client, workflow, branch, }?: {
229
+ client?: GitHubApiClient;
230
+ workflow?: string;
231
+ branch?: string | null;
232
+ }): Promise<GitHubWorkflowRunSummary | null>;
150
233
  export declare function waitForGitHubWorkflowRunCompletion(repository: string | {
151
234
  owner: string;
152
235
  name: string;
@@ -548,6 +548,168 @@ function normalizeWorkflowJob(job) {
548
548
  })) : []
549
549
  };
550
550
  }
551
+ function workflowInspectCommand(repository, runId) {
552
+ return repository && runId ? `gh run view ${runId} --repo ${repository} --log-failed` : null;
553
+ }
554
+ function formatGitHubWorkflowFailure(input = {}) {
555
+ const repository = typeof input.repository === "string" && input.repository.trim() ? input.repository.trim() : null;
556
+ const workflow = typeof input.workflow === "string" && input.workflow.trim() ? input.workflow.trim() : null;
557
+ const numericRunId = Number(input.runId);
558
+ const runId = Number.isFinite(numericRunId) && numericRunId > 0 ? numericRunId : null;
559
+ const runUrl = typeof input.runUrl === "string" && input.runUrl.trim() ? input.runUrl.trim() : null;
560
+ const conclusion = typeof input.conclusion === "string" && input.conclusion.trim() ? input.conclusion.trim() : null;
561
+ const failedJobName = typeof input.failedJobName === "string" && input.failedJobName.trim() ? input.failedJobName.trim() : null;
562
+ const lastActiveStep = typeof input.lastActiveStep === "string" && input.lastActiveStep.trim() ? input.lastActiveStep.trim() : null;
563
+ const blockerCode = typeof input.blockerCode === "string" && input.blockerCode.trim() ? input.blockerCode.trim() : conclusion === "cancelled" ? "github_workflow_cancelled" : conclusion === "timed_out" ? "github_workflow_timed_out" : "github_workflow_failed";
564
+ const detail = failedJobName ? ` Failed job: ${failedJobName}.` : lastActiveStep ? ` Last active step: ${lastActiveStep}.` : "";
565
+ const summary = typeof input.message === "string" && input.message.trim() ? input.message.trim() : `${workflow ?? "GitHub workflow"} ${conclusion ? `completed with conclusion ${conclusion}` : "failed"}.${detail}`;
566
+ return {
567
+ summary,
568
+ provider: "github",
569
+ repository,
570
+ workflow,
571
+ runId,
572
+ runUrl,
573
+ inspectCommand: workflowInspectCommand(repository, runId),
574
+ failedJobName,
575
+ lastActiveStep,
576
+ conclusion,
577
+ retrySafe: input.retrySafe ?? true,
578
+ resumeSafe: input.resumeSafe ?? false,
579
+ blockerCode
580
+ };
581
+ }
582
+ async function dispatchGitHubWorkflowRun(repository, {
583
+ client = createGitHubApiClient(),
584
+ workflow = "deploy-web.yml",
585
+ branch,
586
+ inputs
587
+ }) {
588
+ const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
589
+ try {
590
+ const result = await client.rest.actions.createWorkflowDispatch({
591
+ owner,
592
+ repo: name,
593
+ workflow_id: workflow,
594
+ ref: branch,
595
+ inputs
596
+ });
597
+ return {
598
+ repository: `${owner}/${name}`,
599
+ workflow,
600
+ branch,
601
+ inputs,
602
+ status: typeof result.status === "number" ? result.status : null,
603
+ dispatchedAt: (/* @__PURE__ */ new Date()).toISOString()
604
+ };
605
+ } catch (error) {
606
+ throw normalizeGitHubApiError(error, `Unable to dispatch GitHub workflow ${workflow} in ${owner}/${name}`);
607
+ }
608
+ }
609
+ async function cancelGitHubWorkflowRun(repository, runId, { client = createGitHubApiClient() } = {}) {
610
+ if (!repository || !runId) {
611
+ return {
612
+ ok: false,
613
+ supported: false,
614
+ repository: typeof repository === "string" ? repository : null,
615
+ runId: null,
616
+ message: "GitHub workflow cancellation requires a repository and run id."
617
+ };
618
+ }
619
+ const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
620
+ const numericRunId = Number(runId);
621
+ if (!Number.isFinite(numericRunId) || numericRunId <= 0) {
622
+ return {
623
+ ok: false,
624
+ supported: false,
625
+ repository: `${owner}/${name}`,
626
+ runId: null,
627
+ message: "GitHub workflow cancellation requires a numeric run id."
628
+ };
629
+ }
630
+ try {
631
+ await client.rest.actions.cancelWorkflowRun({
632
+ owner,
633
+ repo: name,
634
+ run_id: numericRunId
635
+ });
636
+ return {
637
+ ok: true,
638
+ supported: true,
639
+ repository: `${owner}/${name}`,
640
+ runId: numericRunId,
641
+ url: `https://github.com/${owner}/${name}/actions/runs/${numericRunId}`,
642
+ message: "GitHub workflow cancellation requested.",
643
+ cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
644
+ };
645
+ } catch (error) {
646
+ const message = error instanceof Error ? error.message : String(error ?? "");
647
+ if (/not supported|not found|404/iu.test(message)) {
648
+ return {
649
+ ok: false,
650
+ supported: false,
651
+ repository: `${owner}/${name}`,
652
+ runId: numericRunId,
653
+ message: "GitHub workflow cancellation is not supported for this run."
654
+ };
655
+ }
656
+ throw normalizeGitHubApiError(error, `Unable to cancel GitHub workflow run ${numericRunId} in ${owner}/${name}`);
657
+ }
658
+ }
659
+ async function getGitHubWorkflowFileStatus(repository, workflow = "deploy-web.yml", { client = createGitHubApiClient() } = {}) {
660
+ const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
661
+ const normalizedWorkflow = workflow.replace(/^\.github\/workflows\//u, "");
662
+ const path = `.github/workflows/${normalizedWorkflow}`;
663
+ try {
664
+ const result = await client.rest.repos.getContent({
665
+ owner,
666
+ repo: name,
667
+ path
668
+ });
669
+ const data = result.data;
670
+ return {
671
+ ok: true,
672
+ exists: true,
673
+ repository: `${owner}/${name}`,
674
+ workflow: normalizedWorkflow,
675
+ url: typeof data.html_url === "string" ? data.html_url : `https://github.com/${owner}/${name}/blob/HEAD/${path}`,
676
+ message: `${normalizedWorkflow} is present.`
677
+ };
678
+ } catch (error) {
679
+ const status = typeof error?.status === "number" ? error.status : null;
680
+ if (status === 404) {
681
+ return {
682
+ ok: true,
683
+ exists: false,
684
+ repository: `${owner}/${name}`,
685
+ workflow: normalizedWorkflow,
686
+ url: null,
687
+ message: `${normalizedWorkflow} is missing from ${owner}/${name}.`
688
+ };
689
+ }
690
+ throw normalizeGitHubApiError(error, `Unable to inspect GitHub workflow file ${path} in ${owner}/${name}`);
691
+ }
692
+ }
693
+ async function getLatestGitHubWorkflowRun(repository, {
694
+ client = createGitHubApiClient(),
695
+ workflow = "deploy-web.yml",
696
+ branch
697
+ } = {}) {
698
+ const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
699
+ try {
700
+ const listed = await client.rest.actions.listWorkflowRuns({
701
+ owner,
702
+ repo: name,
703
+ workflow_id: workflow,
704
+ ...branch ? { branch } : {},
705
+ per_page: 1
706
+ });
707
+ const run = listed.data.workflow_runs[0] ?? null;
708
+ return run ? normalizeWorkflowRun(run) : null;
709
+ } catch (error) {
710
+ throw normalizeGitHubApiError(error, `Unable to inspect latest GitHub workflow run ${workflow} in ${owner}/${name}`);
711
+ }
712
+ }
551
713
  async function listWorkflowJobsForProgress(client, owner, repo, runId) {
552
714
  try {
553
715
  const jobs = await client.rest.actions.listJobsForWorkflowRun({
@@ -717,11 +879,16 @@ async function ensureGitHubBranchFromBase(repository, branch, {
717
879
  }
718
880
  }
719
881
  export {
882
+ cancelGitHubWorkflowRun,
720
883
  createGitHubApiClient,
884
+ dispatchGitHubWorkflowRun,
721
885
  ensureGitHubActionsEnvironment,
722
886
  ensureGitHubBranchFromBase,
723
887
  ensureGitHubRepository,
888
+ formatGitHubWorkflowFailure,
724
889
  getGitHubRepository,
890
+ getGitHubWorkflowFileStatus,
891
+ getLatestGitHubWorkflowRun,
725
892
  listGitHubEnvironmentSecretNames,
726
893
  listGitHubEnvironmentVariableNames,
727
894
  listGitHubRepositorySecretNames,
@@ -2,10 +2,10 @@ import { spawn, spawnSync } from "node:child_process";
2
2
  import { resolve } from "node:path";
3
3
  import { runLocalD1Migrations as applyLocalD1Migrations } from "./d1-migration.js";
4
4
  import {
5
- fixtureMigrationsRoot,
6
5
  fixtureRoot,
7
6
  fixtureWranglerConfig,
8
- corePackageRoot
7
+ corePackageRoot,
8
+ sdkD1MigrationsRoot
9
9
  } from "./runtime-paths.js";
10
10
  import { resolveTreeseedToolCommand } from "../../managed-dependencies.js";
11
11
  function mergeEnv(extraEnv = {}) {
@@ -41,7 +41,7 @@ function runLocalD1Migration(persistTo) {
41
41
  applyLocalD1Migrations({
42
42
  cwd: fixtureRoot,
43
43
  wranglerConfig: fixtureWranglerConfig,
44
- migrationsRoot: fixtureMigrationsRoot,
44
+ migrationsRoot: sdkD1MigrationsRoot,
45
45
  persistTo
46
46
  });
47
47
  }
@@ -3,7 +3,18 @@ export type TreeseedMailpitContainer = {
3
3
  image: string;
4
4
  ports: string;
5
5
  };
6
+ type RunDocker = typeof runDocker;
7
+ declare function runDocker(args: string[], options?: {}): import("child_process").SpawnSyncReturns<string>;
6
8
  export declare function dockerIsAvailable(): boolean;
7
- export declare function findRunningMailpitContainer(): any;
8
- export declare function stopKnownMailpitContainers(): boolean;
9
+ export declare function findRunningMailpitContainer(options?: {
10
+ run?: RunDocker;
11
+ }): {
12
+ name: string;
13
+ image: string;
14
+ ports: string;
15
+ } | null;
16
+ export declare function stopKnownMailpitContainers(options?: {
17
+ run?: RunDocker;
18
+ }): boolean;
9
19
  export declare function streamKnownMailpitLogs(): void;
20
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  const EXPECTED_PORTS = ["1025->1025/tcp", "8025->8025/tcp"];
3
- const KNOWN_MAILPIT_NAMES = ["treeseed_mailpit", "karyon_docs_mailpit"];
3
+ const KNOWN_MAILPIT_NAMES = ["treeseed_mailpit", "docs_mailpit"];
4
4
  function runDocker(args, options = {}) {
5
5
  return spawnSync("docker", args, {
6
6
  encoding: "utf8",
@@ -23,24 +23,29 @@ function dockerIsAvailable() {
23
23
  const result = runDocker(["ps", "--format", "{{.Names}} {{.Image}} {{.Ports}}"]);
24
24
  return result.status === 0;
25
25
  }
26
- function findRunningMailpitContainer() {
27
- const result = runDocker(["ps", "--format", "{{.Names}} {{.Image}} {{.Ports}}"]);
26
+ function findKnownMailpitContainers({ all = false, run = runDocker } = {}) {
27
+ const args = [
28
+ "ps",
29
+ ...all ? ["-a"] : [],
30
+ "--format",
31
+ "{{.Names}} {{.Image}} {{.Ports}}"
32
+ ];
33
+ const result = run(args);
28
34
  if (result.status !== 0) {
29
- return null;
35
+ return [];
30
36
  }
31
- return parseDockerPsOutput(result.stdout).find(isCompatibleMailpitContainer) ?? null;
37
+ return parseDockerPsOutput(result.stdout).filter(isCompatibleMailpitContainer);
32
38
  }
33
- function stopKnownMailpitContainers() {
34
- const container = findRunningMailpitContainer();
35
- if (!container) {
39
+ function findRunningMailpitContainer(options = {}) {
40
+ return findKnownMailpitContainers(options).at(0) ?? null;
41
+ }
42
+ function stopKnownMailpitContainers(options = {}) {
43
+ const run = options.run ?? runDocker;
44
+ const containers = findKnownMailpitContainers({ all: true, run });
45
+ if (containers.length === 0) {
36
46
  return true;
37
47
  }
38
- const stopResult = runDocker(["stop", container.name]);
39
- if (stopResult.status !== 0) {
40
- return false;
41
- }
42
- const removeResult = runDocker(["rm", "-f", container.name]);
43
- return removeResult.status === 0;
48
+ return containers.every((container) => run(["rm", "-f", container.name]).status === 0);
44
49
  }
45
50
  function streamKnownMailpitLogs() {
46
51
  const container = findRunningMailpitContainer();