@treeseed/sdk 0.6.34 → 0.6.35

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.
@@ -0,0 +1,85 @@
1
+ export const DEFAULT_BUILD_WARNING_RULES = [
2
+ {
3
+ label: 'vite-browser-external-libsodium-url',
4
+ pattern: /Module "url" has been externalized for browser compatibility, imported by ".*libsodium-sumo.*"/u,
5
+ },
6
+ ];
7
+
8
+ export function createBuildWarningRules(options = {}) {
9
+ const useDefaultPolicy = options.useDefaultPolicy !== false;
10
+ const customAllow = Array.isArray(options.allow) ? options.allow : [];
11
+ return [
12
+ ...(useDefaultPolicy ? DEFAULT_BUILD_WARNING_RULES : []),
13
+ ...customAllow.map((pattern) => ({
14
+ label: `custom:${pattern}`,
15
+ pattern: pattern instanceof RegExp ? pattern : new RegExp(String(pattern)),
16
+ })),
17
+ ];
18
+ }
19
+
20
+ export function classifyBuildWarningLine(line, options = {}) {
21
+ const value = String(line ?? '');
22
+ if (!value.includes('[WARN]')) {
23
+ return { kind: 'not-warning' };
24
+ }
25
+ const allowed = createBuildWarningRules(options).find((rule) => rule.pattern.test(value));
26
+ if (allowed) {
27
+ return { kind: 'allowed', label: allowed.label };
28
+ }
29
+ return { kind: 'unexpected', line: value };
30
+ }
31
+
32
+ export function createBuildWarningSummary() {
33
+ const allowedWarnings = new Map();
34
+ const unexpectedWarnings = [];
35
+ return {
36
+ allowedWarnings,
37
+ unexpectedWarnings,
38
+ record(line, options = {}) {
39
+ const classified = classifyBuildWarningLine(line, options);
40
+ if (classified.kind === 'allowed') {
41
+ allowedWarnings.set(classified.label, (allowedWarnings.get(classified.label) ?? 0) + 1);
42
+ return classified;
43
+ }
44
+ if (classified.kind === 'unexpected') {
45
+ unexpectedWarnings.push(classified.line);
46
+ }
47
+ return classified;
48
+ },
49
+ };
50
+ }
51
+
52
+ export function mergeAllowedBuildWarnings(target, source) {
53
+ for (const [label, count] of source.entries()) {
54
+ target.set(label, (target.get(label) ?? 0) + count);
55
+ }
56
+ return target;
57
+ }
58
+
59
+ export function countAllowedBuildWarnings(allowedWarnings) {
60
+ return [...allowedWarnings.values()].reduce((sum, count) => sum + count, 0);
61
+ }
62
+
63
+ export function formatAllowedBuildWarnings(allowedWarnings) {
64
+ const total = countAllowedBuildWarnings(allowedWarnings);
65
+ if (total === 0) {
66
+ return [];
67
+ }
68
+ return [
69
+ `Allowed build warnings: ${total}`,
70
+ ...[...allowedWarnings.entries()]
71
+ .sort(([left], [right]) => left.localeCompare(right))
72
+ .map(([label, count]) => `- ${label}: ${count}`),
73
+ ];
74
+ }
75
+
76
+ export function scanBuildWarningText(text, options = {}) {
77
+ const summary = createBuildWarningSummary();
78
+ for (const line of String(text ?? '').split(/\r?\n/u)) {
79
+ summary.record(line, options);
80
+ }
81
+ return {
82
+ allowedWarnings: summary.allowedWarnings,
83
+ unexpectedWarnings: summary.unexpectedWarnings,
84
+ };
85
+ }
@@ -1,4 +1,4 @@
1
- import { type GitHubApiClient } from './github-api.ts';
1
+ import { type GitHubApiClient, type GitHubWorkflowProgressEvent } from './github-api.ts';
2
2
  export type GitHubActionsWorkflowState = 'success' | 'failure' | 'pending' | 'missing' | 'not_pushed' | 'error';
3
3
  export type GitHubActionsVerificationTarget = {
4
4
  name: string;
@@ -118,6 +118,12 @@ export declare function skippedGitHubActionsGate(gate: GitHubActionsWorkflowGate
118
118
  updatedAt: null;
119
119
  };
120
120
  export declare function formatGitHubActionsGateFailure(gate: GitHubActionsWorkflowGate, result: Record<string, unknown>): string;
121
+ export declare function createGitHubActionsGateProgressReporter(gate: GitHubActionsWorkflowGate, options?: {
122
+ operation?: string;
123
+ now?: () => number;
124
+ minRepeatMs?: number;
125
+ onProgress?: (message: string, stream?: 'stdout' | 'stderr') => void;
126
+ }): (event: GitHubWorkflowProgressEvent) => void;
121
127
  export declare function waitForGitHubActionsGate(gate: GitHubActionsWorkflowGate, options?: {
122
128
  timeoutSeconds?: number;
123
129
  pollSeconds?: number;
@@ -433,13 +433,15 @@ function formatElapsed(seconds) {
433
433
  function shortSha(value) {
434
434
  return value ? value.slice(0, 12) : "(unknown)";
435
435
  }
436
- function activeJobSummary(event) {
436
+ function activeJobSummaries(event) {
437
437
  const activeJobs = event.activeJobs ?? [];
438
- if (activeJobs.length === 0) return "";
439
- const summaries = activeJobs.slice(0, 2).map((job) => {
438
+ return activeJobs.slice(0, 2).map((job) => {
440
439
  const activeStep = (job.steps ?? []).find((step) => step.status && step.status !== "completed");
441
440
  return activeStep?.name ? `${job.name} > ${activeStep.name}` : job.name;
442
441
  }).filter(Boolean);
442
+ }
443
+ function activeJobSummary(event) {
444
+ const summaries = activeJobSummaries(event);
443
445
  return summaries.length > 0 ? `; active: ${summaries.join(", ")}` : "";
444
446
  }
445
447
  function failedJobSummary(event) {
@@ -463,8 +465,73 @@ function formatGitHubActionsGateProgress(gate, event, operation) {
463
465
  const run = event.runId ? ` run ${event.runId}` : "";
464
466
  return `${prefix}${run} ${status}${activeJobSummary(event)}${url} (${formatElapsed(event.elapsedSeconds)} elapsed)`;
465
467
  }
468
+ function progressCompactKey(gate, event) {
469
+ const active = activeJobSummaries(event).join(",");
470
+ return [
471
+ gate.name,
472
+ event.workflow,
473
+ event.runId ?? "none",
474
+ event.type,
475
+ event.status ?? "none",
476
+ event.conclusion ?? "none",
477
+ active
478
+ ].join("|");
479
+ }
480
+ function formatCompactedGitHubActionsGateProgress(gate, event, operation, repeatedPolls, lastChangeSeconds) {
481
+ const prefix = `[${operation}][gate][${gate.name}] ${event.workflow}`;
482
+ const run = event.runId ? ` run ${event.runId}` : "";
483
+ const active = activeJobSummaries(event);
484
+ const activeText = active.length > 0 ? active.join(", ") : event.status ?? "waiting";
485
+ const url = event.url ? `: ${event.url}` : "";
486
+ return `${prefix}${run} still active: ${activeText} (${repeatedPolls} polls, ${formatElapsed(lastChangeSeconds)} since last change)${url}`;
487
+ }
488
+ function createGitHubActionsGateProgressReporter(gate, options = {}) {
489
+ const operation = options.operation ?? "workflow";
490
+ const now = options.now ?? (() => Date.now());
491
+ const minRepeatMs = options.minRepeatMs ?? 6e4;
492
+ let lastKey = null;
493
+ let lastChangeAt = now();
494
+ let lastEmitAt = 0;
495
+ let repeatedPolls = 0;
496
+ return (event) => {
497
+ if (event.type === "completed") {
498
+ options.onProgress?.(formatGitHubActionsGateProgress(gate, event, operation));
499
+ lastKey = null;
500
+ repeatedPolls = 0;
501
+ lastEmitAt = now();
502
+ lastChangeAt = lastEmitAt;
503
+ return;
504
+ }
505
+ const currentKey = progressCompactKey(gate, event);
506
+ const currentTime = now();
507
+ if (currentKey !== lastKey) {
508
+ lastKey = currentKey;
509
+ lastChangeAt = currentTime;
510
+ lastEmitAt = currentTime;
511
+ repeatedPolls = 0;
512
+ options.onProgress?.(formatGitHubActionsGateProgress(gate, event, operation));
513
+ return;
514
+ }
515
+ repeatedPolls += 1;
516
+ if (currentTime - lastEmitAt >= minRepeatMs) {
517
+ options.onProgress?.(formatCompactedGitHubActionsGateProgress(
518
+ gate,
519
+ event,
520
+ operation,
521
+ repeatedPolls,
522
+ Math.max(0, Math.round((currentTime - lastChangeAt) / 1e3))
523
+ ));
524
+ lastEmitAt = currentTime;
525
+ repeatedPolls = 0;
526
+ }
527
+ };
528
+ }
466
529
  async function waitForGitHubActionsGate(gate, options = {}) {
467
530
  const { waitForGitHubWorkflowCompletion } = await import("./github-automation.js");
531
+ const reportProgress = createGitHubActionsGateProgressReporter(gate, {
532
+ operation: options.operation ?? "workflow",
533
+ onProgress: options.onProgress
534
+ });
468
535
  return await waitForGitHubWorkflowCompletion(gate.repoPath, {
469
536
  repository: gate.repository,
470
537
  workflow: gate.workflow,
@@ -472,12 +539,11 @@ async function waitForGitHubActionsGate(gate, options = {}) {
472
539
  branch: gate.branch,
473
540
  timeoutSeconds: options.timeoutSeconds,
474
541
  pollSeconds: options.pollSeconds,
475
- onProgress: (event) => {
476
- options.onProgress?.(formatGitHubActionsGateProgress(gate, event, options.operation ?? "workflow"));
477
- }
542
+ onProgress: reportProgress
478
543
  });
479
544
  }
480
545
  export {
546
+ createGitHubActionsGateProgressReporter,
481
547
  formatGitHubActionsGateFailure,
482
548
  inspectGitHubActionsVerification,
483
549
  skippedGitHubActionsGate,
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { spawnSync } from "node:child_process";
2
3
  import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
4
  import { tmpdir } from "node:os";
4
5
  import { dirname, join, relative, resolve } from "node:path";
@@ -10,6 +11,7 @@ import { collectTreeseedEnvironmentContext, resolveTreeseedMachineEnvironmentVal
10
11
  import { loadDeployState } from "./deploy.js";
11
12
  import { loadCliDeployConfig } from "./runtime-tools.js";
12
13
  import { packagesWithScript, run, workspacePackages } from "./workspace-tools.js";
14
+ import { createBuildWarningSummary, formatAllowedBuildWarnings } from "./build-warning-policy.js";
13
15
  const RELEASE_CANDIDATE_CACHE_DIR = ".treeseed/workflow/release-candidates";
14
16
  const STABLE_SEMVER = /^\d+\.\d+\.\d+$/u;
15
17
  const REHEARSAL_IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
@@ -224,9 +226,43 @@ function rehearsalVerifyScript(root) {
224
226
  }
225
227
  return null;
226
228
  }
229
+ function runNpmRehearsalCommand(args, options) {
230
+ const result = spawnSync("npm", args, {
231
+ cwd: options.cwd,
232
+ env: process.env,
233
+ stdio: "pipe",
234
+ encoding: "utf8",
235
+ timeout: options.timeoutMs
236
+ });
237
+ const stdout = result.stdout ?? "";
238
+ const stderr = result.stderr ?? "";
239
+ if (result.status !== 0) {
240
+ if (stdout) process.stdout.write(stdout);
241
+ if (stderr) process.stderr.write(stderr);
242
+ const message = (result.error?.message ? `${result.error.message}
243
+ ` : "") + (stderr.trim() || stdout.trim() || `npm ${args.join(" ")} failed`);
244
+ throw new Error(message);
245
+ }
246
+ const warningSummary = createBuildWarningSummary();
247
+ const emitFiltered = (text, stream) => {
248
+ for (const line of text.split(/\r?\n/u)) {
249
+ if (!line) continue;
250
+ const classified = warningSummary.record(line);
251
+ if (classified.kind === "allowed") continue;
252
+ stream.write(`${line}
253
+ `);
254
+ }
255
+ };
256
+ emitFiltered(stdout, process.stdout);
257
+ emitFiltered(stderr, process.stderr);
258
+ for (const line of formatAllowedBuildWarnings(warningSummary.allowedWarnings)) {
259
+ process.stdout.write(`${line}
260
+ `);
261
+ }
262
+ }
227
263
  function buildRehearsalWorkspacePackageArtifacts(root) {
228
264
  for (const pkg of packagesWithScript("build:dist", root)) {
229
- run("npm", ["--prefix", pkg.dir, "run", "build:dist"], { cwd: root, timeoutMs: 3e5 });
265
+ runNpmRehearsalCommand(["--prefix", pkg.dir, "run", "build:dist"], { cwd: root, timeoutMs: 3e5 });
230
266
  }
231
267
  }
232
268
  function runProductionDependencyRehearsal(root, plannedVersions, selectedPackageNames, failures) {
@@ -239,12 +275,12 @@ function runProductionDependencyRehearsal(root, plannedVersions, selectedPackage
239
275
  const copied = copyWorkspaceForProductionRehearsal(root);
240
276
  tempParent = copied.tempParent;
241
277
  applyPlannedStableMetadata(copied.tempRoot, plannedVersions);
242
- run("npm", ["install", "--package-lock-only", "--ignore-scripts"], { cwd: copied.tempRoot, timeoutMs: 3e5 });
243
- run("npm", ["ci", "--ignore-scripts"], { cwd: copied.tempRoot, timeoutMs: 6e5 });
278
+ runNpmRehearsalCommand(["install", "--package-lock-only", "--ignore-scripts"], { cwd: copied.tempRoot, timeoutMs: 3e5 });
279
+ runNpmRehearsalCommand(["ci", "--ignore-scripts"], { cwd: copied.tempRoot, timeoutMs: 6e5 });
244
280
  buildRehearsalWorkspacePackageArtifacts(copied.tempRoot);
245
281
  const scriptName = rehearsalVerifyScript(copied.tempRoot);
246
282
  if (scriptName) {
247
- run("npm", ["run", scriptName], { cwd: copied.tempRoot, timeoutMs: 9e5 });
283
+ runNpmRehearsalCommand(["run", scriptName], { cwd: copied.tempRoot, timeoutMs: 9e5 });
248
284
  }
249
285
  const postInstallIssues = collectInternalDevReferenceIssues(copied.tempRoot, selectedPackageSet);
250
286
  if (postInstallIssues.length > 0) {
@@ -1,6 +1,7 @@
1
1
  import { type GitRemoteWriteMode } from './git-remote-policy.ts';
2
2
  import { type CommitMessageContext, type CommitMessageProvider, type CommitMessageProviderMode } from './commit-message-provider.ts';
3
3
  import { type DevDependencyReferenceMode, type GitDependencyProtocol } from './package-reference-policy.ts';
4
+ import { type BuildWarningPolicyOptions } from './build-warning-policy.js';
4
5
  export type RepoKind = 'package' | 'project';
5
6
  export type RepoBranchMode = 'package-release-main' | 'package-dev-save' | 'project-save';
6
7
  export type SaveVerifyMode = 'action-first' | 'local-only' | 'skip';
@@ -150,6 +151,15 @@ export type RepositorySaveOptions = {
150
151
  stablePackageRelease?: boolean;
151
152
  onProgress?: (message: string, stream?: 'stdout' | 'stderr') => void;
152
153
  };
154
+ export declare function runStreamingCommand(node: Pick<RepositorySaveNode, 'name' | 'path'>, options: Pick<RepositorySaveOptions, 'onProgress'>, phase: string, command: string, args: string[], commandOptions?: {
155
+ cwd?: string;
156
+ env?: NodeJS.ProcessEnv;
157
+ timeoutMs?: number;
158
+ buildWarningPolicy?: BuildWarningPolicyOptions | false;
159
+ }): Promise<{
160
+ stdout: string;
161
+ stderr: string;
162
+ }>;
153
163
  export declare function nextDevVersion(version: string, branch: string, date?: Date): string;
154
164
  export declare function discoverRepositorySaveNodes(root: string, gitRoot?: string, branch?: string, options?: {
155
165
  stablePackageRelease?: boolean;
@@ -39,6 +39,10 @@ import {
39
39
  workspacePackages
40
40
  } from "./workspace-tools.js";
41
41
  import { collectDeploymentLockfileWorkspaceIssues } from "./workspace-dependency-mode.js";
42
+ import {
43
+ createBuildWarningSummary,
44
+ formatAllowedBuildWarnings
45
+ } from "./build-warning-policy.js";
42
46
  class RepositorySaveError extends Error {
43
47
  exitCode;
44
48
  details;
@@ -79,8 +83,10 @@ function runCapturedCommand(node, options, phase, command, args, commandOptions
79
83
  });
80
84
  const stdout = result.stdout?.trim() ?? "";
81
85
  const stderr = result.stderr?.trim() ?? "";
82
- if (stdout) emitProgress(options, node, phase, stdout);
83
- if (stderr) emitProgress(options, node, phase, stderr, "stderr");
86
+ if (commandOptions.emitOutputOnSuccess !== false) {
87
+ if (stdout) emitProgress(options, node, phase, stdout);
88
+ if (stderr) emitProgress(options, node, phase, stderr, "stderr");
89
+ }
84
90
  if (result.status !== 0) {
85
91
  const message = (result.error?.message ? `${result.error.message}
86
92
  ` : "") + (prefixedOutput(node, phase, stderr) || prefixedOutput(node, phase, stdout) || `${progressPrefix(node, phase)} ${command} ${args.join(" ")} failed`);
@@ -94,6 +100,21 @@ function runCapturedCommand(node, options, phase, command, args, commandOptions
94
100
  }
95
101
  return stdout;
96
102
  }
103
+ function npmLockfilePackageCount(repoDir) {
104
+ try {
105
+ const lockfile = readJson(resolve(repoDir, "package-lock.json"));
106
+ const packages = lockfile.packages;
107
+ if (packages && typeof packages === "object" && !Array.isArray(packages)) {
108
+ return Math.max(0, Object.keys(packages).filter((entry) => entry !== "").length);
109
+ }
110
+ const dependencies = lockfile.dependencies;
111
+ if (dependencies && typeof dependencies === "object" && !Array.isArray(dependencies)) {
112
+ return Object.keys(dependencies).length;
113
+ }
114
+ } catch {
115
+ }
116
+ return null;
117
+ }
97
118
  function isNoOpGitCommitError(error) {
98
119
  if (!(error instanceof RepositorySaveError)) return false;
99
120
  const command = typeof error.details?.command === "string" ? error.details.command : "";
@@ -140,6 +161,7 @@ async function runStreamingCommand(node, options, phase, command, args, commandO
140
161
  let stdoutRemainder = "";
141
162
  let stderrRemainder = "";
142
163
  let settled = false;
164
+ const warningSummary = commandOptions.buildWarningPolicy === false ? null : createBuildWarningSummary();
143
165
  const flush = (chunk, stream) => {
144
166
  const combined = stream === "stdout" ? stdoutRemainder + chunk : stderrRemainder + chunk;
145
167
  const parts = combined.split(/\r?\n/u);
@@ -147,6 +169,10 @@ async function runStreamingCommand(node, options, phase, command, args, commandO
147
169
  if (stream === "stdout") stdoutRemainder = parts.at(-1) ?? "";
148
170
  else stderrRemainder = parts.at(-1) ?? "";
149
171
  for (const line of complete) {
172
+ const classified = warningSummary?.record(line, commandOptions.buildWarningPolicy || void 0);
173
+ if (classified?.kind === "allowed") {
174
+ continue;
175
+ }
150
176
  emitProgress(options, node, phase, line, stream);
151
177
  }
152
178
  };
@@ -176,9 +202,20 @@ async function runStreamingCommand(node, options, phase, command, args, commandO
176
202
  if (settled) return;
177
203
  settled = true;
178
204
  if (timeout) clearTimeout(timeout);
179
- if (stdoutRemainder) emitProgress(options, node, phase, stdoutRemainder);
180
- if (stderrRemainder) emitProgress(options, node, phase, stderrRemainder, "stderr");
205
+ if (stdoutRemainder) {
206
+ const classified = warningSummary?.record(stdoutRemainder, commandOptions.buildWarningPolicy || void 0);
207
+ if (classified?.kind !== "allowed") emitProgress(options, node, phase, stdoutRemainder);
208
+ }
209
+ if (stderrRemainder) {
210
+ const classified = warningSummary?.record(stderrRemainder, commandOptions.buildWarningPolicy || void 0);
211
+ if (classified?.kind !== "allowed") emitProgress(options, node, phase, stderrRemainder, "stderr");
212
+ }
181
213
  if (code === 0) {
214
+ if (warningSummary) {
215
+ for (const line of formatAllowedBuildWarnings(warningSummary.allowedWarnings)) {
216
+ emitProgress(options, node, phase, line);
217
+ }
218
+ }
182
219
  resolvePromise({ stdout, stderr });
183
220
  return;
184
221
  }
@@ -644,7 +681,10 @@ async function validateRepositoryLockfile(node, options) {
644
681
  return { status: "skipped", command: commandText, issues: [], error: "stubbed" };
645
682
  }
646
683
  try {
647
- runCapturedCommand(node, options, "lockfile", command, args, { timeoutMs: 12e4 });
684
+ runCapturedCommand(node, options, "lockfile", command, args, { timeoutMs: 12e4, emitOutputOnSuccess: false });
685
+ const packageCount = npmLockfilePackageCount(node.path);
686
+ const countText = packageCount === null ? "package-lock entries" : `${packageCount} package${packageCount === 1 ? "" : "s"}`;
687
+ emitProgress(options, node, "lockfile", `Lockfile validation passed: ${countText} checked, 0 issues.`);
648
688
  return { status: "passed", command: commandText, issues: [], error: null };
649
689
  } catch (error) {
650
690
  const message = error instanceof Error ? error.message : String(error);
@@ -1520,5 +1560,6 @@ export {
1520
1560
  refreshAndValidateRootWorkspaceLockfileForSave,
1521
1561
  repositorySaveErrorDetails,
1522
1562
  repositorySaveWaves,
1523
- runRepositorySaveOrchestrator
1563
+ runRepositorySaveOrchestrator,
1564
+ runStreamingCommand
1524
1565
  };
@@ -2,14 +2,12 @@
2
2
 
3
3
  import { readFileSync } from 'node:fs';
4
4
  import { resolve } from 'node:path';
5
+ import {
6
+ formatAllowedBuildWarnings,
7
+ scanBuildWarningText,
8
+ } from '../operations/services/build-warning-policy.js';
5
9
 
6
10
  const args = process.argv.slice(2);
7
- const defaultAllowlisted = [
8
- {
9
- label: 'vite-browser-external-libsodium-url',
10
- pattern: /Module "url" has been externalized for browser compatibility, imported by ".*libsodium-sumo.*"/u,
11
- },
12
- ];
13
11
  const allowlisted = [];
14
12
  const files = [];
15
13
  let useDefaultPolicy = true;
@@ -25,10 +23,7 @@ for (let index = 0; index < args.length; index += 1) {
25
23
  if (!pattern) {
26
24
  throw new Error('Missing value for --allow.');
27
25
  }
28
- allowlisted.push({
29
- label: `custom:${pattern}`,
30
- pattern: new RegExp(pattern),
31
- });
26
+ allowlisted.push(pattern);
32
27
  index += 1;
33
28
  continue;
34
29
  }
@@ -41,24 +36,16 @@ if (files.length === 0) {
41
36
 
42
37
  const warningLines = [];
43
38
  const allowedWarnings = new Map();
44
- const effectiveAllowlisted = [
45
- ...(useDefaultPolicy ? defaultAllowlisted : []),
46
- ...allowlisted,
47
- ];
48
39
  for (const file of files) {
49
40
  const contents = readFileSync(resolve(process.cwd(), file), 'utf8');
50
- for (const line of contents.split(/\r?\n/u)) {
51
- if (!line.includes('[WARN]')) {
52
- continue;
53
- }
54
- const allowed = effectiveAllowlisted.find((rule) => rule.pattern.test(line));
55
- if (allowed) {
56
- const current = allowedWarnings.get(allowed.label) ?? 0;
57
- allowedWarnings.set(allowed.label, current + 1);
58
- continue;
59
- }
60
- warningLines.push(line);
41
+ const scan = scanBuildWarningText(contents, {
42
+ useDefaultPolicy,
43
+ allow: allowlisted,
44
+ });
45
+ for (const [label, count] of scan.allowedWarnings.entries()) {
46
+ allowedWarnings.set(label, (allowedWarnings.get(label) ?? 0) + count);
61
47
  }
48
+ warningLines.push(...scan.unexpectedWarnings);
62
49
  }
63
50
 
64
51
  if (warningLines.length > 0) {
@@ -69,11 +56,7 @@ if (warningLines.length > 0) {
69
56
  process.exit(1);
70
57
  }
71
58
 
72
- const allowedTotal = [...allowedWarnings.values()].reduce((sum, count) => sum + count, 0);
73
- if (allowedTotal > 0) {
74
- console.log(`Allowed build warnings: ${allowedTotal}`);
75
- for (const [label, count] of [...allowedWarnings.entries()].sort(([left], [right]) => left.localeCompare(right))) {
76
- console.log(`- ${label}: ${count}`);
77
- }
59
+ for (const line of formatAllowedBuildWarnings(allowedWarnings)) {
60
+ console.log(line);
78
61
  }
79
62
  console.log('No unexpected build warnings detected.');
@@ -35,6 +35,7 @@ export type TreeseedWorkflowProviderStatus = Record<'local' | 'staging' | 'prod'
35
35
  }>;
36
36
  export type TreeseedWorkflowStatusOptions = {
37
37
  live?: boolean;
38
+ history?: 'recent' | 'all';
38
39
  env?: NodeJS.ProcessEnv;
39
40
  };
40
41
  export type TreeseedWorkflowState = {
@@ -75,6 +76,9 @@ export type TreeseedWorkflowState = {
75
76
  updatedAt: string;
76
77
  reasons: string[];
77
78
  }>;
79
+ historyMode: 'recent' | 'all';
80
+ obsoleteRunsTotal: number;
81
+ obsoleteRunsOmitted: number;
78
82
  blockers: string[];
79
83
  };
80
84
  packageSync: {
@@ -218,5 +222,14 @@ export type TreeseedWorkflowState = {
218
222
  }>;
219
223
  recommendations: TreeseedWorkflowRecommendation[];
220
224
  };
225
+ export declare function capObsoleteWorkflowRuns<T>(obsoleteRuns: T[], options?: {
226
+ history?: 'recent' | 'all';
227
+ limit?: number;
228
+ }): {
229
+ historyMode: string;
230
+ obsoleteRuns: T[];
231
+ obsoleteRunsTotal: number;
232
+ obsoleteRunsOmitted: number;
233
+ };
221
234
  export declare function resolveTreeseedWorkflowState(cwd: string, options?: TreeseedWorkflowStatusOptions): TreeseedWorkflowState;
222
235
  export declare function recommendTreeseedNextSteps(state: TreeseedWorkflowState): TreeseedWorkflowRecommendation[];
@@ -293,6 +293,27 @@ function safeReleaseHistory(repoDir) {
293
293
  };
294
294
  }
295
295
  }
296
+ const DEFAULT_OBSOLETE_RUN_HISTORY_LIMIT = 20;
297
+ function capObsoleteWorkflowRuns(obsoleteRuns, options = {}) {
298
+ const historyMode = options.history === "all" ? "all" : "recent";
299
+ const limit = options.limit ?? DEFAULT_OBSOLETE_RUN_HISTORY_LIMIT;
300
+ const obsoleteRunsTotal = obsoleteRuns.length;
301
+ if (historyMode === "all") {
302
+ return {
303
+ historyMode,
304
+ obsoleteRuns,
305
+ obsoleteRunsTotal,
306
+ obsoleteRunsOmitted: 0
307
+ };
308
+ }
309
+ const cappedRuns = obsoleteRuns.slice(0, limit);
310
+ return {
311
+ historyMode,
312
+ obsoleteRuns: cappedRuns,
313
+ obsoleteRunsTotal,
314
+ obsoleteRunsOmitted: Math.max(0, obsoleteRunsTotal - cappedRuns.length)
315
+ };
316
+ }
296
317
  function resolveLocalStatusUrl(deployConfig) {
297
318
  return deployConfig.surfaces?.web?.localBaseUrl ?? deployConfig.surfaces?.api?.localBaseUrl ?? Object.values(deployConfig.services ?? {}).find((service) => service?.enabled !== false && service.environments?.local?.baseUrl)?.environments?.local?.baseUrl ?? null;
298
319
  }
@@ -409,6 +430,7 @@ function resolveTreeseedWorkflowState(cwd, options = {}) {
409
430
  updatedAt: journal.updatedAt,
410
431
  reasons: classification.reasons
411
432
  }));
433
+ const obsoleteHistory = capObsoleteWorkflowRuns(obsoleteRuns, { history: options.history });
412
434
  const workflowBlockers = [];
413
435
  if (workflowLock.active && workflowLock.lock) {
414
436
  workflowBlockers.push(`Workflow lock active for ${workflowLock.lock.command} (${workflowLock.lock.runId}).`);
@@ -442,7 +464,10 @@ function resolveTreeseedWorkflowState(cwd, options = {}) {
442
464
  },
443
465
  interruptedRuns,
444
466
  staleRuns,
445
- obsoleteRuns,
467
+ obsoleteRuns: obsoleteHistory.obsoleteRuns,
468
+ historyMode: obsoleteHistory.historyMode,
469
+ obsoleteRunsTotal: obsoleteHistory.obsoleteRunsTotal,
470
+ obsoleteRunsOmitted: obsoleteHistory.obsoleteRunsOmitted,
446
471
  blockers: workflowBlockers
447
472
  },
448
473
  packageSync: {
@@ -783,6 +808,7 @@ function recommendTreeseedNextSteps(state) {
783
808
  return recommendations.slice(0, 3);
784
809
  }
785
810
  export {
811
+ capObsoleteWorkflowRuns,
786
812
  recommendTreeseedNextSteps,
787
813
  resolveTreeseedWorkflowState
788
814
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.6.34",
3
+ "version": "0.6.35",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {