@blogic-cz/agent-tools 0.3.0 → 0.4.1
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/package.json +1 -1
- package/src/db-tool/service.ts +10 -5
- package/src/db-tool/types.ts +1 -0
- package/src/gh-tool/index.ts +25 -2
- package/src/gh-tool/release.ts +561 -0
package/package.json
CHANGED
package/src/db-tool/service.ts
CHANGED
|
@@ -79,10 +79,11 @@ export class DbService extends ServiceMap.Service<
|
|
|
79
79
|
|
|
80
80
|
const envVars: Record<string, string> = {};
|
|
81
81
|
const regex = /^export\s+([A-Z_][A-Z0-9_]*)=["']?([^"'\n]+)["']?/gm;
|
|
82
|
-
let match
|
|
82
|
+
let match = regex.exec(content);
|
|
83
83
|
|
|
84
|
-
while (
|
|
84
|
+
while (match !== null) {
|
|
85
85
|
envVars[match[1]] = match[2];
|
|
86
|
+
match = regex.exec(content);
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
yield* Ref.set(zshrcEnvCache, envVars);
|
|
@@ -214,7 +215,7 @@ export class DbService extends ServiceMap.Service<
|
|
|
214
215
|
) => {
|
|
215
216
|
const args = [
|
|
216
217
|
"-h",
|
|
217
|
-
|
|
218
|
+
config.host,
|
|
218
219
|
"-p",
|
|
219
220
|
String(config.port),
|
|
220
221
|
"-U",
|
|
@@ -506,14 +507,18 @@ export class DbService extends ServiceMap.Service<
|
|
|
506
507
|
}
|
|
507
508
|
|
|
508
509
|
const isLocal = LOCALHOST_HOSTS.has(envConfig.host);
|
|
510
|
+
const isLocalEnvironment = env === "local";
|
|
511
|
+
const needsTunnel = dbConfig.kubectl !== undefined && !isLocalEnvironment && isLocal;
|
|
509
512
|
|
|
510
513
|
return {
|
|
514
|
+
host: envConfig.host,
|
|
511
515
|
user: envConfig.user,
|
|
512
516
|
database: envConfig.database,
|
|
517
|
+
password: envConfig.password,
|
|
513
518
|
passwordEnvVar: envConfig.passwordEnvVar,
|
|
514
519
|
port: envConfig.port,
|
|
515
|
-
needsTunnel
|
|
516
|
-
allowMutations:
|
|
520
|
+
needsTunnel,
|
|
521
|
+
allowMutations: isLocalEnvironment,
|
|
517
522
|
};
|
|
518
523
|
};
|
|
519
524
|
|
package/src/db-tool/types.ts
CHANGED
package/src/gh-tool/index.ts
CHANGED
|
@@ -34,6 +34,14 @@ import {
|
|
|
34
34
|
prReplyAndResolveCommand,
|
|
35
35
|
prReviewTriageCommand,
|
|
36
36
|
} from "./pr/index";
|
|
37
|
+
import {
|
|
38
|
+
releaseCreateCommand,
|
|
39
|
+
releaseDeleteCommand,
|
|
40
|
+
releaseEditCommand,
|
|
41
|
+
releaseListCommand,
|
|
42
|
+
releaseStatusCommand,
|
|
43
|
+
releaseViewCommand,
|
|
44
|
+
} from "./release";
|
|
37
45
|
import { repoInfoCommand, repoListCommand, repoSearchCodeCommand } from "./repo";
|
|
38
46
|
import { GitHubService } from "./service";
|
|
39
47
|
import {
|
|
@@ -108,6 +116,18 @@ const workflowCommand = Command.make("workflow", {}).pipe(
|
|
|
108
116
|
]),
|
|
109
117
|
);
|
|
110
118
|
|
|
119
|
+
const releaseCommand = Command.make("release", {}).pipe(
|
|
120
|
+
Command.withDescription("Release operations (create, list, view, edit, delete, status)"),
|
|
121
|
+
Command.withSubcommands([
|
|
122
|
+
releaseCreateCommand,
|
|
123
|
+
releaseListCommand,
|
|
124
|
+
releaseViewCommand,
|
|
125
|
+
releaseEditCommand,
|
|
126
|
+
releaseDeleteCommand,
|
|
127
|
+
releaseStatusCommand,
|
|
128
|
+
]),
|
|
129
|
+
);
|
|
130
|
+
|
|
111
131
|
const mainCommand = Command.make("gh-tool", {}).pipe(
|
|
112
132
|
Command.withDescription(
|
|
113
133
|
`GitHub CLI Tool for Coding Agents
|
|
@@ -130,9 +150,12 @@ WORKFLOW FOR AI AGENTS:
|
|
|
130
150
|
12. Use 'workflow view --run N' to inspect a specific run with jobs/steps
|
|
131
151
|
13. Use 'workflow logs --run N' to get logs (failed jobs by default)
|
|
132
152
|
14. Use 'workflow job-logs --run N --job "build-web-app"' to get clean parsed logs for a specific job
|
|
133
|
-
15. Use 'workflow watch --run N' to watch until completion
|
|
153
|
+
15. Use 'workflow watch --run N' to watch until completion
|
|
154
|
+
16. Use 'release status' to inspect latest release + repository context
|
|
155
|
+
17. Use 'release create --tag vX.Y.Z --generate-notes' to publish a release
|
|
156
|
+
18. Use 'release edit/view/list/delete' to maintain existing releases`,
|
|
134
157
|
),
|
|
135
|
-
Command.withSubcommands([prCommand, issueCommand, repoCommand, workflowCommand]),
|
|
158
|
+
Command.withSubcommands([prCommand, issueCommand, repoCommand, workflowCommand, releaseCommand]),
|
|
136
159
|
);
|
|
137
160
|
|
|
138
161
|
const cli = Command.run(mainCommand, {
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import { Command, Flag } from "effect/unstable/cli";
|
|
2
|
+
import { Effect, Option } from "effect";
|
|
3
|
+
|
|
4
|
+
import { formatOption, logFormatted } from "#shared";
|
|
5
|
+
import { GitHubService } from "./service";
|
|
6
|
+
|
|
7
|
+
type ReleaseListItem = {
|
|
8
|
+
tagName: string;
|
|
9
|
+
name: string;
|
|
10
|
+
isDraft: boolean;
|
|
11
|
+
isPrerelease: boolean;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
publishedAt: string | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type ReleaseAuthor = {
|
|
17
|
+
login: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ReleaseAsset = {
|
|
21
|
+
name: string;
|
|
22
|
+
size: number;
|
|
23
|
+
downloadCount: number;
|
|
24
|
+
contentType: string;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
url: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type ReleaseDetail = {
|
|
31
|
+
tagName: string;
|
|
32
|
+
name: string;
|
|
33
|
+
isDraft: boolean;
|
|
34
|
+
isPrerelease: boolean;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
publishedAt: string | null;
|
|
37
|
+
url: string;
|
|
38
|
+
body: string;
|
|
39
|
+
targetCommitish: string;
|
|
40
|
+
author: ReleaseAuthor | null;
|
|
41
|
+
assets: ReleaseAsset[];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type ReleaseCreateResult = {
|
|
45
|
+
created: true;
|
|
46
|
+
tagName: string;
|
|
47
|
+
name: string;
|
|
48
|
+
url: string;
|
|
49
|
+
isDraft: boolean;
|
|
50
|
+
isPrerelease: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type ReleaseEditResult = {
|
|
54
|
+
edited: true;
|
|
55
|
+
tagName: string;
|
|
56
|
+
name: string;
|
|
57
|
+
url: string;
|
|
58
|
+
isDraft: boolean;
|
|
59
|
+
isPrerelease: boolean;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type ReleaseDeleteResult = {
|
|
63
|
+
deleted: boolean;
|
|
64
|
+
tagName: string;
|
|
65
|
+
tagCleaned: boolean;
|
|
66
|
+
dryRun?: true;
|
|
67
|
+
message?: string;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type LatestRelease = {
|
|
71
|
+
tagName: string;
|
|
72
|
+
name: string;
|
|
73
|
+
createdAt: string;
|
|
74
|
+
url: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type ReleaseStatusResult = {
|
|
78
|
+
latestRelease: LatestRelease | null;
|
|
79
|
+
repo: {
|
|
80
|
+
owner: string;
|
|
81
|
+
name: string;
|
|
82
|
+
defaultBranch: string;
|
|
83
|
+
url: string;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const listReleases = Effect.fn("release.listReleases")(function* (opts: {
|
|
88
|
+
limit: number;
|
|
89
|
+
repo: string | null;
|
|
90
|
+
}) {
|
|
91
|
+
const gh = yield* GitHubService;
|
|
92
|
+
|
|
93
|
+
const args = [
|
|
94
|
+
"release",
|
|
95
|
+
"list",
|
|
96
|
+
"--json",
|
|
97
|
+
"tagName,name,isDraft,isPrerelease,createdAt,publishedAt",
|
|
98
|
+
"--limit",
|
|
99
|
+
String(opts.limit),
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
if (opts.repo !== null) {
|
|
103
|
+
args.push("--repo", opts.repo);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return yield* gh.runGhJson<ReleaseListItem[]>(args);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const viewRelease = Effect.fn("release.viewRelease")(function* (opts: {
|
|
110
|
+
tag: string;
|
|
111
|
+
repo: string | null;
|
|
112
|
+
}) {
|
|
113
|
+
const gh = yield* GitHubService;
|
|
114
|
+
|
|
115
|
+
const args = [
|
|
116
|
+
"release",
|
|
117
|
+
"view",
|
|
118
|
+
opts.tag,
|
|
119
|
+
"--json",
|
|
120
|
+
"tagName,name,isDraft,isPrerelease,createdAt,publishedAt,url,body,targetCommitish,author,assets",
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
if (opts.repo !== null) {
|
|
124
|
+
args.push("--repo", opts.repo);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return yield* gh.runGhJson<ReleaseDetail>(args);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const createRelease = Effect.fn("release.createRelease")(function* (opts: {
|
|
131
|
+
tag: string;
|
|
132
|
+
title: string | null;
|
|
133
|
+
body: string | null;
|
|
134
|
+
notesFile: string | null;
|
|
135
|
+
draft: boolean;
|
|
136
|
+
prerelease: boolean;
|
|
137
|
+
generateNotes: boolean;
|
|
138
|
+
notesStartTag: string | null;
|
|
139
|
+
target: string | null;
|
|
140
|
+
verifyTag: boolean;
|
|
141
|
+
latest: boolean | null;
|
|
142
|
+
repo: string | null;
|
|
143
|
+
}) {
|
|
144
|
+
const gh = yield* GitHubService;
|
|
145
|
+
|
|
146
|
+
const args = ["release", "create", opts.tag];
|
|
147
|
+
|
|
148
|
+
if (opts.title !== null) {
|
|
149
|
+
args.push("--title", opts.title);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (opts.body !== null) {
|
|
153
|
+
args.push("--notes", opts.body);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (opts.notesFile !== null) {
|
|
157
|
+
args.push("--notes-file", opts.notesFile);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (opts.draft) {
|
|
161
|
+
args.push("--draft");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (opts.prerelease) {
|
|
165
|
+
args.push("--prerelease");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (opts.generateNotes) {
|
|
169
|
+
args.push("--generate-notes");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (opts.notesStartTag !== null) {
|
|
173
|
+
args.push("--notes-start-tag", opts.notesStartTag);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (opts.target !== null) {
|
|
177
|
+
args.push("--target", opts.target);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (opts.verifyTag) {
|
|
181
|
+
args.push("--verify-tag");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (opts.latest !== null) {
|
|
185
|
+
args.push(`--latest=${opts.latest ? "true" : "false"}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (opts.repo !== null) {
|
|
189
|
+
args.push("--repo", opts.repo);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const result = yield* gh.runGh(args);
|
|
193
|
+
const lines = result.stdout
|
|
194
|
+
.trim()
|
|
195
|
+
.split("\n")
|
|
196
|
+
.map((line) => line.trim())
|
|
197
|
+
.filter((line) => line.length > 0);
|
|
198
|
+
|
|
199
|
+
const url = lines.length > 0 ? lines[lines.length - 1] : "";
|
|
200
|
+
|
|
201
|
+
const created: ReleaseCreateResult = {
|
|
202
|
+
created: true,
|
|
203
|
+
tagName: opts.tag,
|
|
204
|
+
name: opts.title ?? opts.tag,
|
|
205
|
+
url,
|
|
206
|
+
isDraft: opts.draft,
|
|
207
|
+
isPrerelease: opts.prerelease,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return created;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const editRelease = Effect.fn("release.editRelease")(function* (opts: {
|
|
214
|
+
tag: string;
|
|
215
|
+
title: string | null;
|
|
216
|
+
body: string | null;
|
|
217
|
+
draft: boolean | null;
|
|
218
|
+
prerelease: boolean | null;
|
|
219
|
+
latest: boolean | null;
|
|
220
|
+
repo: string | null;
|
|
221
|
+
}) {
|
|
222
|
+
const gh = yield* GitHubService;
|
|
223
|
+
|
|
224
|
+
const args = ["release", "edit", opts.tag];
|
|
225
|
+
|
|
226
|
+
if (opts.title !== null) {
|
|
227
|
+
args.push("--title", opts.title);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (opts.body !== null) {
|
|
231
|
+
args.push("--notes", opts.body);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (opts.draft !== null) {
|
|
235
|
+
args.push(`--draft=${opts.draft ? "true" : "false"}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (opts.prerelease !== null) {
|
|
239
|
+
args.push(`--prerelease=${opts.prerelease ? "true" : "false"}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (opts.latest !== null) {
|
|
243
|
+
args.push(`--latest=${opts.latest ? "true" : "false"}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (opts.repo !== null) {
|
|
247
|
+
args.push("--repo", opts.repo);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
yield* gh.runGh(args);
|
|
251
|
+
|
|
252
|
+
const updated = yield* viewRelease({
|
|
253
|
+
tag: opts.tag,
|
|
254
|
+
repo: opts.repo,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const edited: ReleaseEditResult = {
|
|
258
|
+
edited: true,
|
|
259
|
+
tagName: updated.tagName,
|
|
260
|
+
name: updated.name,
|
|
261
|
+
url: updated.url,
|
|
262
|
+
isDraft: updated.isDraft,
|
|
263
|
+
isPrerelease: updated.isPrerelease,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
return edited;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const deleteRelease = Effect.fn("release.deleteRelease")(function* (opts: {
|
|
270
|
+
tag: string;
|
|
271
|
+
cleanupTag: boolean;
|
|
272
|
+
confirm: boolean;
|
|
273
|
+
repo: string | null;
|
|
274
|
+
}) {
|
|
275
|
+
const gh = yield* GitHubService;
|
|
276
|
+
|
|
277
|
+
if (!opts.confirm) {
|
|
278
|
+
const scope = opts.repo !== null ? ` in ${opts.repo}` : "";
|
|
279
|
+
const cleanup = opts.cleanupTag ? " and its git tag" : "";
|
|
280
|
+
|
|
281
|
+
const dryRun: ReleaseDeleteResult = {
|
|
282
|
+
deleted: false,
|
|
283
|
+
tagName: opts.tag,
|
|
284
|
+
tagCleaned: opts.cleanupTag,
|
|
285
|
+
dryRun: true,
|
|
286
|
+
message: `Dry run: would delete release ${opts.tag}${cleanup}${scope}. Re-run with --confirm to execute.`,
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return dryRun;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const args = ["release", "delete", opts.tag, "--yes"];
|
|
293
|
+
|
|
294
|
+
if (opts.cleanupTag) {
|
|
295
|
+
args.push("--cleanup-tag");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (opts.repo !== null) {
|
|
299
|
+
args.push("--repo", opts.repo);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
yield* gh.runGh(args);
|
|
303
|
+
|
|
304
|
+
const deleted: ReleaseDeleteResult = {
|
|
305
|
+
deleted: true,
|
|
306
|
+
tagName: opts.tag,
|
|
307
|
+
tagCleaned: opts.cleanupTag,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
return deleted;
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const releaseStatus = Effect.fn("release.releaseStatus")(function* (repo: string | null) {
|
|
314
|
+
const gh = yield* GitHubService;
|
|
315
|
+
|
|
316
|
+
const args = ["release", "list", "--json", "tagName,name,createdAt,url", "--limit", "1"];
|
|
317
|
+
if (repo !== null) {
|
|
318
|
+
args.push("--repo", repo);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const [repoInfo, releases] = yield* Effect.all([
|
|
322
|
+
gh.getRepoInfo(),
|
|
323
|
+
gh.runGhJson<LatestRelease[]>(args),
|
|
324
|
+
]);
|
|
325
|
+
|
|
326
|
+
const result: ReleaseStatusResult = {
|
|
327
|
+
latestRelease: releases.length > 0 ? releases[0] : null,
|
|
328
|
+
repo: {
|
|
329
|
+
owner: repoInfo.owner,
|
|
330
|
+
name: repoInfo.name,
|
|
331
|
+
defaultBranch: repoInfo.defaultBranch,
|
|
332
|
+
url: repoInfo.url,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return result;
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
export const releaseCreateCommand = Command.make(
|
|
340
|
+
"create",
|
|
341
|
+
{
|
|
342
|
+
body: Flag.string("body").pipe(
|
|
343
|
+
Flag.withDescription("Release notes body (markdown)"),
|
|
344
|
+
Flag.optional,
|
|
345
|
+
),
|
|
346
|
+
draft: Flag.boolean("draft").pipe(
|
|
347
|
+
Flag.withDescription("Create as draft release"),
|
|
348
|
+
Flag.withDefault(false),
|
|
349
|
+
),
|
|
350
|
+
format: formatOption,
|
|
351
|
+
generateNotes: Flag.boolean("generate-notes").pipe(
|
|
352
|
+
Flag.withDescription("Automatically generate release notes"),
|
|
353
|
+
Flag.withDefault(false),
|
|
354
|
+
),
|
|
355
|
+
latest: Flag.boolean("latest").pipe(
|
|
356
|
+
Flag.withDescription("Mark this release as latest (true/false). Omit to leave unchanged"),
|
|
357
|
+
Flag.optional,
|
|
358
|
+
),
|
|
359
|
+
notesFile: Flag.string("notes-file").pipe(
|
|
360
|
+
Flag.withDescription("Path to release notes file (passed to gh --notes-file)"),
|
|
361
|
+
Flag.optional,
|
|
362
|
+
),
|
|
363
|
+
notesStartTag: Flag.string("notes-start-tag").pipe(
|
|
364
|
+
Flag.withDescription("Tag to start generating notes from"),
|
|
365
|
+
Flag.optional,
|
|
366
|
+
),
|
|
367
|
+
prerelease: Flag.boolean("prerelease").pipe(
|
|
368
|
+
Flag.withDescription("Mark as pre-release"),
|
|
369
|
+
Flag.withDefault(false),
|
|
370
|
+
),
|
|
371
|
+
repo: Flag.string("repo").pipe(
|
|
372
|
+
Flag.withDescription("Target repository (owner/name). Defaults to current repo"),
|
|
373
|
+
Flag.optional,
|
|
374
|
+
),
|
|
375
|
+
tag: Flag.string("tag").pipe(Flag.withDescription("Tag name for release (e.g., v1.2.3)")),
|
|
376
|
+
target: Flag.string("target").pipe(
|
|
377
|
+
Flag.withDescription("Target branch or commit SHA for tag"),
|
|
378
|
+
Flag.optional,
|
|
379
|
+
),
|
|
380
|
+
title: Flag.string("title").pipe(
|
|
381
|
+
Flag.withDescription("Release title (defaults to tag)"),
|
|
382
|
+
Flag.optional,
|
|
383
|
+
),
|
|
384
|
+
verifyTag: Flag.boolean("verify-tag").pipe(
|
|
385
|
+
Flag.withDescription("Abort if tag does not already exist in remote"),
|
|
386
|
+
Flag.withDefault(false),
|
|
387
|
+
),
|
|
388
|
+
},
|
|
389
|
+
({
|
|
390
|
+
body,
|
|
391
|
+
draft,
|
|
392
|
+
format,
|
|
393
|
+
generateNotes,
|
|
394
|
+
latest,
|
|
395
|
+
notesFile,
|
|
396
|
+
notesStartTag,
|
|
397
|
+
prerelease,
|
|
398
|
+
repo,
|
|
399
|
+
tag,
|
|
400
|
+
target,
|
|
401
|
+
title,
|
|
402
|
+
verifyTag,
|
|
403
|
+
}) =>
|
|
404
|
+
Effect.gen(function* () {
|
|
405
|
+
const result = yield* createRelease({
|
|
406
|
+
tag,
|
|
407
|
+
title: Option.getOrNull(title),
|
|
408
|
+
body: Option.getOrNull(body),
|
|
409
|
+
notesFile: Option.getOrNull(notesFile),
|
|
410
|
+
draft,
|
|
411
|
+
prerelease,
|
|
412
|
+
generateNotes,
|
|
413
|
+
notesStartTag: Option.getOrNull(notesStartTag),
|
|
414
|
+
target: Option.getOrNull(target),
|
|
415
|
+
verifyTag,
|
|
416
|
+
latest: Option.getOrNull(latest),
|
|
417
|
+
repo: Option.getOrNull(repo),
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
yield* logFormatted(result, format);
|
|
421
|
+
}),
|
|
422
|
+
).pipe(
|
|
423
|
+
Command.withDescription(
|
|
424
|
+
"Create a release (supports --notes-file, --generate-notes, --target, --verify-tag, --latest)",
|
|
425
|
+
),
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
export const releaseListCommand = Command.make(
|
|
429
|
+
"list",
|
|
430
|
+
{
|
|
431
|
+
format: formatOption,
|
|
432
|
+
limit: Flag.integer("limit").pipe(
|
|
433
|
+
Flag.withDescription("Maximum number of releases to return"),
|
|
434
|
+
Flag.withDefault(10),
|
|
435
|
+
),
|
|
436
|
+
repo: Flag.string("repo").pipe(
|
|
437
|
+
Flag.withDescription("Target repository (owner/name). Defaults to current repo"),
|
|
438
|
+
Flag.optional,
|
|
439
|
+
),
|
|
440
|
+
},
|
|
441
|
+
({ format, limit, repo }) =>
|
|
442
|
+
Effect.gen(function* () {
|
|
443
|
+
const releases = yield* listReleases({
|
|
444
|
+
limit,
|
|
445
|
+
repo: Option.getOrNull(repo),
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
yield* logFormatted(releases, format);
|
|
449
|
+
}),
|
|
450
|
+
).pipe(Command.withDescription("List releases"));
|
|
451
|
+
|
|
452
|
+
export const releaseViewCommand = Command.make(
|
|
453
|
+
"view",
|
|
454
|
+
{
|
|
455
|
+
format: formatOption,
|
|
456
|
+
repo: Flag.string("repo").pipe(
|
|
457
|
+
Flag.withDescription("Target repository (owner/name). Defaults to current repo"),
|
|
458
|
+
Flag.optional,
|
|
459
|
+
),
|
|
460
|
+
tag: Flag.string("tag").pipe(Flag.withDescription("Release tag to view (e.g., v1.2.3)")),
|
|
461
|
+
},
|
|
462
|
+
({ format, repo, tag }) =>
|
|
463
|
+
Effect.gen(function* () {
|
|
464
|
+
const release = yield* viewRelease({
|
|
465
|
+
tag,
|
|
466
|
+
repo: Option.getOrNull(repo),
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
yield* logFormatted(release, format);
|
|
470
|
+
}),
|
|
471
|
+
).pipe(Command.withDescription("View release details by tag"));
|
|
472
|
+
|
|
473
|
+
export const releaseEditCommand = Command.make(
|
|
474
|
+
"edit",
|
|
475
|
+
{
|
|
476
|
+
body: Flag.string("body").pipe(
|
|
477
|
+
Flag.withDescription("New release notes body (markdown)"),
|
|
478
|
+
Flag.optional,
|
|
479
|
+
),
|
|
480
|
+
draft: Flag.boolean("draft").pipe(
|
|
481
|
+
Flag.withDescription("Set draft status (true/false). Omit to keep current value"),
|
|
482
|
+
Flag.optional,
|
|
483
|
+
),
|
|
484
|
+
format: formatOption,
|
|
485
|
+
latest: Flag.boolean("latest").pipe(
|
|
486
|
+
Flag.withDescription("Set latest status (true/false). Omit to keep current value"),
|
|
487
|
+
Flag.optional,
|
|
488
|
+
),
|
|
489
|
+
prerelease: Flag.boolean("prerelease").pipe(
|
|
490
|
+
Flag.withDescription("Set prerelease status (true/false). Omit to keep current value"),
|
|
491
|
+
Flag.optional,
|
|
492
|
+
),
|
|
493
|
+
repo: Flag.string("repo").pipe(
|
|
494
|
+
Flag.withDescription("Target repository (owner/name). Defaults to current repo"),
|
|
495
|
+
Flag.optional,
|
|
496
|
+
),
|
|
497
|
+
tag: Flag.string("tag").pipe(Flag.withDescription("Release tag to edit (e.g., v1.2.3)")),
|
|
498
|
+
title: Flag.string("title").pipe(Flag.withDescription("New release title"), Flag.optional),
|
|
499
|
+
},
|
|
500
|
+
({ body, draft, format, latest, prerelease, repo, tag, title }) =>
|
|
501
|
+
Effect.gen(function* () {
|
|
502
|
+
const edited = yield* editRelease({
|
|
503
|
+
tag,
|
|
504
|
+
title: Option.getOrNull(title),
|
|
505
|
+
body: Option.getOrNull(body),
|
|
506
|
+
draft: Option.getOrNull(draft),
|
|
507
|
+
prerelease: Option.getOrNull(prerelease),
|
|
508
|
+
latest: Option.getOrNull(latest),
|
|
509
|
+
repo: Option.getOrNull(repo),
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
yield* logFormatted(edited, format);
|
|
513
|
+
}),
|
|
514
|
+
).pipe(Command.withDescription("Edit an existing release"));
|
|
515
|
+
|
|
516
|
+
export const releaseDeleteCommand = Command.make(
|
|
517
|
+
"delete",
|
|
518
|
+
{
|
|
519
|
+
cleanupTag: Flag.boolean("cleanup-tag").pipe(
|
|
520
|
+
Flag.withDescription("Also delete the git tag from remote"),
|
|
521
|
+
Flag.withDefault(false),
|
|
522
|
+
),
|
|
523
|
+
confirm: Flag.boolean("confirm").pipe(
|
|
524
|
+
Flag.withDescription("Actually delete release (without this flag, only shows dry-run)"),
|
|
525
|
+
Flag.withDefault(false),
|
|
526
|
+
),
|
|
527
|
+
format: formatOption,
|
|
528
|
+
repo: Flag.string("repo").pipe(
|
|
529
|
+
Flag.withDescription("Target repository (owner/name). Defaults to current repo"),
|
|
530
|
+
Flag.optional,
|
|
531
|
+
),
|
|
532
|
+
tag: Flag.string("tag").pipe(Flag.withDescription("Release tag to delete (e.g., v1.2.3)")),
|
|
533
|
+
},
|
|
534
|
+
({ cleanupTag, confirm, format, repo, tag }) =>
|
|
535
|
+
Effect.gen(function* () {
|
|
536
|
+
const result = yield* deleteRelease({
|
|
537
|
+
tag,
|
|
538
|
+
cleanupTag,
|
|
539
|
+
confirm,
|
|
540
|
+
repo: Option.getOrNull(repo),
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
yield* logFormatted(result, format);
|
|
544
|
+
}),
|
|
545
|
+
).pipe(Command.withDescription("Delete a release (dry-run by default, use --confirm to execute)"));
|
|
546
|
+
|
|
547
|
+
export const releaseStatusCommand = Command.make(
|
|
548
|
+
"status",
|
|
549
|
+
{
|
|
550
|
+
format: formatOption,
|
|
551
|
+
repo: Flag.string("repo").pipe(
|
|
552
|
+
Flag.withDescription("Target repository (owner/name). Defaults to current repo"),
|
|
553
|
+
Flag.optional,
|
|
554
|
+
),
|
|
555
|
+
},
|
|
556
|
+
({ format, repo }) =>
|
|
557
|
+
Effect.gen(function* () {
|
|
558
|
+
const status = yield* releaseStatus(Option.getOrNull(repo));
|
|
559
|
+
yield* logFormatted(status, format);
|
|
560
|
+
}),
|
|
561
|
+
).pipe(Command.withDescription("Show release readiness status (latest release + repository info)"));
|