@treeseed/sdk 0.8.3 → 0.8.5

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 (47) hide show
  1. package/dist/capacity.d.ts +33 -0
  2. package/dist/fixture-support.d.ts +1 -1
  3. package/dist/fixture-support.js +5 -5
  4. package/dist/managed-dependencies.js +132 -10
  5. package/dist/operations/services/bootstrap-runner.js +7 -1
  6. package/dist/operations/services/config-runtime.js +13 -4
  7. package/dist/operations/services/github-actions-verification.d.ts +3 -0
  8. package/dist/operations/services/github-actions-verification.js +3 -0
  9. package/dist/operations/services/github-api.d.ts +4 -1
  10. package/dist/operations/services/github-api.js +26 -8
  11. package/dist/operations/services/github-automation.d.ts +14 -5
  12. package/dist/operations/services/github-automation.js +45 -11
  13. package/dist/operations/services/hub-provider-launch.js +9 -8
  14. package/dist/operations/services/project-platform.d.ts +93 -210
  15. package/dist/operations/services/project-platform.js +74 -34
  16. package/dist/operations/services/railway-deploy.d.ts +25 -2
  17. package/dist/operations/services/railway-deploy.js +312 -20
  18. package/dist/operations/services/repository-save-orchestrator.d.ts +8 -0
  19. package/dist/operations/services/repository-save-orchestrator.js +40 -3
  20. package/dist/operations/services/runtime-paths.d.ts +1 -0
  21. package/dist/operations/services/runtime-paths.js +3 -1
  22. package/dist/operations/services/runtime-tools.d.ts +1 -0
  23. package/dist/operations/services/runtime-tools.js +2 -0
  24. package/dist/operations/services/template-registry.js +3 -0
  25. package/dist/platform/contracts.d.ts +9 -0
  26. package/dist/platform/deploy-config.js +28 -0
  27. package/dist/platform/env.yaml +1 -745
  28. package/dist/platform/environment.js +69 -9
  29. package/dist/reconcile/builtin-adapters.js +7 -2
  30. package/dist/scripts/install-managed-dependencies.js +12 -0
  31. package/dist/scripts/tenant-workflow-action.js +11 -9
  32. package/dist/scripts/test-scaffold.js +3 -1
  33. package/dist/scripts/workflow-commands.test.js +10 -6
  34. package/dist/scripts/workspace-command-e2e.js +1 -1
  35. package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +1 -0
  36. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/api/server.js +1 -1
  37. package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +7 -6
  38. package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +6 -0
  39. package/dist/workflow/operations.d.ts +41 -8
  40. package/dist/workflow/operations.js +119 -24
  41. package/dist/workflow/runs.js +31 -0
  42. package/package.json +1 -1
  43. package/templates/github/deploy-processing.workflow.yml +120 -0
  44. package/templates/github/deploy-web.workflow.yml +116 -0
  45. package/templates/github/hosted-project.workflow.yml +4 -4
  46. package/templates/github/deploy.managed.workflow.yml +0 -208
  47. package/templates/github/deploy.workflow.yml +0 -746
@@ -1,5 +1,38 @@
1
1
  import type { CapacityEstimateConfidence, CapacityGrant, CapacityPlan, CapacityProviderLane, CapacityReservation, TaskEstimateProfile } from './sdk-types.ts';
2
2
  import type { AgentProviderProfile } from './types/agents.ts';
3
+ export type ProcessingEnvironment = 'local' | 'staging' | 'prod';
4
+ export interface CapacityProviderRegistration {
5
+ id: string;
6
+ teamId: string;
7
+ providerKind: 'processing-host';
8
+ serviceBaseUrl: string;
9
+ environments: ProcessingEnvironment[];
10
+ capabilities: string[];
11
+ status: 'pending' | 'active' | 'degraded' | 'disabled';
12
+ heartbeatAt: string;
13
+ limits: {
14
+ maxWorkers: number;
15
+ dailyTaskCreditBudget: number;
16
+ maxQueuedTasks: number;
17
+ };
18
+ }
19
+ export interface CapacityProviderHeartbeat {
20
+ providerId: string;
21
+ status: CapacityProviderRegistration['status'];
22
+ heartbeatAt: string;
23
+ queueDepth?: number | null;
24
+ activeWorkers?: number | null;
25
+ draining?: boolean;
26
+ }
27
+ export interface CapacityProviderHealth {
28
+ ok: boolean;
29
+ status: CapacityProviderRegistration['status'];
30
+ capabilities: string[];
31
+ queueDepth: number;
32
+ activeWorkers: number;
33
+ draining: boolean;
34
+ checkedAt: string;
35
+ }
3
36
  export interface CapacityEstimateInput {
4
37
  taskSignature?: string | null;
5
38
  taskKind?: string | null;
@@ -4,7 +4,7 @@ export type FixtureSupportDeclaration = {
4
4
  modes: readonly FixtureInjectionMode[];
5
5
  workspaceDirName?: string;
6
6
  entrySpecifier?: string;
7
- contractsShim?: 'core-agent';
7
+ contractsShim?: 'agent-contracts';
8
8
  };
9
9
  export type ResolveSharedFixtureOptions = {
10
10
  packageRoot?: string;
@@ -106,15 +106,15 @@ function ensureFixtureLinkedPackage(fixtureRoot, packageName, resolvedPackageRoo
106
106
  rmSync(packageDir, { recursive: true, force: true });
107
107
  symlinkSync(resolvedPackageRoot, packageDir, "dir");
108
108
  }
109
- function buildCoreAgentContractsShimPackage(fixtureRoot) {
110
- const packageDir = resolve(fixtureRoot, "node_modules", "@treeseed", "core");
109
+ function buildAgentContractsShimPackage(fixtureRoot) {
110
+ const packageDir = resolve(fixtureRoot, "node_modules", "@treeseed", "agent");
111
111
  rmSync(packageDir, { recursive: true, force: true });
112
112
  mkdirSync(resolve(packageDir, "contracts"), { recursive: true });
113
113
  writeFileSync(
114
114
  resolve(packageDir, "package.json"),
115
115
  JSON.stringify(
116
116
  {
117
- name: "@treeseed/core",
117
+ name: "@treeseed/agent",
118
118
  type: "module",
119
119
  exports: {
120
120
  "./runtime-types": {
@@ -315,8 +315,8 @@ function prepareFixturePackages(options) {
315
315
  satisfied = true;
316
316
  break;
317
317
  }
318
- if (mode === "contracts-only" && declaration.contractsShim === "core-agent") {
319
- buildCoreAgentContractsShimPackage(options.fixtureRoot);
318
+ if (mode === "contracts-only" && declaration.contractsShim === "agent-contracts") {
319
+ buildAgentContractsShimPackage(options.fixtureRoot);
320
320
  satisfied = true;
321
321
  break;
322
322
  }
@@ -18,7 +18,13 @@ const GH_ASSETS = [
18
18
  ];
19
19
  const NPM_TOOLS = [
20
20
  { name: "wrangler", packageName: "wrangler", binName: "wrangler", version: "4.86.0" },
21
- { name: "railway", packageName: "@railway/cli", binName: "railway", version: "4.44.0" },
21
+ {
22
+ name: "railway",
23
+ packageName: "@railway/cli",
24
+ binName: "railway",
25
+ version: "4.44.0",
26
+ runtimeBinary: (packageRoot) => resolve(packageRoot, "bin", process.platform === "win32" ? "railway.exe" : "railway")
27
+ },
22
28
  { name: "copilot", packageName: "@github/copilot", binName: "copilot", version: "1.0.39" },
23
29
  { name: "copilot-language-server", packageName: "@github/copilot-language-server", binName: "copilot-language-server", version: "1.480.0" }
24
30
  ];
@@ -186,11 +192,45 @@ function resolvePackageRoot(packageName) {
186
192
  function findNpmTool(name) {
187
193
  return NPM_TOOLS.find((tool) => tool.name === name) ?? null;
188
194
  }
195
+ function resolveNpmToolRuntimeBinary(tool) {
196
+ const packageJsonPath = resolvePackageJsonPathOptional(tool.packageName);
197
+ if (!packageJsonPath) {
198
+ return null;
199
+ }
200
+ const packageRoot = dirname(packageJsonPath);
201
+ const packageBin = resolvePackageBinaryOptional(tool.packageName, tool.binName);
202
+ if (!packageBin || !existsSync(packageBin)) {
203
+ return null;
204
+ }
205
+ if (!tool.runtimeBinary) {
206
+ return packageBin;
207
+ }
208
+ const runtimeBinary = tool.runtimeBinary(packageRoot);
209
+ return existsSync(runtimeBinary) ? runtimeBinary : null;
210
+ }
211
+ function npmToolMissingDetail(tool) {
212
+ const packageJsonPath = resolvePackageJsonPathOptional(tool.packageName);
213
+ if (!packageJsonPath) {
214
+ return `${tool.packageName} is missing from the installed package graph.`;
215
+ }
216
+ const packageRoot = dirname(packageJsonPath);
217
+ const packageBin = resolvePackageBinaryOptional(tool.packageName, tool.binName);
218
+ if (!packageBin || !existsSync(packageBin)) {
219
+ return `${tool.packageName} binary ${tool.binName} is missing from the installed package.`;
220
+ }
221
+ if (tool.runtimeBinary) {
222
+ const runtimeBinary = tool.runtimeBinary(packageRoot);
223
+ if (!existsSync(runtimeBinary)) {
224
+ return `${tool.packageName} runtime binary ${runtimeBinary} is missing. Run \`npx trsd install --json\` or npm install without --ignore-scripts.`;
225
+ }
226
+ }
227
+ return `${tool.packageName} is unavailable.`;
228
+ }
189
229
  function npmBackedDependenciesAvailable() {
190
230
  try {
191
231
  for (const tool of NPM_TOOLS) {
192
- const binaryPath = resolvePackageBinary(tool.packageName, tool.binName);
193
- if (!existsSync(binaryPath)) {
232
+ const binaryPath = resolveNpmToolRuntimeBinary(tool);
233
+ if (!binaryPath || !existsSync(binaryPath)) {
194
234
  return false;
195
235
  }
196
236
  }
@@ -228,6 +268,21 @@ function resolveNpmInstallCommand(env = process.env) {
228
268
  display: ["npm", "install", "--no-audit", "--no-fund"]
229
269
  };
230
270
  }
271
+ function resolveNpmRebuildCommand(env = process.env, packageNames) {
272
+ const npmExecPath = env.npm_execpath || env.NPM_EXEC_PATH;
273
+ if (npmExecPath?.trim()) {
274
+ return {
275
+ command: process.execPath,
276
+ args: [npmExecPath, "rebuild", ...packageNames],
277
+ display: [process.execPath, npmExecPath, "rebuild", ...packageNames]
278
+ };
279
+ }
280
+ return {
281
+ command: "npm",
282
+ args: ["rebuild", ...packageNames],
283
+ display: ["npm", "rebuild", ...packageNames]
284
+ };
285
+ }
231
286
  function runNpmBootstrap(options) {
232
287
  const tenantRoot = options.tenantRoot ? resolve(options.tenantRoot) : null;
233
288
  const npmCommand = resolveNpmInstallCommand(options.env);
@@ -251,6 +306,16 @@ function runNpmBootstrap(options) {
251
306
  }
252
307
  const nodeModulesMissing = !existsSync(resolve(tenantRoot, "node_modules"));
253
308
  const npmDepsMissing = !npmBackedDependenciesAvailable();
309
+ const missingRuntimeTools = npmToolsMissingRuntime();
310
+ if (!options.force && !nodeModulesMissing && npmDepsMissing && missingRuntimeTools.length > 0) {
311
+ return [{
312
+ root: tenantRoot,
313
+ command: npmCommand.display,
314
+ status: "already-present",
315
+ exitCode: 0,
316
+ detail: `npm dependencies are installed; rebuilding missing runtime tools: ${missingRuntimeTools.map((tool) => tool.packageName).join(", ")}.`
317
+ }];
318
+ }
254
319
  if (!options.force && !nodeModulesMissing && !npmDepsMissing) {
255
320
  return [{
256
321
  root: tenantRoot,
@@ -292,11 +357,65 @@ ${result.stdout ?? ""}`.trim() || result.error?.message || "";
292
357
  detail: ok ? detail || "npm install completed successfully." : detail || "npm install failed."
293
358
  }];
294
359
  }
360
+ function npmToolsMissingRuntime() {
361
+ return NPM_TOOLS.filter((tool) => !resolveNpmToolRuntimeBinary(tool));
362
+ }
363
+ function runNpmToolRebuilds(options) {
364
+ const missingRuntimeTools = npmToolsMissingRuntime();
365
+ if (missingRuntimeTools.length === 0) {
366
+ return [];
367
+ }
368
+ const tenantRoot = options.tenantRoot ? resolve(options.tenantRoot) : null;
369
+ const npmCommand = resolveNpmRebuildCommand(options.env, missingRuntimeTools.map((tool) => tool.packageName));
370
+ if (!tenantRoot || !existsSync(resolve(tenantRoot, "package.json"))) {
371
+ return [{
372
+ root: tenantRoot,
373
+ command: npmCommand.display,
374
+ status: "skipped",
375
+ exitCode: null,
376
+ detail: tenantRoot ? `No package.json found in ${tenantRoot}; npm rebuild skipped.` : "No tenant root was provided; npm rebuild skipped."
377
+ }];
378
+ }
379
+ if (options.env.TREESEED_MANAGED_NPM_INSTALL === "1") {
380
+ return [{
381
+ root: tenantRoot,
382
+ command: npmCommand.display,
383
+ status: "skipped",
384
+ exitCode: null,
385
+ detail: "npm rebuild skipped because TREESEED_MANAGED_NPM_INSTALL=1 is set."
386
+ }];
387
+ }
388
+ options.write?.(`Rebuilding npm-backed Treeseed tools in ${tenantRoot}...`);
389
+ const result = options.spawn(npmCommand.command, npmCommand.args, {
390
+ cwd: tenantRoot,
391
+ env: {
392
+ ...options.env,
393
+ TREESEED_MANAGED_NPM_INSTALL: "1"
394
+ },
395
+ stdio: "pipe",
396
+ encoding: "utf8"
397
+ });
398
+ const detail = `${result.stderr ?? ""}
399
+ ${result.stdout ?? ""}`.trim() || result.error?.message || "";
400
+ const stillMissing = npmToolsMissingRuntime().map((tool) => tool.packageName);
401
+ const ok = result.status === 0 && !result.error && stillMissing.length === 0;
402
+ return [{
403
+ root: tenantRoot,
404
+ command: npmCommand.display,
405
+ status: ok ? "installed" : "failed",
406
+ exitCode: result.status ?? (ok ? 0 : 1),
407
+ detail: ok ? detail || "npm-backed Treeseed tools rebuilt successfully." : [
408
+ detail || "npm-backed Treeseed tool rebuild failed.",
409
+ stillMissing.length > 0 ? `Missing runtime tools after rebuild: ${stillMissing.join(", ")}` : ""
410
+ ].filter(Boolean).join("\n")
411
+ }];
412
+ }
295
413
  function formatTreeseedDependencyFailureDetails(result) {
296
414
  const npmFailures = result.npmInstalls.filter((entry) => entry.status === "failed").map((entry) => {
297
415
  const root = entry.root ?? "no tenant root";
298
416
  const exit = entry.exitCode === null ? "unknown exit code" : `exit code ${entry.exitCode}`;
299
- return `npm install in ${root}: ${entry.command.join(" ")} failed with ${exit}${entry.detail ? `: ${entry.detail}` : ""}`;
417
+ const operation = entry.command.includes("rebuild") ? "npm rebuild" : "npm install";
418
+ return `${operation} in ${root}: ${entry.command.join(" ")} failed with ${exit}${entry.detail ? `: ${entry.detail}` : ""}`;
300
419
  });
301
420
  const toolFailures = result.reports.filter((entry) => entry.required && ["failed", "missing", "unsupported"].includes(entry.status)).map((entry) => `${entry.name}: ${entry.detail}`);
302
421
  return [...npmFailures, ...toolFailures].join("\n- ") || "No dependency failure details were reported.";
@@ -311,7 +430,7 @@ function resolveTreeseedToolBinary(toolName, options = {}) {
311
430
  }
312
431
  const npmTool = findNpmTool(toolName);
313
432
  if (npmTool) {
314
- return resolvePackageBinaryOptional(npmTool.packageName, npmTool.binName);
433
+ return resolveNpmToolRuntimeBinary(npmTool);
315
434
  }
316
435
  if (toolName === "git" || toolName === "docker") {
317
436
  return locateSystemBinary(toolName, spawnSync, options.env ?? process.env);
@@ -323,7 +442,7 @@ function resolveTreeseedToolCommand(toolName, options = {}) {
323
442
  if (!binaryPath) {
324
443
  return null;
325
444
  }
326
- if (findNpmTool(toolName)) {
445
+ if (findNpmTool(toolName) && /\.(?:cjs|mjs|js)$/u.test(binaryPath)) {
327
446
  return { command: process.execPath, argsPrefix: [binaryPath], binaryPath };
328
447
  }
329
448
  return { command: binaryPath, argsPrefix: [], binaryPath };
@@ -339,7 +458,7 @@ function invocationForTool(toolName, env = process.env) {
339
458
  };
340
459
  }
341
460
  return {
342
- mode: findNpmTool(toolName) ? "node" : "direct",
461
+ mode: findNpmTool(toolName) && /\.(?:cjs|mjs|js)$/u.test(command.binaryPath) ? "node" : "direct",
343
462
  command: command.command,
344
463
  argsPrefix: command.argsPrefix,
345
464
  binaryPath: command.binaryPath
@@ -546,7 +665,7 @@ async function installGh(options) {
546
665
  }
547
666
  function statusForNpmTool(tool, options) {
548
667
  try {
549
- const binaryPath = resolvePackageBinaryOptional(tool.packageName, tool.binName);
668
+ const binaryPath = resolveNpmToolRuntimeBinary(tool);
550
669
  return report({
551
670
  name: tool.name,
552
671
  kind: "npm",
@@ -555,7 +674,7 @@ function statusForNpmTool(tool, options) {
555
674
  binaryPath,
556
675
  status: binaryPath && existsSync(binaryPath) ? "already-present" : "missing",
557
676
  required: true,
558
- detail: binaryPath && existsSync(binaryPath) ? `${tool.packageName} is available from the Treeseed SDK dependency graph.` : `${tool.packageName} binary ${tool.binName} is missing from the installed package.`
677
+ detail: binaryPath && existsSync(binaryPath) ? `${tool.packageName} is available from the Treeseed SDK dependency graph.` : npmToolMissingDetail(tool)
559
678
  });
560
679
  } catch (error) {
561
680
  return report({
@@ -681,7 +800,10 @@ async function installTreeseedDependencies(options = {}) {
681
800
  };
682
801
  mkdirSync(resolveToolsHome(env), { recursive: true });
683
802
  mkdirSync(createTreeseedManagedToolEnv(env).GH_CONFIG_DIR, { recursive: true });
684
- const npmInstalls = runNpmBootstrap(effectiveOptions);
803
+ const npmInstalls = [
804
+ ...runNpmBootstrap(effectiveOptions),
805
+ ...runNpmToolRebuilds(effectiveOptions)
806
+ ];
685
807
  const reports = [
686
808
  systemStatus("git", true, effectiveOptions),
687
809
  await installGh(effectiveOptions),
@@ -73,9 +73,15 @@ async function runPrefixedCommand(command, args, {
73
73
  prefix
74
74
  }) {
75
75
  return await new Promise((resolvePromise, reject) => {
76
+ const childEnv = {};
77
+ for (const [key, value] of Object.entries({ ...process.env, ...env })) {
78
+ if (value !== void 0) {
79
+ childEnv[key] = String(value);
80
+ }
81
+ }
76
82
  const child = spawn(command, args, {
77
83
  cwd,
78
- env: { ...process.env, ...env },
84
+ env: childEnv,
79
85
  stdio: ["pipe", "pipe", "pipe"]
80
86
  });
81
87
  let stdout = "";
@@ -1225,11 +1225,9 @@ function ensureTreeseedRailwayIgnoreEntries(tenantRoot) {
1225
1225
  ".treeseed/",
1226
1226
  ".wrangler/",
1227
1227
  "coverage/",
1228
- "dist/",
1229
1228
  "node_modules/",
1230
1229
  "npm-debug.log*",
1231
1230
  "packages/*/.git/",
1232
- "packages/*/dist/",
1233
1231
  "packages/*/node_modules/",
1234
1232
  "public/__treeseed/*.json",
1235
1233
  "public/books/*.json",
@@ -1239,9 +1237,20 @@ function ensureTreeseedRailwayIgnoreEntries(tenantRoot) {
1239
1237
  "*.log",
1240
1238
  "*.tgz"
1241
1239
  ];
1240
+ const removedEntries = /* @__PURE__ */ new Set([
1241
+ "dist/",
1242
+ "**/dist/",
1243
+ "packages/*/dist/"
1244
+ ]);
1242
1245
  const current = existsSync(railwayIgnorePath) ? readFileSync(railwayIgnorePath, "utf8") : "";
1243
- const lines = current.split(/\r?\n/);
1244
1246
  let changed = false;
1247
+ const lines = current.split(/\r?\n/).filter((line) => {
1248
+ const keep = !removedEntries.has(line.trim());
1249
+ if (!keep) {
1250
+ changed = true;
1251
+ }
1252
+ return keep;
1253
+ });
1245
1254
  for (const entry of requiredEntries) {
1246
1255
  if (!lines.includes(entry)) {
1247
1256
  lines.push(entry);
@@ -2363,7 +2372,7 @@ function createConfigReadiness(values, validation) {
2363
2372
  configured: providerIssues("cloudflare").length === 0
2364
2373
  },
2365
2374
  railway: {
2366
- configured: providerIssues("railway").length === 0
2375
+ configured: validConfigValue("RAILWAY_API_TOKEN") && providerIssues("railway").length === 0
2367
2376
  },
2368
2377
  localDevelopment: {
2369
2378
  configured: localDevelopmentIssues.length === 0
@@ -18,6 +18,9 @@ export type GitHubActionsWorkflowGate = {
18
18
  headSha: string;
19
19
  timeoutSeconds?: number;
20
20
  pollSeconds?: number;
21
+ dispatchIfMissing?: boolean;
22
+ dispatchAfterSeconds?: number;
23
+ dispatchInputs?: Record<string, string>;
21
24
  };
22
25
  export type GitHubActionsWorkflowJobStep = {
23
26
  name: string;
@@ -541,6 +541,9 @@ async function waitForGitHubActionsGate(gate, options = {}) {
541
541
  branch: gate.branch,
542
542
  timeoutSeconds: gate.timeoutSeconds ?? options.timeoutSeconds,
543
543
  pollSeconds: gate.pollSeconds ?? options.pollSeconds,
544
+ dispatchIfMissing: gate.dispatchIfMissing ?? gate.workflow === "verify.yml",
545
+ dispatchAfterSeconds: gate.dispatchAfterSeconds ?? 75,
546
+ dispatchInputs: gate.dispatchInputs,
544
547
  onProgress: reportProgress
545
548
  });
546
549
  }
@@ -150,13 +150,16 @@ export declare function upsertGitHubRepositoryVariableWithGhCli(repository: stri
150
150
  export declare function waitForGitHubWorkflowRunCompletion(repository: string | {
151
151
  owner: string;
152
152
  name: string;
153
- }, { client, workflow, headSha, branch, timeoutSeconds, pollSeconds, onProgress, }?: {
153
+ }, { client, workflow, headSha, branch, timeoutSeconds, pollSeconds, dispatchIfMissing, dispatchAfterSeconds, dispatchInputs, onProgress, }?: {
154
154
  client?: GitHubApiClient;
155
155
  workflow?: string;
156
156
  headSha?: string | null;
157
157
  branch?: string | null;
158
158
  timeoutSeconds?: number;
159
159
  pollSeconds?: number;
160
+ dispatchIfMissing?: boolean;
161
+ dispatchAfterSeconds?: number;
162
+ dispatchInputs?: Record<string, string>;
160
163
  onProgress?: (event: GitHubWorkflowProgressEvent) => void;
161
164
  }): Promise<{
162
165
  status: string;
@@ -196,13 +196,13 @@ async function paginateNames(request) {
196
196
  async function listGitHubRepositorySecretNames(repository, { client = createGitHubApiClient() } = {}) {
197
197
  const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
198
198
  try {
199
- return await paginateNames(
199
+ return await withGitHubApiRetries(() => paginateNames(
200
200
  () => client.paginate(client.rest.actions.listRepoSecrets, {
201
201
  owner,
202
202
  repo: name,
203
203
  per_page: 100
204
204
  })
205
- );
205
+ ));
206
206
  } catch (error) {
207
207
  throw normalizeGitHubApiError(error, `Unable to list GitHub secrets for ${owner}/${name}`);
208
208
  }
@@ -210,13 +210,13 @@ async function listGitHubRepositorySecretNames(repository, { client = createGitH
210
210
  async function listGitHubRepositoryVariableNames(repository, { client = createGitHubApiClient() } = {}) {
211
211
  const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
212
212
  try {
213
- return await paginateNames(
213
+ return await withGitHubApiRetries(() => paginateNames(
214
214
  () => client.paginate(client.rest.actions.listRepoVariables, {
215
215
  owner,
216
216
  repo: name,
217
217
  per_page: 100
218
218
  })
219
- );
219
+ ));
220
220
  } catch (error) {
221
221
  throw normalizeGitHubApiError(error, `Unable to list GitHub variables for ${owner}/${name}`);
222
222
  }
@@ -322,11 +322,11 @@ async function paginateGitHubEnvironmentNames(client, route, params) {
322
322
  async function listGitHubEnvironmentSecretNames(repository, environmentName, { client = createGitHubApiClient() } = {}) {
323
323
  const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
324
324
  try {
325
- return await paginateGitHubEnvironmentNames(
325
+ return await withGitHubApiRetries(() => paginateGitHubEnvironmentNames(
326
326
  client,
327
327
  "GET /repos/{owner}/{repo}/environments/{environment_name}/secrets",
328
328
  { owner, repo: name, environment_name: environmentName }
329
- );
329
+ ));
330
330
  } catch (error) {
331
331
  throw normalizeGitHubApiError(error, `Unable to list GitHub environment secrets for ${owner}/${name}:${environmentName}`);
332
332
  }
@@ -334,11 +334,11 @@ async function listGitHubEnvironmentSecretNames(repository, environmentName, { c
334
334
  async function listGitHubEnvironmentVariableNames(repository, environmentName, { client = createGitHubApiClient() } = {}) {
335
335
  const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
336
336
  try {
337
- return await paginateGitHubEnvironmentNames(
337
+ return await withGitHubApiRetries(() => paginateGitHubEnvironmentNames(
338
338
  client,
339
339
  "GET /repos/{owner}/{repo}/environments/{environment_name}/variables",
340
340
  { owner, repo: name, environment_name: environmentName }
341
- );
341
+ ));
342
342
  } catch (error) {
343
343
  throw normalizeGitHubApiError(error, `Unable to list GitHub environment variables for ${owner}/${name}:${environmentName}`);
344
344
  }
@@ -571,10 +571,14 @@ async function waitForGitHubWorkflowRunCompletion(repository, {
571
571
  branch,
572
572
  timeoutSeconds = 600,
573
573
  pollSeconds = 5,
574
+ dispatchIfMissing = false,
575
+ dispatchAfterSeconds = 60,
576
+ dispatchInputs,
574
577
  onProgress
575
578
  } = {}) {
576
579
  const { owner, name } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
577
580
  const startedAt = Date.now();
581
+ let dispatchedMissingRun = false;
578
582
  let lastProgress = null;
579
583
  const emitProgress = (type, run = null, jobs = []) => {
580
584
  const completedJobs = jobs.filter((job) => job.status === "completed");
@@ -610,6 +614,20 @@ async function waitForGitHubWorkflowRunCompletion(repository, {
610
614
  const match = listed.data.workflow_runs.map((run) => normalizeWorkflowRun(run)).find((run) => (!headSha || run.headSha === headSha) && (!branch || run.headBranch === branch));
611
615
  if (!match?.id) {
612
616
  emitProgress("waiting");
617
+ if (dispatchIfMissing && branch && !dispatchedMissingRun && Date.now() - startedAt >= dispatchAfterSeconds * 1e3) {
618
+ try {
619
+ await client.rest.actions.createWorkflowDispatch({
620
+ owner,
621
+ repo: name,
622
+ workflow_id: workflow,
623
+ ref: branch,
624
+ inputs: dispatchInputs
625
+ });
626
+ dispatchedMissingRun = true;
627
+ } catch (error) {
628
+ throw normalizeGitHubApiError(error, `Unable to dispatch GitHub workflow ${workflow} in ${owner}/${name}`);
629
+ }
630
+ }
613
631
  await sleep(pollSeconds * 1e3);
614
632
  continue;
615
633
  }
@@ -77,18 +77,26 @@ export declare function requiredGitHubEnvironment(tenantRoot: any, { scope, purp
77
77
  variables: string[];
78
78
  };
79
79
  export declare function requiredGitHubSecrets(tenantRoot: any): string[];
80
- export declare function renderDeployWorkflow({ workingDirectory, executionBoundary }: {
80
+ export declare function renderDeployWebWorkflow({ workingDirectory }: {
81
+ workingDirectory: any;
82
+ }): string;
83
+ export declare function renderDeployProcessingWorkflow({ workingDirectory }: {
81
84
  workingDirectory: any;
82
- executionBoundary?: string | undefined;
83
85
  }): string;
84
86
  export declare function renderHostedProjectWorkflow({ workingDirectory }: {
85
87
  workingDirectory: any;
86
88
  }): string;
87
89
  export declare function ensureDeployWorkflow(tenantRoot: any): {
88
- workingDirectory: string;
89
- executionBoundary: string;
90
90
  workflowPath: string;
91
91
  changed: boolean;
92
+ workingDirectory: string;
93
+ executionBoundary: string;
94
+ additionalWorkflows: {
95
+ workingDirectory: string;
96
+ executionBoundary: string;
97
+ workflowPath: string;
98
+ changed: boolean;
99
+ }[];
92
100
  };
93
101
  export declare function ensureHostedProjectWorkflow(tenantRoot: any): {
94
102
  workingDirectory: string;
@@ -197,10 +205,11 @@ export declare function ensureGitHubDeployAutomation(tenantRoot: any, { dryRun,
197
205
  skipped?: undefined;
198
206
  };
199
207
  }>;
200
- export declare function waitForGitHubWorkflowCompletion(tenantRoot: any, { repository, workflow, headSha, branch, timeoutSeconds, pollSeconds, onProgress, }?: {
208
+ export declare function waitForGitHubWorkflowCompletion(tenantRoot: any, { repository, workflow, headSha, branch, timeoutSeconds, pollSeconds, dispatchIfMissing, dispatchAfterSeconds, dispatchInputs, onProgress, }?: {
201
209
  workflow?: string | undefined;
202
210
  timeoutSeconds?: number | undefined;
203
211
  pollSeconds?: number | undefined;
212
+ dispatchIfMissing?: boolean | undefined;
204
213
  }): Promise<{
205
214
  status: string;
206
215
  repository: string;
@@ -279,7 +279,6 @@ function requiredGitHubSecrets(tenantRoot) {
279
279
  function renderTenantWorkflowActionCommand() {
280
280
  return [
281
281
  "EXTRA_ARGS=()",
282
- 'if [[ "${TREESEED_WORKFLOW_SKIP_PROVISION:-}" == "1" ]]; then EXTRA_ARGS+=(--skip-provision); fi',
283
282
  'if [[ -n "${TREESEED_WORKFLOW_PROJECT:-}" ]]; then EXTRA_ARGS+=(--project-id "${TREESEED_WORKFLOW_PROJECT}"); fi',
284
283
  'if [[ -n "${TREESEED_WORKFLOW_PREVIEW_ID:-}" ]]; then EXTRA_ARGS+=(--preview-id "${TREESEED_WORKFLOW_PREVIEW_ID}"); fi',
285
284
  "if test -f ./packages/sdk/scripts/tenant-workflow-action.ts; then",
@@ -305,11 +304,11 @@ function renderWorkflowTemplate(templateName, { workingDirectory }) {
305
304
  normalizedWorkingDirectory === "." ? "package-lock.json" : `${normalizedWorkingDirectory}/package-lock.json`
306
305
  );
307
306
  }
308
- function deployWorkflowTemplateName(executionBoundary = "direct") {
309
- return executionBoundary === "managed" ? "deploy.managed.workflow.yml" : "deploy.workflow.yml";
307
+ function renderDeployWebWorkflow({ workingDirectory }) {
308
+ return renderWorkflowTemplate("deploy-web.workflow.yml", { workingDirectory });
310
309
  }
311
- function renderDeployWorkflow({ workingDirectory, executionBoundary = "direct" }) {
312
- return renderWorkflowTemplate(deployWorkflowTemplateName(executionBoundary), { workingDirectory });
310
+ function renderDeployProcessingWorkflow({ workingDirectory }) {
311
+ return renderWorkflowTemplate("deploy-processing.workflow.yml", { workingDirectory });
313
312
  }
314
313
  function renderHostedProjectWorkflow({ workingDirectory }) {
315
314
  return renderWorkflowTemplate("hosted-project.workflow.yml", { workingDirectory });
@@ -324,16 +323,44 @@ function ensureWorkflowFile(tenantRoot, fileName, expected) {
324
323
  writeFileSync(workflowPath, expected, "utf8");
325
324
  return { workflowPath, changed: true };
326
325
  }
326
+ function hasConcreteProcessingServices(deployConfig) {
327
+ return Object.entries(deployConfig.services ?? {}).some(
328
+ ([serviceKey, service]) => ["api", "manager", "worker", "workerRunner", "workdayStart", "workdayReport"].includes(serviceKey) && service && service.enabled !== false
329
+ );
330
+ }
331
+ function shouldWriteProcessingWorkflow(deployConfig) {
332
+ if ((deployConfig.hosting?.kind ?? "self_hosted_project") === "market_control_plane") {
333
+ return true;
334
+ }
335
+ const mode = deployConfig.processing?.mode ?? "market-assigned";
336
+ return mode === "project-owned" || mode === "local" || hasConcreteProcessingServices(deployConfig);
337
+ }
327
338
  function ensureDeployWorkflow(tenantRoot) {
328
339
  const repositoryRoot = resolveGitRepositoryRoot(tenantRoot);
329
340
  const workingDirectory = relative(repositoryRoot, tenantRoot).replaceAll("\\", "/") || ".";
330
341
  const deployConfig = loadCliDeployConfig(tenantRoot);
331
- const executionBoundary = usesManagedHostOperationRequests(deployConfig) ? "managed" : "direct";
332
- const expected = renderDeployWorkflow({ workingDirectory, executionBoundary });
342
+ const web = ensureWorkflowFile(tenantRoot, "deploy-web.yml", renderDeployWebWorkflow({ workingDirectory }));
343
+ const additionalWorkflows = [];
344
+ let changed = web.changed;
345
+ if (shouldWriteProcessingWorkflow(deployConfig)) {
346
+ const processing = ensureWorkflowFile(
347
+ tenantRoot,
348
+ "deploy-processing.yml",
349
+ renderDeployProcessingWorkflow({ workingDirectory })
350
+ );
351
+ changed = changed || processing.changed;
352
+ additionalWorkflows.push({
353
+ ...processing,
354
+ workingDirectory,
355
+ executionBoundary: "split-plane"
356
+ });
357
+ }
333
358
  return {
334
- ...ensureWorkflowFile(tenantRoot, "deploy.yml", expected),
359
+ workflowPath: web.workflowPath,
360
+ changed,
335
361
  workingDirectory,
336
- executionBoundary
362
+ executionBoundary: "split-plane",
363
+ additionalWorkflows
337
364
  };
338
365
  }
339
366
  function ensureHostedProjectWorkflow(tenantRoot) {
@@ -348,7 +375,7 @@ function ensureHostedProjectWorkflow(tenantRoot) {
348
375
  function ensureStandardizedGitHubWorkflows(tenantRoot) {
349
376
  const deployConfig = loadCliDeployConfig(tenantRoot);
350
377
  const deploy = ensureDeployWorkflow(tenantRoot);
351
- const workflows = [deploy];
378
+ const workflows = [deploy, ...deploy.additionalWorkflows ?? []];
352
379
  if ((deployConfig.hosting?.kind ?? "self_hosted_project") === "market_control_plane") {
353
380
  workflows.push(ensureHostedProjectWorkflow(tenantRoot));
354
381
  }
@@ -465,6 +492,9 @@ async function waitForGitHubWorkflowCompletion(tenantRoot, {
465
492
  branch,
466
493
  timeoutSeconds = 600,
467
494
  pollSeconds = 5,
495
+ dispatchIfMissing = false,
496
+ dispatchAfterSeconds,
497
+ dispatchInputs,
468
498
  onProgress
469
499
  } = {}) {
470
500
  const repo = repository ?? resolveGitHubRepositorySlug(tenantRoot);
@@ -475,6 +505,9 @@ async function waitForGitHubWorkflowCompletion(tenantRoot, {
475
505
  branch,
476
506
  timeoutSeconds,
477
507
  pollSeconds,
508
+ dispatchIfMissing,
509
+ dispatchAfterSeconds,
510
+ dispatchInputs,
478
511
  onProgress
479
512
  });
480
513
  }
@@ -494,7 +527,8 @@ export {
494
527
  listGitHubVariableNames,
495
528
  maybeResolveGitHubRepositorySlug,
496
529
  parseGitHubRepositoryFromRemote,
497
- renderDeployWorkflow,
530
+ renderDeployProcessingWorkflow,
531
+ renderDeployWebWorkflow,
498
532
  renderHostedProjectWorkflow,
499
533
  requiredGitHubEnvironment,
500
534
  requiredGitHubSecrets,