@treeseed/sdk 0.6.34 → 0.6.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/operations/services/build-warning-policy.js +85 -0
- package/dist/operations/services/github-actions-verification.d.ts +7 -1
- package/dist/operations/services/github-actions-verification.js +72 -6
- package/dist/operations/services/release-candidate.js +40 -4
- package/dist/operations/services/repository-save-orchestrator.d.ts +10 -0
- package/dist/operations/services/repository-save-orchestrator.js +47 -6
- package/dist/scripts/check-build-warnings.js +14 -31
- package/dist/workflow-state.d.ts +13 -0
- package/dist/workflow-state.js +27 -1
- package/package.json +1 -1
|
@@ -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
|
|
436
|
+
function activeJobSummaries(event) {
|
|
437
437
|
const activeJobs = event.activeJobs ?? [];
|
|
438
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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 (
|
|
83
|
-
|
|
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)
|
|
180
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
73
|
-
|
|
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.');
|
package/dist/workflow-state.d.ts
CHANGED
|
@@ -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[];
|
package/dist/workflow-state.js
CHANGED
|
@@ -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
|
};
|