@treeseed/sdk 0.6.36 → 0.6.38
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/git-workflow.js +5 -5
- package/dist/operations/services/github-actions-verification.d.ts +4 -0
- package/dist/operations/services/github-actions-verification.js +5 -3
- package/dist/operations/services/github-automation.d.ts +6 -98
- package/dist/operations/services/github-automation.js +1 -74
- package/dist/operations/services/release-candidate.js +5 -11
- package/dist/operations/services/release-history.d.ts +59 -0
- package/dist/operations/services/release-history.js +159 -0
- package/dist/operations/services/repository-save-orchestrator.js +7 -8
- package/dist/operations/services/save-deploy-preflight.d.ts +0 -5
- package/dist/operations/services/save-deploy-preflight.js +1 -9
- package/dist/scripts/tenant-build.js +31 -2
- package/dist/scripts/workspace-command-e2e.js +3 -3
- package/dist/verification.js +0 -1
- package/dist/workflow/operations.d.ts +14 -2
- package/dist/workflow/operations.js +293 -131
- package/package.json +1 -1
- package/templates/github/deploy.workflow.yml +12 -9
|
@@ -162,7 +162,7 @@ function remoteBranchExists(repoDir, branchName) {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
function fetchOrigin(repoDir) {
|
|
165
|
-
runGit(["fetch", "origin"], { cwd: repoDir });
|
|
165
|
+
runGit(["fetch", "origin"], { cwd: repoDir, capture: true });
|
|
166
166
|
}
|
|
167
167
|
function ensureLocalBranchTracking(repoDir, branchName) {
|
|
168
168
|
if (branchExists(repoDir, branchName)) {
|
|
@@ -175,7 +175,7 @@ function ensureLocalBranchTracking(repoDir, branchName) {
|
|
|
175
175
|
runGit(["checkout", "--orphan", branchName], { cwd: repoDir });
|
|
176
176
|
}
|
|
177
177
|
function checkoutBranch(repoDir, branchName) {
|
|
178
|
-
runGit(["checkout", branchName], { cwd: repoDir });
|
|
178
|
+
runGit(["checkout", branchName], { cwd: repoDir, capture: true });
|
|
179
179
|
}
|
|
180
180
|
function checkoutTaskBranchFromStaging(cwd, branchName, { createIfMissing = true, pushIfCreated = false } = {}) {
|
|
181
181
|
const repoDir = assertCleanWorktree(cwd);
|
|
@@ -244,7 +244,7 @@ function syncBranchWithOrigin(repoDir, branchName) {
|
|
|
244
244
|
checkoutBranch(repoDir, branchName);
|
|
245
245
|
}
|
|
246
246
|
if (remoteBranchExists(repoDir, branchName)) {
|
|
247
|
-
runGit(["merge", "--ff-only", `origin/${branchName}`], { cwd: repoDir });
|
|
247
|
+
runGit(["merge", "--ff-only", `origin/${branchName}`], { cwd: repoDir, capture: true });
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
function checkoutDetachedOriginBranch(repoDir, branchName) {
|
|
@@ -420,8 +420,8 @@ function createDeprecatedTaskTag(repoDir, branchName, message) {
|
|
|
420
420
|
return { tagName, head };
|
|
421
421
|
}
|
|
422
422
|
function waitForStagingAutomation(repoDir) {
|
|
423
|
-
if (process.env.TREESEED_STAGE_WAIT_MODE === "skip"
|
|
424
|
-
return { status: "skipped", reason: "
|
|
423
|
+
if (process.env.TREESEED_STAGE_WAIT_MODE === "skip") {
|
|
424
|
+
return { status: "skipped", reason: "disabled" };
|
|
425
425
|
}
|
|
426
426
|
try {
|
|
427
427
|
const gh = resolveTreeseedToolBinary("gh");
|
|
@@ -16,6 +16,8 @@ export type GitHubActionsWorkflowGate = {
|
|
|
16
16
|
workflow: string;
|
|
17
17
|
branch: string;
|
|
18
18
|
headSha: string;
|
|
19
|
+
timeoutSeconds?: number;
|
|
20
|
+
pollSeconds?: number;
|
|
19
21
|
};
|
|
20
22
|
export type GitHubActionsWorkflowJobStep = {
|
|
21
23
|
name: string;
|
|
@@ -116,6 +118,8 @@ export declare function skippedGitHubActionsGate(gate: GitHubActionsWorkflowGate
|
|
|
116
118
|
url: null;
|
|
117
119
|
createdAt: null;
|
|
118
120
|
updatedAt: null;
|
|
121
|
+
timeoutSeconds: number | null;
|
|
122
|
+
cached: boolean;
|
|
119
123
|
};
|
|
120
124
|
export declare function formatGitHubActionsGateFailure(gate: GitHubActionsWorkflowGate, result: Record<string, unknown>): string;
|
|
121
125
|
export declare function createGitHubActionsGateProgressReporter(gate: GitHubActionsWorkflowGate, options?: {
|
|
@@ -408,7 +408,9 @@ function skippedGitHubActionsGate(gate, reason) {
|
|
|
408
408
|
runId: null,
|
|
409
409
|
url: null,
|
|
410
410
|
createdAt: null,
|
|
411
|
-
updatedAt: null
|
|
411
|
+
updatedAt: null,
|
|
412
|
+
timeoutSeconds: gate.timeoutSeconds ?? null,
|
|
413
|
+
cached: false
|
|
412
414
|
};
|
|
413
415
|
}
|
|
414
416
|
function formatGitHubActionsGateFailure(gate, result) {
|
|
@@ -537,8 +539,8 @@ async function waitForGitHubActionsGate(gate, options = {}) {
|
|
|
537
539
|
workflow: gate.workflow,
|
|
538
540
|
headSha: gate.headSha,
|
|
539
541
|
branch: gate.branch,
|
|
540
|
-
timeoutSeconds: options.timeoutSeconds,
|
|
541
|
-
pollSeconds: options.pollSeconds,
|
|
542
|
+
timeoutSeconds: gate.timeoutSeconds ?? options.timeoutSeconds,
|
|
543
|
+
pollSeconds: gate.pollSeconds ?? options.pollSeconds,
|
|
542
544
|
onProgress: reportProgress
|
|
543
545
|
});
|
|
544
546
|
}
|
|
@@ -22,7 +22,7 @@ export interface TreeseedGitHubRepositoryTarget {
|
|
|
22
22
|
visibility: 'private' | 'public' | 'internal';
|
|
23
23
|
source: 'config' | 'origin' | 'default';
|
|
24
24
|
}
|
|
25
|
-
export declare function getGitHubAutomationMode():
|
|
25
|
+
export declare function getGitHubAutomationMode(): string;
|
|
26
26
|
export declare function parseGitHubRepositoryFromRemote(remoteUrl: any): string | null;
|
|
27
27
|
export declare function resolveGitHubRepositorySlug(tenantRoot: any): string;
|
|
28
28
|
export declare function maybeResolveGitHubRepositorySlug(tenantRoot: any): string | null;
|
|
@@ -51,17 +51,7 @@ export declare function ensureGitHubBootstrapRepository(tenantRoot: string, { va
|
|
|
51
51
|
pushed: boolean;
|
|
52
52
|
mode: string;
|
|
53
53
|
}>;
|
|
54
|
-
export declare function createGitHubRepository(input: any): Promise<import("./github-api.ts").GitHubRepositorySummary
|
|
55
|
-
visibility: any;
|
|
56
|
-
defaultBranch: string;
|
|
57
|
-
mode: string;
|
|
58
|
-
slug: string;
|
|
59
|
-
owner: string;
|
|
60
|
-
name: string;
|
|
61
|
-
sshUrl: string;
|
|
62
|
-
httpsUrl: string;
|
|
63
|
-
url: string;
|
|
64
|
-
}>;
|
|
54
|
+
export declare function createGitHubRepository(input: any): Promise<import("./github-api.ts").GitHubRepositorySummary>;
|
|
65
55
|
export declare function initializeGitHubRepositoryWorkingTree(cwd: any, repository: any, { defaultBranch, createStaging, commitMessage, remoteName, push, }?: {
|
|
66
56
|
defaultBranch?: string | undefined;
|
|
67
57
|
createStaging?: boolean | undefined;
|
|
@@ -74,14 +64,6 @@ export declare function initializeGitHubRepositoryWorkingTree(cwd: any, reposito
|
|
|
74
64
|
defaultBranch: string;
|
|
75
65
|
stagingBranch: string | null;
|
|
76
66
|
pushed: boolean;
|
|
77
|
-
mode: string;
|
|
78
|
-
} | {
|
|
79
|
-
repository: any;
|
|
80
|
-
remoteName: string;
|
|
81
|
-
defaultBranch: string;
|
|
82
|
-
stagingBranch: string | null;
|
|
83
|
-
pushed: boolean;
|
|
84
|
-
mode?: undefined;
|
|
85
67
|
};
|
|
86
68
|
export declare function resolveGitRepositoryRoot(tenantRoot: any): any;
|
|
87
69
|
export declare function requiredGitHubEnvironment(tenantRoot: any, { scope, purpose }?: {
|
|
@@ -99,38 +81,20 @@ export declare function renderHostedProjectWorkflow({ workingDirectory }: {
|
|
|
99
81
|
workingDirectory: any;
|
|
100
82
|
}): string;
|
|
101
83
|
export declare function ensureDeployWorkflow(tenantRoot: any): {
|
|
102
|
-
workflowPath: string;
|
|
103
|
-
changed: boolean;
|
|
104
|
-
workingDirectory: string;
|
|
105
|
-
mode: string;
|
|
106
|
-
} | {
|
|
107
84
|
workingDirectory: string;
|
|
108
85
|
workflowPath: string;
|
|
109
86
|
changed: boolean;
|
|
110
|
-
mode?: undefined;
|
|
111
87
|
};
|
|
112
88
|
export declare function ensureHostedProjectWorkflow(tenantRoot: any): {
|
|
113
|
-
workflowPath: string;
|
|
114
|
-
changed: boolean;
|
|
115
|
-
workingDirectory: string;
|
|
116
|
-
mode: string;
|
|
117
|
-
} | {
|
|
118
89
|
workingDirectory: string;
|
|
119
90
|
workflowPath: string;
|
|
120
91
|
changed: boolean;
|
|
121
|
-
mode?: undefined;
|
|
122
92
|
};
|
|
123
|
-
export declare function ensureStandardizedGitHubWorkflows(tenantRoot: any):
|
|
124
|
-
workflowPath: string;
|
|
125
|
-
changed: boolean;
|
|
126
|
-
workingDirectory: string;
|
|
127
|
-
mode: string;
|
|
128
|
-
} | {
|
|
93
|
+
export declare function ensureStandardizedGitHubWorkflows(tenantRoot: any): {
|
|
129
94
|
workingDirectory: string;
|
|
130
95
|
workflowPath: string;
|
|
131
96
|
changed: boolean;
|
|
132
|
-
|
|
133
|
-
})[];
|
|
97
|
+
}[];
|
|
134
98
|
export declare function listGitHubSecretNames(repository: any, tenantRoot: any): Promise<Set<string>>;
|
|
135
99
|
export declare function listGitHubVariableNames(repository: any, tenantRoot: any): Promise<Set<string>>;
|
|
136
100
|
export declare function formatMissingSecretsReport(repository: any, missingSecrets: any, reason?: string): string;
|
|
@@ -139,9 +103,6 @@ export declare function ensureGitHubSecrets(tenantRoot: any, { dryRun }?: {
|
|
|
139
103
|
}): Promise<{
|
|
140
104
|
existing: never[];
|
|
141
105
|
created: never[];
|
|
142
|
-
} | {
|
|
143
|
-
existing: never[];
|
|
144
|
-
created: never[];
|
|
145
106
|
} | {
|
|
146
107
|
existing: string[];
|
|
147
108
|
created: string[];
|
|
@@ -152,18 +113,6 @@ export declare function ensureGitHubEnvironment(tenantRoot: any, { dryRun, scope
|
|
|
152
113
|
purpose?: string | undefined;
|
|
153
114
|
valuesOverlay?: {} | undefined;
|
|
154
115
|
}): Promise<{
|
|
155
|
-
repository: string | null;
|
|
156
|
-
secrets: {
|
|
157
|
-
existing: never[];
|
|
158
|
-
created: never[];
|
|
159
|
-
};
|
|
160
|
-
variables: {
|
|
161
|
-
existing: never[];
|
|
162
|
-
created: never[];
|
|
163
|
-
};
|
|
164
|
-
skipped: string;
|
|
165
|
-
mode: string;
|
|
166
|
-
} | {
|
|
167
116
|
repository: null;
|
|
168
117
|
secrets: {
|
|
169
118
|
existing: never[];
|
|
@@ -174,7 +123,6 @@ export declare function ensureGitHubEnvironment(tenantRoot: any, { dryRun, scope
|
|
|
174
123
|
created: never[];
|
|
175
124
|
};
|
|
176
125
|
skipped: string;
|
|
177
|
-
mode?: undefined;
|
|
178
126
|
} | {
|
|
179
127
|
repository: string;
|
|
180
128
|
secrets: {
|
|
@@ -186,7 +134,6 @@ export declare function ensureGitHubEnvironment(tenantRoot: any, { dryRun, scope
|
|
|
186
134
|
created: string[];
|
|
187
135
|
};
|
|
188
136
|
skipped?: undefined;
|
|
189
|
-
mode?: undefined;
|
|
190
137
|
}>;
|
|
191
138
|
export declare function ensureGitHubDeployAutomation(tenantRoot: any, { dryRun, valuesOverlay }?: {
|
|
192
139
|
dryRun?: boolean | undefined;
|
|
@@ -194,33 +141,18 @@ export declare function ensureGitHubDeployAutomation(tenantRoot: any, { dryRun,
|
|
|
194
141
|
}): Promise<{
|
|
195
142
|
mode: string;
|
|
196
143
|
workflow: {
|
|
197
|
-
workflowPath: string;
|
|
198
|
-
changed: boolean;
|
|
199
|
-
workingDirectory: string;
|
|
200
|
-
mode: string;
|
|
201
|
-
} | {
|
|
202
144
|
workingDirectory: string;
|
|
203
145
|
workflowPath: string;
|
|
204
146
|
changed: boolean;
|
|
205
|
-
mode?: undefined;
|
|
206
147
|
};
|
|
207
|
-
workflows:
|
|
208
|
-
workflowPath: string;
|
|
209
|
-
changed: boolean;
|
|
210
|
-
workingDirectory: string;
|
|
211
|
-
mode: string;
|
|
212
|
-
} | {
|
|
148
|
+
workflows: {
|
|
213
149
|
workingDirectory: string;
|
|
214
150
|
workflowPath: string;
|
|
215
151
|
changed: boolean;
|
|
216
|
-
|
|
217
|
-
})[];
|
|
152
|
+
}[];
|
|
218
153
|
secrets: {
|
|
219
154
|
existing: never[];
|
|
220
155
|
created: never[];
|
|
221
|
-
} | {
|
|
222
|
-
existing: never[];
|
|
223
|
-
created: never[];
|
|
224
156
|
} | {
|
|
225
157
|
existing: string[];
|
|
226
158
|
created: string[];
|
|
@@ -228,26 +160,11 @@ export declare function ensureGitHubDeployAutomation(tenantRoot: any, { dryRun,
|
|
|
228
160
|
variables: {
|
|
229
161
|
existing: never[];
|
|
230
162
|
created: never[];
|
|
231
|
-
} | {
|
|
232
|
-
existing: never[];
|
|
233
|
-
created: never[];
|
|
234
163
|
} | {
|
|
235
164
|
existing: string[];
|
|
236
165
|
created: string[];
|
|
237
166
|
};
|
|
238
167
|
environment: {
|
|
239
|
-
repository: string | null;
|
|
240
|
-
secrets: {
|
|
241
|
-
existing: never[];
|
|
242
|
-
created: never[];
|
|
243
|
-
};
|
|
244
|
-
variables: {
|
|
245
|
-
existing: never[];
|
|
246
|
-
created: never[];
|
|
247
|
-
};
|
|
248
|
-
skipped: string;
|
|
249
|
-
mode: string;
|
|
250
|
-
} | {
|
|
251
168
|
repository: null;
|
|
252
169
|
secrets: {
|
|
253
170
|
existing: never[];
|
|
@@ -258,7 +175,6 @@ export declare function ensureGitHubDeployAutomation(tenantRoot: any, { dryRun,
|
|
|
258
175
|
created: never[];
|
|
259
176
|
};
|
|
260
177
|
skipped: string;
|
|
261
|
-
mode?: undefined;
|
|
262
178
|
} | {
|
|
263
179
|
repository: string;
|
|
264
180
|
secrets: {
|
|
@@ -270,7 +186,6 @@ export declare function ensureGitHubDeployAutomation(tenantRoot: any, { dryRun,
|
|
|
270
186
|
created: string[];
|
|
271
187
|
};
|
|
272
188
|
skipped?: undefined;
|
|
273
|
-
mode?: undefined;
|
|
274
189
|
};
|
|
275
190
|
}>;
|
|
276
191
|
export declare function waitForGitHubWorkflowCompletion(tenantRoot: any, { repository, workflow, headSha, branch, timeoutSeconds, pollSeconds, onProgress, }?: {
|
|
@@ -290,11 +205,4 @@ export declare function waitForGitHubWorkflowCompletion(tenantRoot: any, { repos
|
|
|
290
205
|
url: string | null;
|
|
291
206
|
jobs: import("./github-api.ts").GitHubWorkflowJobSummary[];
|
|
292
207
|
failedJobs: import("./github-api.ts").GitHubWorkflowJobSummary[];
|
|
293
|
-
} | {
|
|
294
|
-
status: string;
|
|
295
|
-
reason: string;
|
|
296
|
-
repository: any;
|
|
297
|
-
workflow: string;
|
|
298
|
-
headSha: any;
|
|
299
|
-
branch: any;
|
|
300
208
|
}>;
|
|
@@ -22,10 +22,7 @@ function slugifySegment(value, fallback = "project") {
|
|
|
22
22
|
return String(value ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-").slice(0, 96) || fallback;
|
|
23
23
|
}
|
|
24
24
|
function getGitHubAutomationMode() {
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
function isGitHubAutomationStubbed() {
|
|
28
|
-
return getGitHubAutomationMode() === "stub";
|
|
25
|
+
return "real";
|
|
29
26
|
}
|
|
30
27
|
function parseGitHubRepositoryFromRemote(remoteUrl) {
|
|
31
28
|
if (!remoteUrl) {
|
|
@@ -170,17 +167,6 @@ async function ensureGitHubBootstrapRepository(tenantRoot, {
|
|
|
170
167
|
const remotes = resolveGitHubRemoteUrls(target.owner, target.name);
|
|
171
168
|
const slug = remotes.slug;
|
|
172
169
|
onProgress?.(`[local][github][repo] Preparing ${slug} from ${target.source}...`);
|
|
173
|
-
if (isGitHubAutomationStubbed()) {
|
|
174
|
-
onProgress?.(`[local][github][repo] Stubbed GitHub automation; repository ${slug} not changed.`);
|
|
175
|
-
return {
|
|
176
|
-
repository: slug,
|
|
177
|
-
target,
|
|
178
|
-
created: false,
|
|
179
|
-
remote: { changed: false, previous: null, next: remotes.sshUrl },
|
|
180
|
-
pushed: false,
|
|
181
|
-
mode: "stub"
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
170
|
const client = createGitHubApiClient({
|
|
185
171
|
env: {
|
|
186
172
|
GH_TOKEN: configuredValue(values, "GH_TOKEN") || configuredValue(values, "GITHUB_TOKEN"),
|
|
@@ -217,14 +203,6 @@ async function ensureGitHubBootstrapRepository(tenantRoot, {
|
|
|
217
203
|
async function createGitHubRepository(input) {
|
|
218
204
|
const visibility = input.visibility ?? "private";
|
|
219
205
|
const remotes = resolveGitHubRemoteUrls(input.owner, input.name);
|
|
220
|
-
if (isGitHubAutomationStubbed()) {
|
|
221
|
-
return {
|
|
222
|
-
...remotes,
|
|
223
|
-
visibility,
|
|
224
|
-
defaultBranch: "main",
|
|
225
|
-
mode: "stub"
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
206
|
return await ensureGitHubRepository({
|
|
229
207
|
owner: remotes.owner,
|
|
230
208
|
name: remotes.name,
|
|
@@ -243,16 +221,6 @@ function initializeGitHubRepositoryWorkingTree(cwd, repository, {
|
|
|
243
221
|
remoteName = "origin",
|
|
244
222
|
push = true
|
|
245
223
|
} = {}) {
|
|
246
|
-
if (isGitHubAutomationStubbed()) {
|
|
247
|
-
return {
|
|
248
|
-
repository,
|
|
249
|
-
remoteName,
|
|
250
|
-
defaultBranch,
|
|
251
|
-
stagingBranch: createStaging ? "staging" : null,
|
|
252
|
-
pushed: false,
|
|
253
|
-
mode: "stub"
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
224
|
runGit(["init", "-b", defaultBranch], { cwd, allowFailure: true });
|
|
257
225
|
ensureGitIdentity(cwd);
|
|
258
226
|
const currentRemote = runGit(["remote", "get-url", remoteName], { cwd, allowFailure: true }).stdout?.trim() ?? "";
|
|
@@ -348,14 +316,6 @@ function ensureWorkflowFile(tenantRoot, fileName, expected) {
|
|
|
348
316
|
return { workflowPath, changed: true };
|
|
349
317
|
}
|
|
350
318
|
function ensureDeployWorkflow(tenantRoot) {
|
|
351
|
-
if (isGitHubAutomationStubbed()) {
|
|
352
|
-
return {
|
|
353
|
-
workflowPath: resolve(tenantRoot, ".github", "workflows", "deploy.yml"),
|
|
354
|
-
changed: false,
|
|
355
|
-
workingDirectory: ".",
|
|
356
|
-
mode: "stub"
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
319
|
const repositoryRoot = resolveGitRepositoryRoot(tenantRoot);
|
|
360
320
|
const workingDirectory = relative(repositoryRoot, tenantRoot).replaceAll("\\", "/") || ".";
|
|
361
321
|
const expected = renderDeployWorkflow({ workingDirectory });
|
|
@@ -365,14 +325,6 @@ function ensureDeployWorkflow(tenantRoot) {
|
|
|
365
325
|
};
|
|
366
326
|
}
|
|
367
327
|
function ensureHostedProjectWorkflow(tenantRoot) {
|
|
368
|
-
if (isGitHubAutomationStubbed()) {
|
|
369
|
-
return {
|
|
370
|
-
workflowPath: resolve(tenantRoot, ".github", "workflows", "hosted-project.yml"),
|
|
371
|
-
changed: false,
|
|
372
|
-
workingDirectory: ".",
|
|
373
|
-
mode: "stub"
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
328
|
const repositoryRoot = resolveGitRepositoryRoot(tenantRoot);
|
|
377
329
|
const workingDirectory = relative(repositoryRoot, tenantRoot).replaceAll("\\", "/") || ".";
|
|
378
330
|
const expected = renderHostedProjectWorkflow({ workingDirectory });
|
|
@@ -419,21 +371,6 @@ function nonEmptyValues(values = {}) {
|
|
|
419
371
|
);
|
|
420
372
|
}
|
|
421
373
|
async function ensureGitHubEnvironment(tenantRoot, { dryRun = false, scope = "prod", purpose = "save", valuesOverlay = {} } = {}) {
|
|
422
|
-
if (isGitHubAutomationStubbed()) {
|
|
423
|
-
return {
|
|
424
|
-
repository: maybeResolveGitHubRepositorySlug(tenantRoot),
|
|
425
|
-
secrets: {
|
|
426
|
-
existing: [],
|
|
427
|
-
created: []
|
|
428
|
-
},
|
|
429
|
-
variables: {
|
|
430
|
-
existing: [],
|
|
431
|
-
created: []
|
|
432
|
-
},
|
|
433
|
-
skipped: "stubbed",
|
|
434
|
-
mode: "stub"
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
374
|
const repository = maybeResolveGitHubRepositorySlug(tenantRoot);
|
|
438
375
|
if (!repository) {
|
|
439
376
|
if (dryRun) {
|
|
@@ -518,16 +455,6 @@ async function waitForGitHubWorkflowCompletion(tenantRoot, {
|
|
|
518
455
|
pollSeconds = 5,
|
|
519
456
|
onProgress
|
|
520
457
|
} = {}) {
|
|
521
|
-
if (isGitHubAutomationStubbed()) {
|
|
522
|
-
return {
|
|
523
|
-
status: "skipped",
|
|
524
|
-
reason: "stubbed",
|
|
525
|
-
repository: repository ?? maybeResolveGitHubRepositorySlug(tenantRoot),
|
|
526
|
-
workflow,
|
|
527
|
-
headSha: headSha ?? null,
|
|
528
|
-
branch: branch ?? null
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
458
|
const repo = repository ?? resolveGitHubRepositorySlug(tenantRoot);
|
|
532
459
|
return await waitForGitHubWorkflowRunCompletion(repo, {
|
|
533
460
|
client: createGitHubApiClient(),
|
|
@@ -4,7 +4,7 @@ import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, write
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { dirname, join, relative, resolve } from "node:path";
|
|
6
6
|
import { isTreeseedEnvironmentEntryRelevant, isTreeseedEnvironmentEntryRequired } from "../../platform/environment.js";
|
|
7
|
-
import {
|
|
7
|
+
import { maybeResolveGitHubRepositorySlug } from "./github-automation.js";
|
|
8
8
|
import { createGitHubApiClient, listGitHubEnvironmentSecretNames, listGitHubEnvironmentVariableNames } from "./github-api.js";
|
|
9
9
|
import { collectInternalDevReferenceIssues } from "./package-reference-policy.js";
|
|
10
10
|
import { collectTreeseedEnvironmentContext, resolveTreeseedMachineEnvironmentValues, validateTreeseedCommandEnvironment } from "./config-runtime.js";
|
|
@@ -123,9 +123,6 @@ function packageReadinessChecks(root, selectedPackageNames, failures) {
|
|
|
123
123
|
if (selectedPackageNames.length === 0) {
|
|
124
124
|
return { name: "package-release-readiness", status: "skipped", detail: "No packages are selected for this release." };
|
|
125
125
|
}
|
|
126
|
-
if (getGitHubAutomationMode() === "stub") {
|
|
127
|
-
return { name: "package-release-readiness", status: "skipped", detail: "GitHub automation is stubbed." };
|
|
128
|
-
}
|
|
129
126
|
const selected = new Set(selectedPackageNames);
|
|
130
127
|
const packages = workspacePackages(root).filter((pkg) => selected.has(pkg.name));
|
|
131
128
|
for (const pkg of packages) {
|
|
@@ -266,8 +263,8 @@ function buildRehearsalWorkspacePackageArtifacts(root) {
|
|
|
266
263
|
}
|
|
267
264
|
}
|
|
268
265
|
function runProductionDependencyRehearsal(root, plannedVersions, selectedPackageNames, failures) {
|
|
269
|
-
if (
|
|
270
|
-
return "Skipped clean install rehearsal
|
|
266
|
+
if (process.env.TREESEED_RELEASE_CANDIDATE_REHEARSAL_MODE === "skip") {
|
|
267
|
+
return "Skipped clean install rehearsal by request.";
|
|
271
268
|
}
|
|
272
269
|
const selectedPackageSet = new Set(selectedPackageNames);
|
|
273
270
|
let tempParent = null;
|
|
@@ -379,9 +376,6 @@ function localConfigCheck(root, scope, failures) {
|
|
|
379
376
|
}
|
|
380
377
|
}
|
|
381
378
|
async function githubRemoteConfigCheck(root, scope, failures) {
|
|
382
|
-
if (getGitHubAutomationMode() === "stub") {
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
379
|
const repository = maybeResolveGitHubRepositorySlug(root);
|
|
386
380
|
if (!repository) {
|
|
387
381
|
addFailure(failures, {
|
|
@@ -490,8 +484,8 @@ function providerResourceIdentifierCheck(root, scope, failures) {
|
|
|
490
484
|
}
|
|
491
485
|
}
|
|
492
486
|
async function configParityChecks(root, failures) {
|
|
493
|
-
if (
|
|
494
|
-
return { name: "config-parity", status: "skipped", detail: "
|
|
487
|
+
if (process.env.TREESEED_RELEASE_CANDIDATE_CONFIG_PARITY_MODE === "skip") {
|
|
488
|
+
return { name: "config-parity", status: "skipped", detail: "Remote config parity skipped by request." };
|
|
495
489
|
}
|
|
496
490
|
const before = failures.length;
|
|
497
491
|
localConfigCheck(root, "staging", failures);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type ReleaseHistoryCommit = {
|
|
2
|
+
sha: string;
|
|
3
|
+
subject: string;
|
|
4
|
+
body: string;
|
|
5
|
+
};
|
|
6
|
+
export type ReleaseHistorySection = 'Added' | 'Changed' | 'Fixed' | 'Infrastructure' | 'Tests' | 'Dependencies';
|
|
7
|
+
export type ReleaseHistorySummary = {
|
|
8
|
+
version: string;
|
|
9
|
+
date: string;
|
|
10
|
+
sourceRef: string;
|
|
11
|
+
targetRef: string;
|
|
12
|
+
commitCount: number;
|
|
13
|
+
sections: Record<ReleaseHistorySection, string[]>;
|
|
14
|
+
notableCommits: ReleaseHistoryCommit[];
|
|
15
|
+
changelogPath: string;
|
|
16
|
+
changelogUpdated: boolean;
|
|
17
|
+
entry: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function collectReleaseHistoryCommits(repoDir: string, sourceRef: string, targetRef: string, options?: {
|
|
20
|
+
maxCommits?: number;
|
|
21
|
+
}): ReleaseHistoryCommit[];
|
|
22
|
+
export declare function renderReleaseChangelogEntry(input: {
|
|
23
|
+
version: string;
|
|
24
|
+
date?: string;
|
|
25
|
+
commits: ReleaseHistoryCommit[];
|
|
26
|
+
extraBullets?: Partial<Record<ReleaseHistorySection, string[]>>;
|
|
27
|
+
}): {
|
|
28
|
+
date: string;
|
|
29
|
+
sections: Record<ReleaseHistorySection, string[]>;
|
|
30
|
+
entry: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function upsertReleaseChangelog(repoDir: string, input: {
|
|
33
|
+
version: string;
|
|
34
|
+
sourceRef: string;
|
|
35
|
+
targetRef: string;
|
|
36
|
+
commits: ReleaseHistoryCommit[];
|
|
37
|
+
extraBullets?: Partial<Record<ReleaseHistorySection, string[]>>;
|
|
38
|
+
}): {
|
|
39
|
+
version: string;
|
|
40
|
+
date: string;
|
|
41
|
+
sourceRef: string;
|
|
42
|
+
targetRef: string;
|
|
43
|
+
commitCount: number;
|
|
44
|
+
sections: Record<ReleaseHistorySection, string[]>;
|
|
45
|
+
notableCommits: ReleaseHistoryCommit[];
|
|
46
|
+
changelogPath: string;
|
|
47
|
+
changelogUpdated: boolean;
|
|
48
|
+
entry: string;
|
|
49
|
+
};
|
|
50
|
+
export declare function renderAdministrativeCommitMessage(input: {
|
|
51
|
+
subject: string;
|
|
52
|
+
version?: string | null;
|
|
53
|
+
tagName?: string | null;
|
|
54
|
+
sourceRef: string;
|
|
55
|
+
targetRef: string;
|
|
56
|
+
commits: ReleaseHistoryCommit[];
|
|
57
|
+
changelog?: ReleaseHistorySummary | null;
|
|
58
|
+
extraLines?: string[];
|
|
59
|
+
}): string;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
const SECTION_ORDER = [
|
|
5
|
+
"Added",
|
|
6
|
+
"Changed",
|
|
7
|
+
"Fixed",
|
|
8
|
+
"Infrastructure",
|
|
9
|
+
"Tests",
|
|
10
|
+
"Dependencies"
|
|
11
|
+
];
|
|
12
|
+
function runGit(repoDir, args) {
|
|
13
|
+
const result = spawnSync("git", args, {
|
|
14
|
+
cwd: repoDir,
|
|
15
|
+
stdio: "pipe",
|
|
16
|
+
encoding: "utf8"
|
|
17
|
+
});
|
|
18
|
+
if (result.status !== 0) {
|
|
19
|
+
throw new Error(result.stderr?.trim() || result.stdout?.trim() || `git ${args.join(" ")} failed`);
|
|
20
|
+
}
|
|
21
|
+
return result.stdout;
|
|
22
|
+
}
|
|
23
|
+
function shortSha(value) {
|
|
24
|
+
return value.slice(0, 12);
|
|
25
|
+
}
|
|
26
|
+
function cleanLine(value) {
|
|
27
|
+
return value.replace(/\s+/gu, " ").trim();
|
|
28
|
+
}
|
|
29
|
+
function bulletText(commit) {
|
|
30
|
+
const subject = cleanLine(commit.subject);
|
|
31
|
+
return subject ? `${subject} (${shortSha(commit.sha)})` : shortSha(commit.sha);
|
|
32
|
+
}
|
|
33
|
+
function sectionForCommit(commit) {
|
|
34
|
+
const value = `${commit.subject}
|
|
35
|
+
${commit.body}`.toLowerCase();
|
|
36
|
+
if (/^(feat|add)(\(|:)/u.test(value) || /\badded?\b/u.test(value)) return "Added";
|
|
37
|
+
if (/^(fix|hotfix)(\(|:)/u.test(value) || /\bfix(e[ds])?\b|\bbug\b/u.test(value)) return "Fixed";
|
|
38
|
+
if (/^(test)(\(|:)/u.test(value) || /\btest(s|ing)?\b|\bverify\b/u.test(value)) return "Tests";
|
|
39
|
+
if (/^(deps?|build)(\(|:)/u.test(value) || /\bdependenc(y|ies)\b|\blockfile\b|\bpackage pointer\b/u.test(value)) return "Dependencies";
|
|
40
|
+
if (/^(ci|chore|release)(\(|:)/u.test(value) || /\bdeploy\b|\bworkflow\b|\brelease\b|\bsubmodule\b/u.test(value)) return "Infrastructure";
|
|
41
|
+
return "Changed";
|
|
42
|
+
}
|
|
43
|
+
function uniqueSectionBullets(commits) {
|
|
44
|
+
const sections = Object.fromEntries(SECTION_ORDER.map((section) => [section, []]));
|
|
45
|
+
const seen = /* @__PURE__ */ new Set();
|
|
46
|
+
for (const commit of commits) {
|
|
47
|
+
const bullet = bulletText(commit);
|
|
48
|
+
const key = bullet.toLowerCase();
|
|
49
|
+
if (seen.has(key)) continue;
|
|
50
|
+
seen.add(key);
|
|
51
|
+
sections[sectionForCommit(commit)].push(bullet);
|
|
52
|
+
}
|
|
53
|
+
return sections;
|
|
54
|
+
}
|
|
55
|
+
function collectReleaseHistoryCommits(repoDir, sourceRef, targetRef, options = {}) {
|
|
56
|
+
const maxCommits = options.maxCommits ?? 80;
|
|
57
|
+
const output = runGit(repoDir, [
|
|
58
|
+
"log",
|
|
59
|
+
"--no-merges",
|
|
60
|
+
`--max-count=${maxCommits}`,
|
|
61
|
+
"--format=%H%x1f%s%x1f%b%x1e",
|
|
62
|
+
`${sourceRef}..${targetRef}`
|
|
63
|
+
]);
|
|
64
|
+
return output.split("").map((entry) => entry.trim()).filter(Boolean).map((entry) => {
|
|
65
|
+
const [sha = "", subject = "", body = ""] = entry.split("");
|
|
66
|
+
return { sha: sha.trim(), subject: subject.trim(), body: body.trim() };
|
|
67
|
+
}).filter((commit) => commit.sha.length > 0);
|
|
68
|
+
}
|
|
69
|
+
function renderReleaseChangelogEntry(input) {
|
|
70
|
+
const date = input.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
71
|
+
const sections = uniqueSectionBullets(input.commits);
|
|
72
|
+
for (const [section, bullets] of Object.entries(input.extraBullets ?? {})) {
|
|
73
|
+
for (const bullet of bullets ?? []) {
|
|
74
|
+
const normalized = cleanLine(bullet);
|
|
75
|
+
if (normalized) sections[section].push(normalized);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const lines = [`## [${input.version}] - ${date}`, ""];
|
|
79
|
+
let wroteSection = false;
|
|
80
|
+
for (const section of SECTION_ORDER) {
|
|
81
|
+
const bullets = sections[section];
|
|
82
|
+
if (bullets.length === 0) continue;
|
|
83
|
+
wroteSection = true;
|
|
84
|
+
lines.push(`### ${section}`, "");
|
|
85
|
+
for (const bullet of bullets.slice(0, 20)) {
|
|
86
|
+
lines.push(`- ${bullet}`);
|
|
87
|
+
}
|
|
88
|
+
if (bullets.length > 20) {
|
|
89
|
+
lines.push(`- ${bullets.length - 20} additional change${bullets.length - 20 === 1 ? "" : "s"} omitted from this summary.`);
|
|
90
|
+
}
|
|
91
|
+
lines.push("");
|
|
92
|
+
}
|
|
93
|
+
if (!wroteSection) {
|
|
94
|
+
lines.push("### Changed", "", "- Release metadata and deployment history updated.", "");
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
date,
|
|
98
|
+
sections,
|
|
99
|
+
entry: lines.join("\n").trimEnd()
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function upsertReleaseChangelog(repoDir, input) {
|
|
103
|
+
const rendered = renderReleaseChangelogEntry(input);
|
|
104
|
+
const changelogPath = resolve(repoDir, "CHANGELOG.md");
|
|
105
|
+
const current = existsSync(changelogPath) ? readFileSync(changelogPath, "utf8") : "";
|
|
106
|
+
const title = "# Changelog";
|
|
107
|
+
const withoutExisting = current.replace(new RegExp(`^# Changelog\\s*\\n+## \\[${input.version.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}\\][\\s\\S]*?(?=\\n## \\[|$)`, "u"), `${title}
|
|
108
|
+
|
|
109
|
+
`).trim();
|
|
110
|
+
const body = withoutExisting.startsWith(title) ? withoutExisting.slice(title.length).trim() : withoutExisting.trim();
|
|
111
|
+
const next = `${title}
|
|
112
|
+
|
|
113
|
+
${rendered.entry}${body ? `
|
|
114
|
+
|
|
115
|
+
${body}` : ""}
|
|
116
|
+
`;
|
|
117
|
+
const changed = current !== next;
|
|
118
|
+
if (changed) {
|
|
119
|
+
writeFileSync(changelogPath, next, "utf8");
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
version: input.version,
|
|
123
|
+
date: rendered.date,
|
|
124
|
+
sourceRef: input.sourceRef,
|
|
125
|
+
targetRef: input.targetRef,
|
|
126
|
+
commitCount: input.commits.length,
|
|
127
|
+
sections: rendered.sections,
|
|
128
|
+
notableCommits: input.commits.slice(0, 12),
|
|
129
|
+
changelogPath,
|
|
130
|
+
changelogUpdated: changed,
|
|
131
|
+
entry: rendered.entry
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function renderAdministrativeCommitMessage(input) {
|
|
135
|
+
const lines = [
|
|
136
|
+
input.subject,
|
|
137
|
+
"",
|
|
138
|
+
"Release summary:",
|
|
139
|
+
input.version ? `- Version: ${input.version}` : null,
|
|
140
|
+
input.tagName ? `- Tag: ${input.tagName}` : null,
|
|
141
|
+
`- Source: ${input.sourceRef}`,
|
|
142
|
+
`- Target: ${input.targetRef}`,
|
|
143
|
+
`- Promoted commits: ${input.commits.length}`,
|
|
144
|
+
...(input.extraLines ?? []).map((line) => `- ${line}`),
|
|
145
|
+
"",
|
|
146
|
+
"Notable changes:",
|
|
147
|
+
...input.commits.length > 0 ? input.commits.slice(0, 12).map((commit) => `- ${bulletText(commit)}`) : ["- Release metadata and package pointers updated."],
|
|
148
|
+
input.commits.length > 12 ? `- ${input.commits.length - 12} additional promoted commit${input.commits.length - 12 === 1 ? "" : "s"} omitted from this summary.` : null,
|
|
149
|
+
input.changelog ? "" : null,
|
|
150
|
+
input.changelog ? "See CHANGELOG.md for the release history entry." : null
|
|
151
|
+
].filter((line) => line !== null);
|
|
152
|
+
return lines.join("\n");
|
|
153
|
+
}
|
|
154
|
+
export {
|
|
155
|
+
collectReleaseHistoryCommits,
|
|
156
|
+
renderAdministrativeCommitMessage,
|
|
157
|
+
renderReleaseChangelogEntry,
|
|
158
|
+
upsertReleaseChangelog
|
|
159
|
+
};
|