@treeseed/sdk 0.4.12 → 0.4.13
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/providers/default.js +2 -0
- package/dist/operations/services/git-workflow.d.ts +47 -3
- package/dist/operations/services/git-workflow.js +125 -19
- package/dist/operations/services/github-automation.d.ts +25 -0
- package/dist/operations/services/github-automation.js +82 -1
- package/dist/operations/services/workspace-save.d.ts +10 -1
- package/dist/operations/services/workspace-save.js +54 -3
- package/dist/operations/services/workspace-tools.d.ts +1 -0
- package/dist/operations/services/workspace-tools.js +20 -5
- package/dist/operations-registry.js +8 -6
- package/dist/operations-types.d.ts +2 -2
- package/dist/scripts/workspace-start-warning.js +2 -2
- package/dist/workflow/operations.d.ts +515 -264
- package/dist/workflow/operations.js +1680 -213
- package/dist/workflow/runs.d.ts +90 -0
- package/dist/workflow/runs.js +242 -0
- package/dist/workflow/session.d.ts +31 -0
- package/dist/workflow/session.js +97 -0
- package/dist/workflow-state.d.ts +34 -0
- package/dist/workflow-state.js +118 -2
- package/dist/workflow.d.ts +64 -3
- package/dist/workflow.js +12 -0
- package/package.json +1 -1
- package/dist/scripts/workspace-close.js +0 -24
- package/dist/scripts/workspace-release.js +0 -42
- package/dist/scripts/workspace-start.js +0 -71
|
@@ -454,6 +454,8 @@ class DefaultTreeseedOperationsProvider {
|
|
|
454
454
|
new WorkflowOperation("save"),
|
|
455
455
|
new WorkflowOperation("close"),
|
|
456
456
|
new WorkflowOperation("stage"),
|
|
457
|
+
new WorkflowOperation("resume"),
|
|
458
|
+
new WorkflowOperation("recover"),
|
|
457
459
|
new WorkflowOperation("config"),
|
|
458
460
|
new WorkflowOperation("export"),
|
|
459
461
|
new WorkflowOperation("release"),
|
|
@@ -1,24 +1,55 @@
|
|
|
1
1
|
export declare const STAGING_BRANCH = "staging";
|
|
2
2
|
export declare const PRODUCTION_BRANCH = "main";
|
|
3
|
+
export declare function headCommit(repoDir: any, ref?: string): string;
|
|
3
4
|
export declare function gitWorkflowRoot(cwd?: any): string;
|
|
4
5
|
export declare function assertCleanWorktree(cwd?: any): string;
|
|
6
|
+
export declare function assertCleanWorktrees(repoDirs: any): any;
|
|
5
7
|
export declare function branchExists(repoDir: any, branchName: any): boolean;
|
|
6
8
|
export declare function remoteBranchExists(repoDir: any, branchName: any): boolean;
|
|
7
9
|
export declare function fetchOrigin(repoDir: any): void;
|
|
8
10
|
export declare function ensureLocalBranchTracking(repoDir: any, branchName: any): void;
|
|
9
11
|
export declare function checkoutBranch(repoDir: any, branchName: any): void;
|
|
12
|
+
export declare function checkoutTaskBranchFromStaging(cwd: any, branchName: any, { createIfMissing, pushIfCreated }?: {
|
|
13
|
+
createIfMissing?: boolean | undefined;
|
|
14
|
+
pushIfCreated?: boolean | undefined;
|
|
15
|
+
}): {
|
|
16
|
+
repoDir: string;
|
|
17
|
+
branchName: any;
|
|
18
|
+
baseBranch: string;
|
|
19
|
+
created: boolean;
|
|
20
|
+
resumed: boolean;
|
|
21
|
+
remoteBranch: boolean;
|
|
22
|
+
};
|
|
10
23
|
export declare function syncBranchWithOrigin(repoDir: any, branchName: any): void;
|
|
11
24
|
export declare function createFeatureBranchFromStaging(cwd: any, branchName: any): {
|
|
12
25
|
repoDir: string;
|
|
13
|
-
baseBranch: string;
|
|
14
26
|
branchName: any;
|
|
27
|
+
baseBranch: string;
|
|
28
|
+
created: boolean;
|
|
29
|
+
resumed: boolean;
|
|
30
|
+
remoteBranch: boolean;
|
|
15
31
|
};
|
|
16
32
|
export declare function pushBranch(repoDir: any, branchName: any, { setUpstream }?: {
|
|
17
33
|
setUpstream?: boolean | undefined;
|
|
18
34
|
}): void;
|
|
19
35
|
export declare function deleteLocalBranch(repoDir: any, branchName: any): void;
|
|
20
36
|
export declare function deleteRemoteBranch(repoDir: any, branchName: any): boolean;
|
|
21
|
-
export declare function mergeCurrentBranchIntoStaging(cwd: any, featureBranch: any):
|
|
37
|
+
export declare function mergeCurrentBranchIntoStaging(cwd: any, featureBranch: any): {
|
|
38
|
+
repoDir: string;
|
|
39
|
+
targetBranch: string;
|
|
40
|
+
committed: boolean;
|
|
41
|
+
commitSha: string;
|
|
42
|
+
pushed: boolean;
|
|
43
|
+
};
|
|
44
|
+
export declare function squashMergeBranchIntoStaging(cwd: any, featureBranch: any, message: any, { pushTarget }?: {
|
|
45
|
+
pushTarget?: boolean | undefined;
|
|
46
|
+
}): {
|
|
47
|
+
repoDir: string;
|
|
48
|
+
targetBranch: string;
|
|
49
|
+
committed: boolean;
|
|
50
|
+
commitSha: string;
|
|
51
|
+
pushed: boolean;
|
|
52
|
+
};
|
|
22
53
|
export declare function currentManagedBranch(cwd?: any): string;
|
|
23
54
|
export declare function isTaskBranch(branchName: any): boolean;
|
|
24
55
|
export declare function assertFeatureBranch(cwd?: any): string;
|
|
@@ -46,4 +77,17 @@ export declare function waitForStagingAutomation(repoDir: any): {
|
|
|
46
77
|
reason?: undefined;
|
|
47
78
|
};
|
|
48
79
|
export declare function prepareReleaseBranches(cwd?: any): string;
|
|
49
|
-
export declare function mergeStagingIntoMain(cwd?: any):
|
|
80
|
+
export declare function mergeStagingIntoMain(cwd?: any): {
|
|
81
|
+
repoDir: string;
|
|
82
|
+
targetBranch: any;
|
|
83
|
+
commitSha: string;
|
|
84
|
+
pushed: boolean;
|
|
85
|
+
};
|
|
86
|
+
export declare function mergeBranchIntoTarget(cwd?: any, { sourceBranch, targetBranch, message, pushTarget }?: {
|
|
87
|
+
pushTarget?: boolean | undefined;
|
|
88
|
+
}): {
|
|
89
|
+
repoDir: string;
|
|
90
|
+
targetBranch: any;
|
|
91
|
+
commitSha: string;
|
|
92
|
+
pushed: boolean;
|
|
93
|
+
};
|
|
@@ -6,6 +6,17 @@ const RESERVED_BRANCHES = /* @__PURE__ */ new Set([STAGING_BRANCH, PRODUCTION_BR
|
|
|
6
6
|
function runGit(args, { cwd, capture = false } = {}) {
|
|
7
7
|
return run("git", args, { cwd, capture });
|
|
8
8
|
}
|
|
9
|
+
function repoHasStagedChanges(repoDir) {
|
|
10
|
+
try {
|
|
11
|
+
runGit(["diff", "--cached", "--quiet"], { cwd: repoDir });
|
|
12
|
+
return false;
|
|
13
|
+
} catch {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function headCommit(repoDir, ref = "HEAD") {
|
|
18
|
+
return runGit(["rev-parse", ref], { cwd: repoDir, capture: true }).trim();
|
|
19
|
+
}
|
|
9
20
|
function gitWorkflowRoot(cwd = workspaceRoot()) {
|
|
10
21
|
return repoRoot(cwd);
|
|
11
22
|
}
|
|
@@ -16,6 +27,12 @@ function assertCleanWorktree(cwd = workspaceRoot()) {
|
|
|
16
27
|
}
|
|
17
28
|
return root;
|
|
18
29
|
}
|
|
30
|
+
function assertCleanWorktrees(repoDirs) {
|
|
31
|
+
for (const repoDir of repoDirs) {
|
|
32
|
+
assertCleanWorktree(repoDir);
|
|
33
|
+
}
|
|
34
|
+
return repoDirs;
|
|
35
|
+
}
|
|
19
36
|
function branchExists(repoDir, branchName) {
|
|
20
37
|
try {
|
|
21
38
|
runGit(["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`], { cwd: repoDir });
|
|
@@ -48,6 +65,63 @@ function ensureLocalBranchTracking(repoDir, branchName) {
|
|
|
48
65
|
function checkoutBranch(repoDir, branchName) {
|
|
49
66
|
runGit(["checkout", branchName], { cwd: repoDir });
|
|
50
67
|
}
|
|
68
|
+
function checkoutTaskBranchFromStaging(cwd, branchName, { createIfMissing = true, pushIfCreated = false } = {}) {
|
|
69
|
+
const repoDir = assertCleanWorktree(cwd);
|
|
70
|
+
fetchOrigin(repoDir);
|
|
71
|
+
syncBranchWithOrigin(repoDir, STAGING_BRANCH);
|
|
72
|
+
if (currentBranch(repoDir) === branchName) {
|
|
73
|
+
return {
|
|
74
|
+
repoDir,
|
|
75
|
+
branchName,
|
|
76
|
+
baseBranch: STAGING_BRANCH,
|
|
77
|
+
created: false,
|
|
78
|
+
resumed: true,
|
|
79
|
+
remoteBranch: remoteBranchExists(repoDir, branchName)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (branchExists(repoDir, branchName)) {
|
|
83
|
+
checkoutBranch(repoDir, branchName);
|
|
84
|
+
if (remoteBranchExists(repoDir, branchName)) {
|
|
85
|
+
runGit(["pull", "--rebase", "origin", branchName], { cwd: repoDir });
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
repoDir,
|
|
89
|
+
branchName,
|
|
90
|
+
baseBranch: STAGING_BRANCH,
|
|
91
|
+
created: false,
|
|
92
|
+
resumed: true,
|
|
93
|
+
remoteBranch: remoteBranchExists(repoDir, branchName)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (remoteBranchExists(repoDir, branchName)) {
|
|
97
|
+
runGit(["checkout", "-b", branchName, `origin/${branchName}`], { cwd: repoDir });
|
|
98
|
+
runGit(["pull", "--rebase", "origin", branchName], { cwd: repoDir });
|
|
99
|
+
return {
|
|
100
|
+
repoDir,
|
|
101
|
+
branchName,
|
|
102
|
+
baseBranch: STAGING_BRANCH,
|
|
103
|
+
created: false,
|
|
104
|
+
resumed: true,
|
|
105
|
+
remoteBranch: true
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (!createIfMissing) {
|
|
109
|
+
throw new Error(`Branch "${branchName}" does not exist locally or on origin.`);
|
|
110
|
+
}
|
|
111
|
+
checkoutBranch(repoDir, STAGING_BRANCH);
|
|
112
|
+
runGit(["checkout", "-b", branchName], { cwd: repoDir });
|
|
113
|
+
if (pushIfCreated) {
|
|
114
|
+
pushBranch(repoDir, branchName, { setUpstream: true });
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
repoDir,
|
|
118
|
+
branchName,
|
|
119
|
+
baseBranch: STAGING_BRANCH,
|
|
120
|
+
created: true,
|
|
121
|
+
resumed: false,
|
|
122
|
+
remoteBranch: pushIfCreated
|
|
123
|
+
};
|
|
124
|
+
}
|
|
51
125
|
function syncBranchWithOrigin(repoDir, branchName) {
|
|
52
126
|
fetchOrigin(repoDir);
|
|
53
127
|
if (!branchExists(repoDir, branchName) && remoteBranchExists(repoDir, branchName)) {
|
|
@@ -60,18 +134,14 @@ function syncBranchWithOrigin(repoDir, branchName) {
|
|
|
60
134
|
}
|
|
61
135
|
}
|
|
62
136
|
function createFeatureBranchFromStaging(cwd, branchName) {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
137
|
+
const result = checkoutTaskBranchFromStaging(cwd, branchName, {
|
|
138
|
+
createIfMissing: true,
|
|
139
|
+
pushIfCreated: false
|
|
140
|
+
});
|
|
141
|
+
if (!result.created) {
|
|
66
142
|
throw new Error(`Branch "${branchName}" already exists locally or on origin.`);
|
|
67
143
|
}
|
|
68
|
-
|
|
69
|
-
runGit(["checkout", "-b", branchName], { cwd: repoDir });
|
|
70
|
-
return {
|
|
71
|
-
repoDir,
|
|
72
|
-
baseBranch: STAGING_BRANCH,
|
|
73
|
-
branchName
|
|
74
|
-
};
|
|
144
|
+
return result;
|
|
75
145
|
}
|
|
76
146
|
function pushBranch(repoDir, branchName, { setUpstream = false } = {}) {
|
|
77
147
|
const args = setUpstream ? ["push", "-u", "origin", branchName] : ["push", "origin", branchName];
|
|
@@ -91,12 +161,28 @@ function deleteRemoteBranch(repoDir, branchName) {
|
|
|
91
161
|
return true;
|
|
92
162
|
}
|
|
93
163
|
function mergeCurrentBranchIntoStaging(cwd, featureBranch) {
|
|
164
|
+
return squashMergeBranchIntoStaging(cwd, featureBranch, `stage: ${featureBranch}`);
|
|
165
|
+
}
|
|
166
|
+
function squashMergeBranchIntoStaging(cwd, featureBranch, message, { pushTarget = true } = {}) {
|
|
94
167
|
const repoDir = assertCleanWorktree(cwd);
|
|
95
168
|
fetchOrigin(repoDir);
|
|
96
169
|
syncBranchWithOrigin(repoDir, STAGING_BRANCH);
|
|
97
|
-
runGit(["merge", "--
|
|
98
|
-
|
|
99
|
-
|
|
170
|
+
runGit(["merge", "--squash", featureBranch], { cwd: repoDir });
|
|
171
|
+
let committed = false;
|
|
172
|
+
if (repoHasStagedChanges(repoDir)) {
|
|
173
|
+
runGit(["commit", "-m", message], { cwd: repoDir });
|
|
174
|
+
committed = true;
|
|
175
|
+
}
|
|
176
|
+
if (pushTarget) {
|
|
177
|
+
pushBranch(repoDir, STAGING_BRANCH);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
repoDir,
|
|
181
|
+
targetBranch: STAGING_BRANCH,
|
|
182
|
+
committed,
|
|
183
|
+
commitSha: headCommit(repoDir),
|
|
184
|
+
pushed: pushTarget
|
|
185
|
+
};
|
|
100
186
|
}
|
|
101
187
|
function currentManagedBranch(cwd = workspaceRoot()) {
|
|
102
188
|
return currentBranch(gitWorkflowRoot(cwd));
|
|
@@ -180,23 +266,40 @@ function prepareReleaseBranches(cwd = workspaceRoot()) {
|
|
|
180
266
|
return repoDir;
|
|
181
267
|
}
|
|
182
268
|
function mergeStagingIntoMain(cwd = workspaceRoot()) {
|
|
269
|
+
return mergeBranchIntoTarget(cwd, {
|
|
270
|
+
sourceBranch: STAGING_BRANCH,
|
|
271
|
+
targetBranch: PRODUCTION_BRANCH,
|
|
272
|
+
message: `release: ${STAGING_BRANCH} -> ${PRODUCTION_BRANCH}`,
|
|
273
|
+
pushTarget: true
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function mergeBranchIntoTarget(cwd = workspaceRoot(), { sourceBranch, targetBranch, message, pushTarget = true } = {}) {
|
|
183
277
|
const repoDir = prepareReleaseBranches(cwd);
|
|
184
|
-
checkoutBranch(repoDir,
|
|
185
|
-
if (remoteBranchExists(repoDir,
|
|
186
|
-
runGit(["pull", "--rebase", "origin",
|
|
278
|
+
checkoutBranch(repoDir, targetBranch);
|
|
279
|
+
if (remoteBranchExists(repoDir, targetBranch)) {
|
|
280
|
+
runGit(["pull", "--rebase", "origin", targetBranch], { cwd: repoDir });
|
|
187
281
|
}
|
|
188
|
-
runGit(["merge", "--no-ff",
|
|
282
|
+
runGit(["merge", "--no-ff", sourceBranch, "-m", message], { cwd: repoDir });
|
|
189
283
|
pushBranch(repoDir, STAGING_BRANCH);
|
|
190
|
-
|
|
191
|
-
|
|
284
|
+
if (pushTarget) {
|
|
285
|
+
pushBranch(repoDir, targetBranch);
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
repoDir,
|
|
289
|
+
targetBranch,
|
|
290
|
+
commitSha: headCommit(repoDir),
|
|
291
|
+
pushed: pushTarget
|
|
292
|
+
};
|
|
192
293
|
}
|
|
193
294
|
export {
|
|
194
295
|
PRODUCTION_BRANCH,
|
|
195
296
|
STAGING_BRANCH,
|
|
196
297
|
assertCleanWorktree,
|
|
298
|
+
assertCleanWorktrees,
|
|
197
299
|
assertFeatureBranch,
|
|
198
300
|
branchExists,
|
|
199
301
|
checkoutBranch,
|
|
302
|
+
checkoutTaskBranchFromStaging,
|
|
200
303
|
createDeprecatedTaskTag,
|
|
201
304
|
createFeatureBranchFromStaging,
|
|
202
305
|
currentManagedBranch,
|
|
@@ -205,13 +308,16 @@ export {
|
|
|
205
308
|
ensureLocalBranchTracking,
|
|
206
309
|
fetchOrigin,
|
|
207
310
|
gitWorkflowRoot,
|
|
311
|
+
headCommit,
|
|
208
312
|
isTaskBranch,
|
|
209
313
|
listTaskBranches,
|
|
314
|
+
mergeBranchIntoTarget,
|
|
210
315
|
mergeCurrentBranchIntoStaging,
|
|
211
316
|
mergeStagingIntoMain,
|
|
212
317
|
prepareReleaseBranches,
|
|
213
318
|
pushBranch,
|
|
214
319
|
remoteBranchExists,
|
|
320
|
+
squashMergeBranchIntoStaging,
|
|
215
321
|
syncBranchWithOrigin,
|
|
216
322
|
taskTagSlug,
|
|
217
323
|
waitForStagingAutomation
|
|
@@ -190,3 +190,28 @@ export declare function ensureGitHubDeployAutomation(tenantRoot: any, { dryRun }
|
|
|
190
190
|
mode?: undefined;
|
|
191
191
|
};
|
|
192
192
|
};
|
|
193
|
+
export declare function waitForGitHubWorkflowCompletion(tenantRoot: any, { repository, workflow, headSha, branch, timeoutSeconds, pollSeconds, }?: {
|
|
194
|
+
workflow?: string | undefined;
|
|
195
|
+
timeoutSeconds?: number | undefined;
|
|
196
|
+
pollSeconds?: number | undefined;
|
|
197
|
+
}): {
|
|
198
|
+
status: string;
|
|
199
|
+
reason: string;
|
|
200
|
+
repository: any;
|
|
201
|
+
workflow: string;
|
|
202
|
+
headSha: any;
|
|
203
|
+
branch: any;
|
|
204
|
+
runId?: undefined;
|
|
205
|
+
conclusion?: undefined;
|
|
206
|
+
url?: undefined;
|
|
207
|
+
} | {
|
|
208
|
+
status: string;
|
|
209
|
+
repository: any;
|
|
210
|
+
workflow: string;
|
|
211
|
+
runId: any;
|
|
212
|
+
headSha: any;
|
|
213
|
+
conclusion: any;
|
|
214
|
+
url: any;
|
|
215
|
+
reason?: undefined;
|
|
216
|
+
branch?: undefined;
|
|
217
|
+
};
|
|
@@ -53,6 +53,14 @@ function runGh(args, { cwd, allowFailure = false, capture = true, input } = {})
|
|
|
53
53
|
}
|
|
54
54
|
return result;
|
|
55
55
|
}
|
|
56
|
+
function sleepSeconds(seconds) {
|
|
57
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
spawnSync("sleep", [String(seconds)], {
|
|
61
|
+
stdio: "ignore"
|
|
62
|
+
});
|
|
63
|
+
}
|
|
56
64
|
function resolveGitHubRepositorySlug(tenantRoot) {
|
|
57
65
|
const remoteResult = runGit(["remote", "get-url", "origin"], { cwd: tenantRoot });
|
|
58
66
|
const remoteUrl = remoteResult.stdout?.trim() ?? "";
|
|
@@ -291,6 +299,78 @@ function ensureGitHubDeployAutomation(tenantRoot, { dryRun = false } = {}) {
|
|
|
291
299
|
environment
|
|
292
300
|
};
|
|
293
301
|
}
|
|
302
|
+
function waitForGitHubWorkflowCompletion(tenantRoot, {
|
|
303
|
+
repository,
|
|
304
|
+
workflow = "publish.yml",
|
|
305
|
+
headSha,
|
|
306
|
+
branch,
|
|
307
|
+
timeoutSeconds = 600,
|
|
308
|
+
pollSeconds = 5
|
|
309
|
+
} = {}) {
|
|
310
|
+
if (isGitHubAutomationStubbed()) {
|
|
311
|
+
return {
|
|
312
|
+
status: "skipped",
|
|
313
|
+
reason: "stubbed",
|
|
314
|
+
repository: repository ?? maybeResolveGitHubRepositorySlug(tenantRoot),
|
|
315
|
+
workflow,
|
|
316
|
+
headSha: headSha ?? null,
|
|
317
|
+
branch: branch ?? null
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const repo = repository ?? resolveGitHubRepositorySlug(tenantRoot);
|
|
321
|
+
const startedAt = Date.now();
|
|
322
|
+
while (Date.now() - startedAt < timeoutSeconds * 1e3) {
|
|
323
|
+
const result = runGh([
|
|
324
|
+
"run",
|
|
325
|
+
"list",
|
|
326
|
+
"--repo",
|
|
327
|
+
repo,
|
|
328
|
+
"--workflow",
|
|
329
|
+
workflow,
|
|
330
|
+
"--limit",
|
|
331
|
+
"20",
|
|
332
|
+
"--json",
|
|
333
|
+
"databaseId,headSha,headBranch,status,conclusion,event,displayTitle,url"
|
|
334
|
+
], { cwd: tenantRoot });
|
|
335
|
+
const runs = JSON.parse(result.stdout || "[]");
|
|
336
|
+
const match = runs.find((run) => {
|
|
337
|
+
if (headSha && run?.headSha !== headSha) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
if (branch && run?.headBranch !== branch) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
return true;
|
|
344
|
+
});
|
|
345
|
+
if (match?.databaseId) {
|
|
346
|
+
runGh(["run", "watch", String(match.databaseId), "--repo", repo, "--exit-status"], {
|
|
347
|
+
cwd: tenantRoot,
|
|
348
|
+
capture: false
|
|
349
|
+
});
|
|
350
|
+
const finalResult = runGh([
|
|
351
|
+
"run",
|
|
352
|
+
"view",
|
|
353
|
+
String(match.databaseId),
|
|
354
|
+
"--repo",
|
|
355
|
+
repo,
|
|
356
|
+
"--json",
|
|
357
|
+
"status,conclusion,url,workflowName,headSha"
|
|
358
|
+
], { cwd: tenantRoot });
|
|
359
|
+
const finalRun = JSON.parse(finalResult.stdout || "{}");
|
|
360
|
+
return {
|
|
361
|
+
status: "completed",
|
|
362
|
+
repository: repo,
|
|
363
|
+
workflow,
|
|
364
|
+
runId: match.databaseId,
|
|
365
|
+
headSha: finalRun.headSha ?? match.headSha ?? null,
|
|
366
|
+
conclusion: finalRun.conclusion ?? match.conclusion ?? null,
|
|
367
|
+
url: finalRun.url ?? match.url ?? null
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
sleepSeconds(pollSeconds);
|
|
371
|
+
}
|
|
372
|
+
throw new Error(`Timed out waiting for GitHub workflow ${workflow} in ${repo}.`);
|
|
373
|
+
}
|
|
294
374
|
export {
|
|
295
375
|
ensureDeployWorkflow,
|
|
296
376
|
ensureGitHubDeployAutomation,
|
|
@@ -309,5 +389,6 @@ export {
|
|
|
309
389
|
requiredGitHubEnvironment,
|
|
310
390
|
requiredGitHubSecrets,
|
|
311
391
|
resolveGitHubRepositorySlug,
|
|
312
|
-
resolveGitRepositoryRoot
|
|
392
|
+
resolveGitRepositoryRoot,
|
|
393
|
+
waitForGitHubWorkflowCompletion
|
|
313
394
|
};
|
|
@@ -8,12 +8,21 @@ export declare function planWorkspaceVersionChanges(root?: any): {
|
|
|
8
8
|
touched: Set<unknown>;
|
|
9
9
|
};
|
|
10
10
|
export declare function applyWorkspaceVersionChanges(plan: any): any;
|
|
11
|
-
export declare function planWorkspaceReleaseBump(level?: string, root?: any): {
|
|
11
|
+
export declare function planWorkspaceReleaseBump(level?: string, root?: any, options?: {}): {
|
|
12
12
|
packages: any[];
|
|
13
13
|
touched: Set<unknown>;
|
|
14
14
|
versions: Map<any, any>;
|
|
15
15
|
level: string;
|
|
16
|
+
selected: Set<any>;
|
|
16
17
|
};
|
|
18
|
+
export declare function collectWorkspaceVersionConsistencyIssues(root?: any): {
|
|
19
|
+
packageName: any;
|
|
20
|
+
dependencyName: string;
|
|
21
|
+
field: string;
|
|
22
|
+
currentSpec: unknown;
|
|
23
|
+
expectedSpec: string;
|
|
24
|
+
}[];
|
|
25
|
+
export declare function assertWorkspaceVersionConsistency(root?: any): void;
|
|
17
26
|
export declare function repoRoot(cwd?: any): string;
|
|
18
27
|
export declare function currentBranch(repoDir: any): string;
|
|
19
28
|
export declare function originRemoteUrl(repoDir: any): string;
|
|
@@ -113,17 +113,20 @@ function applyWorkspaceVersionChanges(plan) {
|
|
|
113
113
|
}
|
|
114
114
|
return plan;
|
|
115
115
|
}
|
|
116
|
-
function planWorkspaceReleaseBump(level = "patch", root = workspaceRoot()) {
|
|
116
|
+
function planWorkspaceReleaseBump(level = "patch", root = workspaceRoot(), options = {}) {
|
|
117
117
|
const packages = workspacePackages(root).map((pkg) => ({
|
|
118
118
|
...pkg,
|
|
119
119
|
packageJsonPath: resolve(pkg.dir, "package.json"),
|
|
120
120
|
packageJson: readPackageJson(resolve(pkg.dir, "package.json"))
|
|
121
121
|
}));
|
|
122
122
|
const publishable = new Set(publishableWorkspacePackages(root).map((pkg) => pkg.name));
|
|
123
|
+
const selected = options.selectedPackageNames ? new Set(
|
|
124
|
+
[...options.selectedPackageNames].map((name) => String(name)).filter((name) => publishable.has(name))
|
|
125
|
+
) : new Set(publishable);
|
|
123
126
|
const touched = /* @__PURE__ */ new Set();
|
|
124
127
|
const versions = /* @__PURE__ */ new Map();
|
|
125
128
|
for (const pkg of packages) {
|
|
126
|
-
if (!publishable.has(pkg.name)) {
|
|
129
|
+
if (!publishable.has(pkg.name) || !selected.has(pkg.name)) {
|
|
127
130
|
continue;
|
|
128
131
|
}
|
|
129
132
|
const nextVersion = incrementVersion(pkg.packageJson.version, level);
|
|
@@ -132,6 +135,9 @@ function planWorkspaceReleaseBump(level = "patch", root = workspaceRoot()) {
|
|
|
132
135
|
touched.add(pkg.name);
|
|
133
136
|
}
|
|
134
137
|
for (const pkg of packages) {
|
|
138
|
+
if (!selected.has(pkg.name)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
135
141
|
for (const field of internalDependencyFields(pkg.packageJson)) {
|
|
136
142
|
for (const depName of Object.keys(pkg.packageJson[field] ?? {})) {
|
|
137
143
|
if (!versions.has(depName)) {
|
|
@@ -146,9 +152,52 @@ function planWorkspaceReleaseBump(level = "patch", root = workspaceRoot()) {
|
|
|
146
152
|
packages,
|
|
147
153
|
touched,
|
|
148
154
|
versions,
|
|
149
|
-
level
|
|
155
|
+
level,
|
|
156
|
+
selected
|
|
150
157
|
};
|
|
151
158
|
}
|
|
159
|
+
function collectWorkspaceVersionConsistencyIssues(root = workspaceRoot()) {
|
|
160
|
+
const packages = workspacePackages(root).map((pkg) => ({
|
|
161
|
+
...pkg,
|
|
162
|
+
packageJson: readPackageJson(resolve(pkg.dir, "package.json"))
|
|
163
|
+
}));
|
|
164
|
+
const versions = new Map(packages.map((pkg) => [pkg.name, pkg.packageJson.version]));
|
|
165
|
+
const issues = [];
|
|
166
|
+
for (const pkg of packages) {
|
|
167
|
+
for (const field of internalDependencyFields(pkg.packageJson)) {
|
|
168
|
+
for (const [depName, currentSpec] of Object.entries(pkg.packageJson[field] ?? {})) {
|
|
169
|
+
if (!versions.has(depName)) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const expectedSpec = `^${versions.get(depName)}`;
|
|
173
|
+
if (currentSpec !== expectedSpec) {
|
|
174
|
+
issues.push({
|
|
175
|
+
packageName: pkg.name,
|
|
176
|
+
dependencyName: depName,
|
|
177
|
+
field,
|
|
178
|
+
currentSpec,
|
|
179
|
+
expectedSpec
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return issues;
|
|
186
|
+
}
|
|
187
|
+
function assertWorkspaceVersionConsistency(root = workspaceRoot()) {
|
|
188
|
+
const issues = collectWorkspaceVersionConsistencyIssues(root);
|
|
189
|
+
if (issues.length === 0) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const rendered = issues.map((issue) => `${issue.packageName} ${issue.field}.${issue.dependencyName}=${issue.currentSpec} expected ${issue.expectedSpec}`).join("\n");
|
|
193
|
+
throw new Error(
|
|
194
|
+
[
|
|
195
|
+
"Treeseed save found inconsistent checked-out package dependency versions.",
|
|
196
|
+
"Resolve the package manifest drift before saving.",
|
|
197
|
+
rendered
|
|
198
|
+
].join("\n")
|
|
199
|
+
);
|
|
200
|
+
}
|
|
152
201
|
function repoRoot(cwd = workspaceRoot()) {
|
|
153
202
|
return run("git", ["rev-parse", "--show-toplevel"], { cwd, capture: true }).trim();
|
|
154
203
|
}
|
|
@@ -220,7 +269,9 @@ function formatMergeConflictReport(report, repoDir, targetBranch = "main") {
|
|
|
220
269
|
export {
|
|
221
270
|
MERGE_CONFLICT_EXIT_CODE,
|
|
222
271
|
applyWorkspaceVersionChanges,
|
|
272
|
+
assertWorkspaceVersionConsistency,
|
|
223
273
|
collectMergeConflictReport,
|
|
274
|
+
collectWorkspaceVersionConsistencyIssues,
|
|
224
275
|
countConflictMarkers,
|
|
225
276
|
currentBranch,
|
|
226
277
|
formatMergeConflictReport,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const TREESEED_WORKSPACE_PACKAGE_DIRS: string[];
|
|
1
2
|
export declare function workspaceRoot(startCwd?: string): any;
|
|
2
3
|
export declare function findNearestTreeseedRoot(startCwd?: string): string | null;
|
|
3
4
|
export declare function isWorkspaceRoot(root?: string): any;
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { join, relative, resolve } from "node:path";
|
|
4
|
-
const TREESEED_WORKSPACE_PACKAGE_DIRS = ["sdk", "core", "cli"
|
|
4
|
+
const TREESEED_WORKSPACE_PACKAGE_DIRS = ["sdk", "core", "cli"];
|
|
5
|
+
function packageSortWeight(pkg) {
|
|
6
|
+
const relativeDir = String(pkg.relativeDir ?? "");
|
|
7
|
+
const dirName = relativeDir.split("/").pop() ?? "";
|
|
8
|
+
const index = TREESEED_WORKSPACE_PACKAGE_DIRS.indexOf(dirName);
|
|
9
|
+
return index >= 0 ? index : Number.MAX_SAFE_INTEGER;
|
|
10
|
+
}
|
|
11
|
+
function compareWorkspacePackages(left, right) {
|
|
12
|
+
const leftWeight = packageSortWeight(left);
|
|
13
|
+
const rightWeight = packageSortWeight(right);
|
|
14
|
+
if (leftWeight !== rightWeight) {
|
|
15
|
+
return leftWeight - rightWeight;
|
|
16
|
+
}
|
|
17
|
+
return left.name.localeCompare(right.name);
|
|
18
|
+
}
|
|
5
19
|
function escapeRegex(source) {
|
|
6
20
|
return source.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
7
21
|
}
|
|
@@ -103,7 +117,7 @@ function workspacePackages(root = workspaceRoot()) {
|
|
|
103
117
|
});
|
|
104
118
|
}
|
|
105
119
|
}
|
|
106
|
-
return [...discovered.values()].sort(
|
|
120
|
+
return [...discovered.values()].sort(compareWorkspacePackages);
|
|
107
121
|
}
|
|
108
122
|
function internalDependenciesFor(pkg, packageNames) {
|
|
109
123
|
const internalDeps = /* @__PURE__ */ new Set();
|
|
@@ -127,7 +141,7 @@ function sortWorkspacePackages(packages) {
|
|
|
127
141
|
indegree.set(pkg.name, (indegree.get(pkg.name) ?? 0) + 1);
|
|
128
142
|
}
|
|
129
143
|
}
|
|
130
|
-
const ready = [...packages].filter((pkg) => (indegree.get(pkg.name) ?? 0) === 0).sort(
|
|
144
|
+
const ready = [...packages].filter((pkg) => (indegree.get(pkg.name) ?? 0) === 0).sort(compareWorkspacePackages);
|
|
131
145
|
const ordered = [];
|
|
132
146
|
while (ready.length > 0) {
|
|
133
147
|
const next = ready.shift();
|
|
@@ -142,13 +156,13 @@ function sortWorkspacePackages(packages) {
|
|
|
142
156
|
const dependent = packageMap.get(dependentName);
|
|
143
157
|
if (dependent) {
|
|
144
158
|
ready.push(dependent);
|
|
145
|
-
ready.sort(
|
|
159
|
+
ready.sort(compareWorkspacePackages);
|
|
146
160
|
}
|
|
147
161
|
}
|
|
148
162
|
}
|
|
149
163
|
}
|
|
150
164
|
if (ordered.length !== packages.length) {
|
|
151
|
-
return [...packages].sort(
|
|
165
|
+
return [...packages].sort(compareWorkspacePackages);
|
|
152
166
|
}
|
|
153
167
|
return ordered;
|
|
154
168
|
}
|
|
@@ -251,6 +265,7 @@ function cleanupDir(dirPath) {
|
|
|
251
265
|
}
|
|
252
266
|
}
|
|
253
267
|
export {
|
|
268
|
+
TREESEED_WORKSPACE_PACKAGE_DIRS,
|
|
254
269
|
changedWorkspacePackages,
|
|
255
270
|
cleanupDir,
|
|
256
271
|
createTempDir,
|
|
@@ -3,11 +3,13 @@ function operation(spec) {
|
|
|
3
3
|
}
|
|
4
4
|
const TRESEED_OPERATION_SPECS = [
|
|
5
5
|
operation({ id: "workspace.status", name: "status", aliases: [], group: "Workflow", summary: "Show Treeseed project health and the current task state.", description: "Report branch/task state, runtime readiness, preview/deploy state, auth readiness, and recommended next operations.", provider: "default", related: ["tasks", "switch", "config"] }),
|
|
6
|
-
operation({ id: "branch.tasks", name: "tasks", aliases: [], group: "Workflow", summary: "List task branches
|
|
7
|
-
operation({ id: "branch.switch", name: "switch", aliases: [], group: "Workflow", summary: "Create or resume a task branch.", description: "Create
|
|
8
|
-
operation({ id: "branch.save", name: "save", aliases: [], group: "Workflow", summary: "
|
|
9
|
-
operation({ id: "branch.close", name: "close", aliases: [], group: "Workflow", summary: "
|
|
10
|
-
operation({ id: "branch.stage", name: "stage", aliases: [], group: "Workflow", summary: "
|
|
6
|
+
operation({ id: "branch.tasks", name: "tasks", aliases: [], group: "Workflow", summary: "List task branches plus package branch alignment.", description: "List task branches from market and checked-out package repos, including preview metadata and branch/pointer alignment state.", provider: "default", related: ["status", "switch", "close"] }),
|
|
7
|
+
operation({ id: "branch.switch", name: "switch", aliases: [], group: "Workflow", summary: "Create or resume a recursive task branch.", description: "Create or resume the task branch in market and any checked-out package repos, with optional preview provisioning.", provider: "default", related: ["tasks", "dev", "save"] }),
|
|
8
|
+
operation({ id: "branch.save", name: "save", aliases: [], group: "Workflow", summary: "Recursively verify, commit, and push the current task checkpoint.", description: "Save dirty package repos in dependency order, then verify, commit, push, and optionally refresh preview for market.", provider: "default", related: ["switch", "stage", "status"] }),
|
|
9
|
+
operation({ id: "branch.close", name: "close", aliases: [], group: "Workflow", summary: "Recursively archive and delete a task branch.", description: "Auto-save if needed, clean preview resources, create deprecated tags, and remove the task branch across market and checked-out package repos.", provider: "default", related: ["tasks", "switch", "stage"] }),
|
|
10
|
+
operation({ id: "branch.stage", name: "stage", aliases: [], group: "Workflow", summary: "Squash a task branch into staging across market and packages.", description: "Auto-save if needed, squash-merge package task branches into staging first, update market submodule pointers, then squash-merge market into staging and clean up the task branch.", provider: "default", related: ["save", "release", "close"] }),
|
|
11
|
+
operation({ id: "workspace.resume", name: "resume", aliases: [], group: "Workflow", summary: "Resume an interrupted workflow run.", description: "Continue a failed journaled workflow run from its next incomplete step after validating current workspace preconditions.", provider: "default", related: ["recover", "status"] }),
|
|
12
|
+
operation({ id: "workspace.recover", name: "recover", aliases: [], group: "Workflow", summary: "Inspect active workflow locks and interrupted runs.", description: "List active workflow locks, resumable interrupted runs, and the exact commands needed to continue or recover the workspace state.", provider: "default", related: ["resume", "status"] }),
|
|
11
13
|
operation({ id: "deploy.rollback", name: "rollback", aliases: [], group: "Workflow", summary: "Roll back staging or production to a recorded deployment.", description: "Redeploy a previously recorded staging or production commit using a temporary checkout of that revision.", provider: "default", related: ["status", "release"] }),
|
|
12
14
|
operation({ id: "workspace.doctor", name: "doctor", aliases: [], group: "Validation", summary: "Diagnose Treeseed tooling, auth, and workflow readiness.", description: "Collect doctor-style diagnostics for workspace readiness and optional safe repairs.", provider: "default", related: ["status", "config"] }),
|
|
13
15
|
operation({ id: "auth.login", name: "auth:login", aliases: [], group: "Validation", summary: "Authenticate against the configured Treeseed API.", description: "Start the device login flow against the active Treeseed API host and persist the returned session locally.", provider: "default", related: ["auth:check", "auth:whoami", "auth:logout"] }),
|
|
@@ -18,7 +20,7 @@ const TRESEED_OPERATION_SPECS = [
|
|
|
18
20
|
operation({ id: "project.init", name: "init", aliases: [], group: "Workflow", summary: "Scaffold a new Treeseed tenant project.", description: "Create a new Treeseed tenant directory from a remote-catalog template backed by the packaged scaffold artifact.", provider: "default", related: ["config", "switch", "dev"] }),
|
|
19
21
|
operation({ id: "project.config", name: "config", aliases: [], group: "Workflow", summary: "Configure and test the runtime foundation.", description: "Apply safe repairs, collect environment values, write local machine config, generate local env files, initialize environments, sync providers, and run doctor-style checks.", provider: "default", related: ["status", "switch", "dev"] }),
|
|
20
22
|
operation({ id: "project.export", name: "export", aliases: [], group: "Utilities", summary: "Export a Markdown snapshot of the current codebase.", description: "Generate a Markdown codebase snapshot for the selected directory using the SDK-owned repomix integration and store it under .treeseed/exports.", provider: "default", related: ["status", "config"] }),
|
|
21
|
-
operation({ id: "deploy.release", name: "release", aliases: [], group: "Workflow", summary: "
|
|
23
|
+
operation({ id: "deploy.release", name: "release", aliases: [], group: "Workflow", summary: "Release changed packages and market from staging to production.", description: "Select changed packages plus dependents, validate publish workflows, release packages first, then promote market from staging to main with aligned package pointers.", provider: "default", related: ["stage", "status", "rollback"] }),
|
|
22
24
|
operation({ id: "deploy.destroy", name: "destroy", aliases: [], group: "Workflow", summary: "Destroy a persistent environment and its local state.", description: "Delete the selected persistent environment resources and remove the local deploy state after confirmation.", provider: "default", related: ["config", "status"] }),
|
|
23
25
|
operation({ id: "local.dev", name: "dev", aliases: [], group: "Local Development", summary: "Start the unified local Treeseed development environment.", description: "Start the unified local Treeseed development environment.", provider: "default" }),
|
|
24
26
|
operation({ id: "local.devWatch", name: "dev:watch", aliases: [], group: "Local Development", summary: "Start local development with rebuild and watch mode.", description: "Start local development with rebuild and watch mode.", provider: "default" }),
|