@treeseed/sdk 0.6.33 → 0.6.34
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/deploy.d.ts +1 -0
- package/dist/operations/services/deploy.js +37 -0
- package/dist/operations/services/git-workflow.d.ts +18 -1
- package/dist/operations/services/git-workflow.js +42 -8
- package/dist/operations/services/github-actions-verification.d.ts +2 -0
- package/dist/operations/services/github-actions-verification.js +3 -1
- package/dist/operations/services/github-api.d.ts +4 -0
- package/dist/operations/services/github-api.js +5 -1
- package/dist/operations/services/github-automation.d.ts +2 -0
- package/dist/scripts/check-build-warnings.js +20 -3
- package/dist/workflow/operations.d.ts +12 -0
- package/dist/workflow/operations.js +37 -0
- package/dist/workflow-state.d.ts +1 -0
- package/dist/workflow-state.js +16 -4
- package/package.json +1 -1
|
@@ -998,6 +998,7 @@ export declare function runRemoteD1Migrations(tenantRoot: any, options?: {}): {
|
|
|
998
998
|
};
|
|
999
999
|
export declare function markDeploymentInitialized(tenantRoot: any, options?: {}): any;
|
|
1000
1000
|
export declare function markManagedServicesInitialized(tenantRoot: any, options?: {}): any;
|
|
1001
|
+
export declare function recordHostedDeploymentState(tenantRoot: any, options?: {}): any;
|
|
1001
1002
|
export declare function assertDeploymentInitialized(tenantRoot: any, options?: {}): any;
|
|
1002
1003
|
export declare function finalizeDeploymentState(tenantRoot: any, options?: {}): any;
|
|
1003
1004
|
export declare function printDeploySummary(summary: any): void;
|
|
@@ -2096,6 +2096,42 @@ function markManagedServicesInitialized(tenantRoot, options = {}) {
|
|
|
2096
2096
|
writeDeployState(tenantRoot, state, { target });
|
|
2097
2097
|
return state;
|
|
2098
2098
|
}
|
|
2099
|
+
function recordHostedDeploymentState(tenantRoot, options = {}) {
|
|
2100
|
+
const target = normalizeTarget(options.scope ?? options.target ?? "prod");
|
|
2101
|
+
const deployConfig = loadTenantDeployConfig(tenantRoot);
|
|
2102
|
+
const state = loadDeployState(tenantRoot, deployConfig, { target });
|
|
2103
|
+
const timestamp = typeof options.timestamp === "string" && options.timestamp.trim() ? options.timestamp.trim() : (/* @__PURE__ */ new Date()).toISOString();
|
|
2104
|
+
const deployedUrl = typeof options.url === "string" && options.url.trim() ? options.url.trim() : state.lastDeployedUrl ?? resolveConfiguredSurfaceBaseUrl(deployConfig, target, "web");
|
|
2105
|
+
const commit = typeof options.commit === "string" && options.commit.trim() ? options.commit.trim() : null;
|
|
2106
|
+
state.lastDeployedUrl = deployedUrl;
|
|
2107
|
+
state.lastDeploymentTimestamp = timestamp;
|
|
2108
|
+
state.lastDeployedCommit = commit;
|
|
2109
|
+
state.readiness = {
|
|
2110
|
+
...state.readiness ?? {},
|
|
2111
|
+
initialized: true,
|
|
2112
|
+
configured: true,
|
|
2113
|
+
provisioned: true,
|
|
2114
|
+
deployable: true,
|
|
2115
|
+
phase: "provisioned",
|
|
2116
|
+
initializedAt: state.readiness?.initializedAt ?? timestamp,
|
|
2117
|
+
lastValidatedAt: timestamp,
|
|
2118
|
+
blockers: [],
|
|
2119
|
+
warnings: state.readiness?.warnings ?? []
|
|
2120
|
+
};
|
|
2121
|
+
const nextHistoryEntry = {
|
|
2122
|
+
commit,
|
|
2123
|
+
timestamp,
|
|
2124
|
+
url: deployedUrl,
|
|
2125
|
+
target: deployTargetLabel(target),
|
|
2126
|
+
source: options.source ?? "hosted-github-workflow",
|
|
2127
|
+
workflow: options.workflow ?? null,
|
|
2128
|
+
runId: options.runId ?? null
|
|
2129
|
+
};
|
|
2130
|
+
const history = Array.isArray(state.deploymentHistory) ? state.deploymentHistory : [];
|
|
2131
|
+
state.deploymentHistory = [...history, nextHistoryEntry].slice(-20);
|
|
2132
|
+
writeDeployState(tenantRoot, state, { target });
|
|
2133
|
+
return state;
|
|
2134
|
+
}
|
|
2099
2135
|
function assertDeploymentInitialized(tenantRoot, options = {}) {
|
|
2100
2136
|
const target = normalizeTarget(options.scope ?? options.target ?? "prod");
|
|
2101
2137
|
const deployConfig = loadTenantDeployConfig(tenantRoot);
|
|
@@ -2225,6 +2261,7 @@ export {
|
|
|
2225
2261
|
queueId,
|
|
2226
2262
|
queueName,
|
|
2227
2263
|
reconcileCloudflareWebCacheRules,
|
|
2264
|
+
recordHostedDeploymentState,
|
|
2228
2265
|
resolveCloudflareZoneIdForHost,
|
|
2229
2266
|
resolveConfiguredCloudflareAccountId,
|
|
2230
2267
|
resolveConfiguredSurfaceBaseUrl,
|
|
@@ -119,15 +119,32 @@ export declare function mergeCurrentBranchIntoStaging(cwd: any, featureBranch: a
|
|
|
119
119
|
committed: boolean;
|
|
120
120
|
commitSha: string;
|
|
121
121
|
pushed: boolean;
|
|
122
|
+
generatedMetadataReconciliation: {
|
|
123
|
+
commitSha: null;
|
|
124
|
+
resolved: boolean;
|
|
125
|
+
repoDir: any;
|
|
126
|
+
targetBranch: string;
|
|
127
|
+
reconciledFiles: string[];
|
|
128
|
+
allConflictsWereGeneratedMetadata: boolean;
|
|
129
|
+
} | null;
|
|
122
130
|
};
|
|
123
|
-
export declare function squashMergeBranchIntoStaging(cwd: any, featureBranch: any, message: any, { pushTarget }?: {
|
|
131
|
+
export declare function squashMergeBranchIntoStaging(cwd: any, featureBranch: any, message: any, { pushTarget, reportGeneratedMetadataReconciliation }?: {
|
|
124
132
|
pushTarget?: boolean | undefined;
|
|
133
|
+
reportGeneratedMetadataReconciliation?: boolean | undefined;
|
|
125
134
|
}): {
|
|
126
135
|
repoDir: string;
|
|
127
136
|
targetBranch: string;
|
|
128
137
|
committed: boolean;
|
|
129
138
|
commitSha: string;
|
|
130
139
|
pushed: boolean;
|
|
140
|
+
generatedMetadataReconciliation: {
|
|
141
|
+
commitSha: null;
|
|
142
|
+
resolved: boolean;
|
|
143
|
+
repoDir: any;
|
|
144
|
+
targetBranch: string;
|
|
145
|
+
reconciledFiles: string[];
|
|
146
|
+
allConflictsWereGeneratedMetadata: boolean;
|
|
147
|
+
} | null;
|
|
131
148
|
};
|
|
132
149
|
export declare function currentManagedBranch(cwd?: any): string;
|
|
133
150
|
export declare function isTaskBranch(branchName: any): boolean;
|
|
@@ -28,14 +28,34 @@ function conflictedFiles(repoDir) {
|
|
|
28
28
|
}
|
|
29
29
|
function resolveGeneratedPackageMetadataConflicts(repoDir) {
|
|
30
30
|
const files = conflictedFiles(repoDir);
|
|
31
|
-
if (files.length === 0)
|
|
31
|
+
if (files.length === 0) {
|
|
32
|
+
return {
|
|
33
|
+
resolved: false,
|
|
34
|
+
repoDir,
|
|
35
|
+
targetBranch: STAGING_BRANCH,
|
|
36
|
+
reconciledFiles: [],
|
|
37
|
+
allConflictsWereGeneratedMetadata: false
|
|
38
|
+
};
|
|
39
|
+
}
|
|
32
40
|
const generatedMetadataFiles = /* @__PURE__ */ new Set(["package.json", "package-lock.json"]);
|
|
33
41
|
if (files.some((file) => !generatedMetadataFiles.has(file))) {
|
|
34
|
-
return
|
|
42
|
+
return {
|
|
43
|
+
resolved: false,
|
|
44
|
+
repoDir,
|
|
45
|
+
targetBranch: STAGING_BRANCH,
|
|
46
|
+
reconciledFiles: files,
|
|
47
|
+
allConflictsWereGeneratedMetadata: false
|
|
48
|
+
};
|
|
35
49
|
}
|
|
36
50
|
runGit(["checkout", "--theirs", "--", ...files], { cwd: repoDir });
|
|
37
51
|
runGit(["add", "--", ...files], { cwd: repoDir });
|
|
38
|
-
return
|
|
52
|
+
return {
|
|
53
|
+
resolved: true,
|
|
54
|
+
repoDir,
|
|
55
|
+
targetBranch: STAGING_BRANCH,
|
|
56
|
+
reconciledFiles: files,
|
|
57
|
+
allConflictsWereGeneratedMetadata: true
|
|
58
|
+
};
|
|
39
59
|
}
|
|
40
60
|
function headCommit(repoDir, ref = "HEAD") {
|
|
41
61
|
return runGit(["rev-parse", ref], { cwd: repoDir, capture: true }).trim();
|
|
@@ -301,22 +321,35 @@ function deleteRemoteBranch(repoDir, branchName) {
|
|
|
301
321
|
function mergeCurrentBranchIntoStaging(cwd, featureBranch) {
|
|
302
322
|
return squashMergeBranchIntoStaging(cwd, featureBranch, `stage: ${featureBranch}`);
|
|
303
323
|
}
|
|
304
|
-
function squashMergeBranchIntoStaging(cwd, featureBranch, message, { pushTarget = true } = {}) {
|
|
324
|
+
function squashMergeBranchIntoStaging(cwd, featureBranch, message, { pushTarget = true, reportGeneratedMetadataReconciliation = true } = {}) {
|
|
305
325
|
const repoDir = assertCleanWorktree(cwd);
|
|
306
326
|
fetchOrigin(repoDir);
|
|
307
327
|
syncBranchWithOrigin(repoDir, STAGING_BRANCH);
|
|
328
|
+
let generatedMetadataReconciliation = null;
|
|
308
329
|
try {
|
|
309
|
-
runGit(["merge", "--squash", featureBranch], { cwd: repoDir });
|
|
330
|
+
runGit(["merge", "--squash", featureBranch], { cwd: repoDir, capture: true });
|
|
310
331
|
} catch (error) {
|
|
311
|
-
|
|
332
|
+
const reconciliation = resolveGeneratedPackageMetadataConflicts(repoDir);
|
|
333
|
+
if (!reconciliation.resolved) {
|
|
312
334
|
throw error;
|
|
313
335
|
}
|
|
336
|
+
if (reportGeneratedMetadataReconciliation) {
|
|
337
|
+
console.log(`Resolving generated package metadata reconciliation for ${reconciliation.reconciledFiles.join(", ")}.`);
|
|
338
|
+
}
|
|
339
|
+
generatedMetadataReconciliation = {
|
|
340
|
+
...reconciliation,
|
|
341
|
+
commitSha: null
|
|
342
|
+
};
|
|
314
343
|
}
|
|
315
344
|
let committed = false;
|
|
316
345
|
if (repoHasStagedChanges(repoDir)) {
|
|
317
346
|
runGit(["commit", "-m", message], { cwd: repoDir });
|
|
318
347
|
committed = true;
|
|
319
348
|
}
|
|
349
|
+
const commitSha = headCommit(repoDir);
|
|
350
|
+
if (generatedMetadataReconciliation) {
|
|
351
|
+
generatedMetadataReconciliation.commitSha = commitSha;
|
|
352
|
+
}
|
|
320
353
|
if (pushTarget) {
|
|
321
354
|
pushBranch(repoDir, STAGING_BRANCH);
|
|
322
355
|
}
|
|
@@ -324,8 +357,9 @@ function squashMergeBranchIntoStaging(cwd, featureBranch, message, { pushTarget
|
|
|
324
357
|
repoDir,
|
|
325
358
|
targetBranch: STAGING_BRANCH,
|
|
326
359
|
committed,
|
|
327
|
-
commitSha
|
|
328
|
-
pushed: pushTarget
|
|
360
|
+
commitSha,
|
|
361
|
+
pushed: pushTarget,
|
|
362
|
+
generatedMetadataReconciliation
|
|
329
363
|
};
|
|
330
364
|
}
|
|
331
365
|
function currentManagedBranch(cwd = workspaceRoot()) {
|
|
@@ -114,6 +114,8 @@ export declare function skippedGitHubActionsGate(gate: GitHubActionsWorkflowGate
|
|
|
114
114
|
conclusion: null;
|
|
115
115
|
runId: null;
|
|
116
116
|
url: null;
|
|
117
|
+
createdAt: null;
|
|
118
|
+
updatedAt: null;
|
|
117
119
|
};
|
|
118
120
|
export declare function formatGitHubActionsGateFailure(gate: GitHubActionsWorkflowGate, result: Record<string, unknown>): string;
|
|
119
121
|
export declare function waitForGitHubActionsGate(gate: GitHubActionsWorkflowGate, options?: {
|
|
@@ -26,6 +26,8 @@ export interface GitHubWorkflowRunSummary {
|
|
|
26
26
|
url: string | null;
|
|
27
27
|
headSha: string | null;
|
|
28
28
|
headBranch: string | null;
|
|
29
|
+
createdAt: string | null;
|
|
30
|
+
updatedAt: string | null;
|
|
29
31
|
}
|
|
30
32
|
export interface GitHubWorkflowJobSummary {
|
|
31
33
|
id: number;
|
|
@@ -162,6 +164,8 @@ export declare function waitForGitHubWorkflowRunCompletion(repository: string |
|
|
|
162
164
|
runId: number;
|
|
163
165
|
headSha: string | null;
|
|
164
166
|
branch: string | null;
|
|
167
|
+
createdAt: string | null;
|
|
168
|
+
updatedAt: string | null;
|
|
165
169
|
conclusion: string | null;
|
|
166
170
|
url: string | null;
|
|
167
171
|
jobs: GitHubWorkflowJobSummary[];
|
|
@@ -520,7 +520,9 @@ function normalizeWorkflowRun(run) {
|
|
|
520
520
|
conclusion: typeof run.conclusion === "string" ? run.conclusion : null,
|
|
521
521
|
url: typeof run.html_url === "string" ? run.html_url : null,
|
|
522
522
|
headSha: typeof run.head_sha === "string" ? run.head_sha : null,
|
|
523
|
-
headBranch: typeof run.head_branch === "string" ? run.head_branch : null
|
|
523
|
+
headBranch: typeof run.head_branch === "string" ? run.head_branch : null,
|
|
524
|
+
createdAt: typeof run.created_at === "string" ? run.created_at : null,
|
|
525
|
+
updatedAt: typeof run.updated_at === "string" ? run.updated_at : null
|
|
524
526
|
};
|
|
525
527
|
}
|
|
526
528
|
function normalizeWorkflowJob(job) {
|
|
@@ -623,6 +625,8 @@ async function waitForGitHubWorkflowRunCompletion(repository, {
|
|
|
623
625
|
runId: normalized.id,
|
|
624
626
|
headSha: normalized.headSha,
|
|
625
627
|
branch: normalized.headBranch,
|
|
628
|
+
createdAt: normalized.createdAt,
|
|
629
|
+
updatedAt: normalized.updatedAt,
|
|
626
630
|
conclusion: normalized.conclusion,
|
|
627
631
|
url: normalized.url,
|
|
628
632
|
jobs: normalizedJobs,
|
|
@@ -284,6 +284,8 @@ export declare function waitForGitHubWorkflowCompletion(tenantRoot: any, { repos
|
|
|
284
284
|
runId: number;
|
|
285
285
|
headSha: string | null;
|
|
286
286
|
branch: string | null;
|
|
287
|
+
createdAt: string | null;
|
|
288
|
+
updatedAt: string | null;
|
|
287
289
|
conclusion: string | null;
|
|
288
290
|
url: string | null;
|
|
289
291
|
jobs: import("./github-api.ts").GitHubWorkflowJobSummary[];
|
|
@@ -5,7 +5,10 @@ import { resolve } from 'node:path';
|
|
|
5
5
|
|
|
6
6
|
const args = process.argv.slice(2);
|
|
7
7
|
const defaultAllowlisted = [
|
|
8
|
-
|
|
8
|
+
{
|
|
9
|
+
label: 'vite-browser-external-libsodium-url',
|
|
10
|
+
pattern: /Module "url" has been externalized for browser compatibility, imported by ".*libsodium-sumo.*"/u,
|
|
11
|
+
},
|
|
9
12
|
];
|
|
10
13
|
const allowlisted = [];
|
|
11
14
|
const files = [];
|
|
@@ -22,7 +25,10 @@ for (let index = 0; index < args.length; index += 1) {
|
|
|
22
25
|
if (!pattern) {
|
|
23
26
|
throw new Error('Missing value for --allow.');
|
|
24
27
|
}
|
|
25
|
-
allowlisted.push(
|
|
28
|
+
allowlisted.push({
|
|
29
|
+
label: `custom:${pattern}`,
|
|
30
|
+
pattern: new RegExp(pattern),
|
|
31
|
+
});
|
|
26
32
|
index += 1;
|
|
27
33
|
continue;
|
|
28
34
|
}
|
|
@@ -34,6 +40,7 @@ if (files.length === 0) {
|
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
const warningLines = [];
|
|
43
|
+
const allowedWarnings = new Map();
|
|
37
44
|
const effectiveAllowlisted = [
|
|
38
45
|
...(useDefaultPolicy ? defaultAllowlisted : []),
|
|
39
46
|
...allowlisted,
|
|
@@ -44,7 +51,10 @@ for (const file of files) {
|
|
|
44
51
|
if (!line.includes('[WARN]')) {
|
|
45
52
|
continue;
|
|
46
53
|
}
|
|
47
|
-
|
|
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);
|
|
48
58
|
continue;
|
|
49
59
|
}
|
|
50
60
|
warningLines.push(line);
|
|
@@ -59,4 +69,11 @@ if (warningLines.length > 0) {
|
|
|
59
69
|
process.exit(1);
|
|
60
70
|
}
|
|
61
71
|
|
|
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
|
+
}
|
|
78
|
+
}
|
|
62
79
|
console.log('No unexpected build warnings detected.');
|
|
@@ -415,6 +415,8 @@ export declare function workflowSave(helpers: WorkflowOperationHelpers, input: T
|
|
|
415
415
|
conclusion: null;
|
|
416
416
|
runId: null;
|
|
417
417
|
url: null;
|
|
418
|
+
createdAt: null;
|
|
419
|
+
updatedAt: null;
|
|
418
420
|
}[];
|
|
419
421
|
releaseCandidate: ReleaseCandidateReport | null;
|
|
420
422
|
} & {
|
|
@@ -553,6 +555,8 @@ export declare function workflowClose(helpers: WorkflowOperationHelpers, input:
|
|
|
553
555
|
conclusion: null;
|
|
554
556
|
runId: null;
|
|
555
557
|
url: null;
|
|
558
|
+
createdAt: null;
|
|
559
|
+
updatedAt: null;
|
|
556
560
|
}[];
|
|
557
561
|
releaseCandidate: ReleaseCandidateReport | null;
|
|
558
562
|
} & {
|
|
@@ -732,6 +736,8 @@ export declare function workflowStage(helpers: WorkflowOperationHelpers, input:
|
|
|
732
736
|
conclusion: null;
|
|
733
737
|
runId: null;
|
|
734
738
|
url: null;
|
|
739
|
+
createdAt: null;
|
|
740
|
+
updatedAt: null;
|
|
735
741
|
}[];
|
|
736
742
|
releaseCandidate: ReleaseCandidateReport | null;
|
|
737
743
|
} & {
|
|
@@ -886,6 +892,7 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
886
892
|
targetBranch: string;
|
|
887
893
|
commitSha: string;
|
|
888
894
|
};
|
|
895
|
+
hostedDeploymentState: Record<string, unknown>[];
|
|
889
896
|
finalBranch: string;
|
|
890
897
|
pushStatus: {
|
|
891
898
|
stagingPushed: boolean;
|
|
@@ -905,6 +912,8 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
905
912
|
conclusion: null;
|
|
906
913
|
runId: null;
|
|
907
914
|
url: null;
|
|
915
|
+
createdAt: null;
|
|
916
|
+
updatedAt: null;
|
|
908
917
|
}[];
|
|
909
918
|
} & {
|
|
910
919
|
finalState?: WorkflowStatePayload;
|
|
@@ -968,6 +977,7 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
968
977
|
targetBranch: string;
|
|
969
978
|
commitSha: string;
|
|
970
979
|
};
|
|
980
|
+
hostedDeploymentState: Record<string, unknown>[];
|
|
971
981
|
finalBranch: string;
|
|
972
982
|
pushStatus: {
|
|
973
983
|
stagingPushed: boolean;
|
|
@@ -987,6 +997,8 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
987
997
|
conclusion: null;
|
|
988
998
|
runId: null;
|
|
989
999
|
url: null;
|
|
1000
|
+
createdAt: null;
|
|
1001
|
+
updatedAt: null;
|
|
990
1002
|
})[];
|
|
991
1003
|
} & {
|
|
992
1004
|
finalState?: WorkflowStatePayload;
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
ensureGeneratedWranglerConfig,
|
|
37
37
|
finalizeDeploymentState,
|
|
38
38
|
loadDeployState,
|
|
39
|
+
recordHostedDeploymentState,
|
|
39
40
|
runRemoteD1Migrations,
|
|
40
41
|
validateDeployPrerequisites,
|
|
41
42
|
validateDestroyPrerequisites
|
|
@@ -418,6 +419,38 @@ async function waitForWorkflowGates(operation, gates, ciMode, options = {}) {
|
|
|
418
419
|
}
|
|
419
420
|
return results;
|
|
420
421
|
}
|
|
422
|
+
function recordHostedDeploymentStatesFromRootGates(root, rootRelease, workflowGates) {
|
|
423
|
+
const gates = Array.isArray(workflowGates) ? workflowGates.map((gate) => stringRecord(gate)).filter((gate) => Boolean(gate)) : [];
|
|
424
|
+
const releaseRecord = stringRecord(rootRelease) ?? {};
|
|
425
|
+
const reports = [];
|
|
426
|
+
for (const target of [
|
|
427
|
+
{ scope: "staging", branch: STAGING_BRANCH, commit: releaseRecord.stagingCommit },
|
|
428
|
+
{ scope: "prod", branch: PRODUCTION_BRANCH, commit: releaseRecord.releasedCommit }
|
|
429
|
+
]) {
|
|
430
|
+
const gate = gates.find((candidate) => candidate.workflow === "deploy.yml" && candidate.branch === target.branch && candidate.status === "completed" && candidate.conclusion === "success");
|
|
431
|
+
const timestamp = typeof gate?.updatedAt === "string" && gate.updatedAt.trim() ? gate.updatedAt : null;
|
|
432
|
+
if (!gate || !timestamp) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const state = recordHostedDeploymentState(root, {
|
|
436
|
+
scope: target.scope,
|
|
437
|
+
commit: typeof target.commit === "string" ? target.commit : null,
|
|
438
|
+
timestamp,
|
|
439
|
+
workflow: gate.workflow,
|
|
440
|
+
runId: gate.runId ?? null
|
|
441
|
+
});
|
|
442
|
+
reports.push({
|
|
443
|
+
scope: target.scope,
|
|
444
|
+
branch: target.branch,
|
|
445
|
+
commit: typeof target.commit === "string" ? target.commit : null,
|
|
446
|
+
timestamp: state.lastDeploymentTimestamp ?? timestamp,
|
|
447
|
+
url: state.lastDeployedUrl ?? null,
|
|
448
|
+
workflow: gate.workflow,
|
|
449
|
+
runId: gate.runId ?? null
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return reports;
|
|
453
|
+
}
|
|
421
454
|
function ensureTreeseedCommandReadiness(root) {
|
|
422
455
|
if (getGitHubAutomationMode() === "stub") {
|
|
423
456
|
return {
|
|
@@ -3625,6 +3658,7 @@ async function workflowRelease(helpers, input) {
|
|
|
3625
3658
|
runId: workflowRun.runId,
|
|
3626
3659
|
onProgress: (line, stream) => helpers.write(line, stream)
|
|
3627
3660
|
}).then((workflowGates) => ({ workflowGates })));
|
|
3661
|
+
const hostedDeploymentState2 = recordHostedDeploymentStatesFromRootGates(root, rootRelease2, rootWorkflowGateResult2?.workflowGates);
|
|
3628
3662
|
const releaseBackMerge2 = await executeJournalStep(root, workflowRun.runId, "release-back-merge", () => backMergeRootProductionIntoStaging(root, false));
|
|
3629
3663
|
const workspaceLinks2 = ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
3630
3664
|
const payload2 = {
|
|
@@ -3648,6 +3682,7 @@ async function workflowRelease(helpers, input) {
|
|
|
3648
3682
|
rootRepo,
|
|
3649
3683
|
releaseCandidate,
|
|
3650
3684
|
releaseBackMerge: releaseBackMerge2,
|
|
3685
|
+
hostedDeploymentState: hostedDeploymentState2,
|
|
3651
3686
|
finalBranch: currentBranch(gitRoot) || STAGING_BRANCH,
|
|
3652
3687
|
pushStatus: { stagingPushed: true, productionPushed: true, tagPushed: true },
|
|
3653
3688
|
workspaceLinks: workspaceLinks2,
|
|
@@ -3870,6 +3905,7 @@ async function workflowRelease(helpers, input) {
|
|
|
3870
3905
|
runId: workflowRun.runId,
|
|
3871
3906
|
onProgress: (line, stream) => helpers.write(line, stream)
|
|
3872
3907
|
}).then((workflowGates) => ({ workflowGates })));
|
|
3908
|
+
const hostedDeploymentState = recordHostedDeploymentStatesFromRootGates(root, rootRelease, rootWorkflowGateResult?.workflowGates);
|
|
3873
3909
|
const releaseBackMerge = await executeJournalStep(root, workflowRun.runId, "release-back-merge", () => backMergeRootProductionIntoStaging(root, true));
|
|
3874
3910
|
const devTagCleanupMode = effectiveInput.devTagCleanup ?? "safe-after-release";
|
|
3875
3911
|
const devTagCleanup = devTagCleanupMode === "off" ? (skipJournalStep(root, workflowRun.runId, "cleanup-dev-tags", { status: "skipped", reason: "disabled" }), { status: "skipped", reason: "disabled" }) : await executeJournalStep(root, workflowRun.runId, "cleanup-dev-tags", () => {
|
|
@@ -3921,6 +3957,7 @@ async function workflowRelease(helpers, input) {
|
|
|
3921
3957
|
rootRepo,
|
|
3922
3958
|
releaseCandidate,
|
|
3923
3959
|
releaseBackMerge,
|
|
3960
|
+
hostedDeploymentState,
|
|
3924
3961
|
finalBranch: currentBranch(gitRoot) || STAGING_BRANCH,
|
|
3925
3962
|
pushStatus: {
|
|
3926
3963
|
stagingPushed: true,
|
package/dist/workflow-state.d.ts
CHANGED
package/dist/workflow-state.js
CHANGED
|
@@ -261,6 +261,7 @@ function safeReleaseHistory(repoDir) {
|
|
|
261
261
|
return {
|
|
262
262
|
stagingAheadMain: null,
|
|
263
263
|
stagingBehindMain: null,
|
|
264
|
+
unreleasedStagingCommits: null,
|
|
264
265
|
backMerged: null,
|
|
265
266
|
detail: "Repository root is unavailable."
|
|
266
267
|
};
|
|
@@ -273,16 +274,20 @@ function safeReleaseHistory(repoDir) {
|
|
|
273
274
|
if (!Number.isFinite(stagingAheadMain) || !Number.isFinite(stagingBehindMain)) {
|
|
274
275
|
throw new Error("invalid rev-list output");
|
|
275
276
|
}
|
|
277
|
+
const stagingOnlySubjects = run("git", ["log", "--format=%s", "main..staging"], { cwd: repoDir, capture: true }).split("\n").map((line) => line.trim()).filter(Boolean);
|
|
278
|
+
const unreleasedStagingCommits = stagingOnlySubjects.filter((subject) => subject !== "release: sync package staging heads" && subject !== "release: back-merge main into staging" && !subject.startsWith("release: back-merge main into staging ")).length;
|
|
276
279
|
return {
|
|
277
280
|
stagingAheadMain,
|
|
278
281
|
stagingBehindMain,
|
|
282
|
+
unreleasedStagingCommits,
|
|
279
283
|
backMerged: stagingBehindMain === 0,
|
|
280
|
-
detail: stagingBehindMain === 0 ? "Staging contains current main release history." : `Staging is missing ${stagingBehindMain} main commit${stagingBehindMain === 1 ? "" : "s"}.`
|
|
284
|
+
detail: stagingBehindMain === 0 && unreleasedStagingCommits === 0 ? stagingAheadMain > 0 ? "Staging contains current main release history and is only ahead by release sync commits." : "Staging contains current main release history." : stagingBehindMain === 0 ? `Staging has ${unreleasedStagingCommits} unreleased commit${unreleasedStagingCommits === 1 ? "" : "s"} and contains current main release history.` : `Staging is missing ${stagingBehindMain} main commit${stagingBehindMain === 1 ? "" : "s"}.`
|
|
281
285
|
};
|
|
282
286
|
} catch {
|
|
283
287
|
return {
|
|
284
288
|
stagingAheadMain: null,
|
|
285
289
|
stagingBehindMain: null,
|
|
290
|
+
unreleasedStagingCommits: null,
|
|
286
291
|
backMerged: null,
|
|
287
292
|
detail: "Could not compare staging and main release history."
|
|
288
293
|
};
|
|
@@ -414,6 +419,8 @@ function resolveTreeseedWorkflowState(cwd, options = {}) {
|
|
|
414
419
|
if (interruptedRuns.length > 0) {
|
|
415
420
|
workflowBlockers.push(`Interrupted workflow runs detected: ${interruptedRuns.map((run2) => run2.runId).join(", ")}.`);
|
|
416
421
|
}
|
|
422
|
+
const releaseHistory = safeReleaseHistory(root);
|
|
423
|
+
const releaseReady = branchRole === "staging" && !dirtyWorktree && (releaseHistory.unreleasedStagingCommits ?? 0) > 0;
|
|
417
424
|
const state = {
|
|
418
425
|
cwd: effectiveCwd,
|
|
419
426
|
workspaceRoot,
|
|
@@ -527,8 +534,8 @@ function resolveTreeseedWorkflowState(cwd, options = {}) {
|
|
|
527
534
|
idleRemainingMs: keyStatus.idleRemainingMs,
|
|
528
535
|
startupPassphraseConfigured: Boolean(process.env.TREESEED_KEY_PASSPHRASE?.trim())
|
|
529
536
|
},
|
|
530
|
-
releaseReady
|
|
531
|
-
releaseHistory
|
|
537
|
+
releaseReady,
|
|
538
|
+
releaseHistory,
|
|
532
539
|
readiness: {
|
|
533
540
|
local: { ready: false, blockers: [], warnings: [] },
|
|
534
541
|
staging: { ready: false, blockers: [], warnings: [] },
|
|
@@ -747,8 +754,13 @@ function recommendTreeseedNextSteps(state) {
|
|
|
747
754
|
}
|
|
748
755
|
if (!state.persistentEnvironments.staging.initialized) {
|
|
749
756
|
recommendations.push({ operation: "config", reason: "Initialize the staging environment before releasing.", input: { environment: ["staging"] } });
|
|
757
|
+
} else if ((state.releaseHistory.unreleasedStagingCommits ?? 0) > 0) {
|
|
758
|
+
recommendations.push({ operation: "release", reason: "Promote unreleased staging commits into production.", input: { bump: "patch" } });
|
|
759
|
+
if (state.managedServices.api.enabled) {
|
|
760
|
+
recommendations.push({ operation: "auth:login", reason: "Keep the local runtime authenticated to the remote API used by managed services." });
|
|
761
|
+
}
|
|
750
762
|
} else {
|
|
751
|
-
recommendations.push({ operation: "
|
|
763
|
+
recommendations.push({ operation: "status", reason: "Inspect staging and production state; no unreleased staging commits are pending." });
|
|
752
764
|
if (state.managedServices.api.enabled) {
|
|
753
765
|
recommendations.push({ operation: "auth:login", reason: "Keep the local runtime authenticated to the remote API used by managed services." });
|
|
754
766
|
}
|