@corelayer-ai/cli 0.5.1 → 0.5.3
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/README.md +3 -0
- package/dist/commands/anomalies.js +76 -0
- package/dist/commands/api-keys.js +1 -6
- package/dist/commands/config.js +3 -2
- package/dist/commands/integrations.js +3 -8
- package/dist/commands/issue-tickets.js +163 -0
- package/dist/commands/issues.js +196 -192
- package/dist/commands/login.js +1 -6
- package/dist/commands/logout.js +21 -3
- package/dist/commands/preflight.js +69 -34
- package/dist/commands/schedules.js +51 -0
- package/dist/index.js +13 -3
- package/dist/lib/api-client.js +22 -4
- package/dist/lib/args.js +4 -0
- package/package.json +5 -3
- package/skill/SKILL.md +298 -108
- package/skill/references/ecs-monitor-recovery.md +67 -0
- package/skill/references/preflight.md +2 -0
package/dist/commands/issues.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { stdin as input, stdout as output } from "node:process";
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
3
|
import { CorelayerClient } from "../lib/api-client.js";
|
|
4
|
+
import { readFlag } from "../lib/args.js";
|
|
4
5
|
import { readConfig, resolveApiUrl, resolveToken } from "../lib/config.js";
|
|
5
6
|
import { fail, formatDate, printJson, printTable } from "../lib/output.js";
|
|
6
|
-
|
|
7
|
-
const index = args.indexOf(flag);
|
|
8
|
-
if (index === -1)
|
|
9
|
-
return undefined;
|
|
10
|
-
return args[index + 1];
|
|
11
|
-
}
|
|
7
|
+
import { runIssueTickets } from "./issue-tickets.js";
|
|
12
8
|
function hasFlag(args, flag) {
|
|
13
9
|
return args.includes(flag);
|
|
14
10
|
}
|
|
@@ -58,6 +54,7 @@ USAGE:
|
|
|
58
54
|
SUBCOMMANDS:
|
|
59
55
|
list List issues in a group
|
|
60
56
|
get <id|slug> Show details for one issue
|
|
57
|
+
ticket <action> <id> Jira/Linear destinations, search, ensure, create, link, status
|
|
61
58
|
summary Summarize issue health for a group
|
|
62
59
|
diff Show issues new or changed since a timestamp
|
|
63
60
|
close <id|slug> Close an issue (write operation)
|
|
@@ -80,6 +77,15 @@ OPTIONS:
|
|
|
80
77
|
Diff: ISO 8601 timestamp or preset (1hour, 24hours, 7days, 14days, 30days)
|
|
81
78
|
--duration <duration>
|
|
82
79
|
Diff: human-friendly duration (e.g. 2hours, 4hours, 30m, 1d)
|
|
80
|
+
--provider jira|linear Ticket provider
|
|
81
|
+
--account <id> Connected ticket account
|
|
82
|
+
--project <id> Ticket project
|
|
83
|
+
--team <id> Linear team
|
|
84
|
+
--type <id> Jira issue type
|
|
85
|
+
--ticket <id> Selected existing ticket (link)
|
|
86
|
+
--request-id <uuid> Resume the same mutation or read its status
|
|
87
|
+
--revision <n> Jira account revision from ticket destinations
|
|
88
|
+
--fields <json> Required provider creation fields
|
|
83
89
|
--yes Skip confirmation prompt (delete)
|
|
84
90
|
--json Output as JSON
|
|
85
91
|
-h, --help Print help
|
|
@@ -112,208 +118,206 @@ function parseDuration(value) {
|
|
|
112
118
|
return Math.round(n * 24 * 60 * 60 * 1000);
|
|
113
119
|
return null;
|
|
114
120
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
function resolveGroupId(args, config) {
|
|
122
|
+
const groupId = readFlag(args, "--group") || config.defaults?.group;
|
|
123
|
+
if (!groupId) {
|
|
124
|
+
fail("groupId is required. Use --group <groupId> or set default-group via corelayer config set default-group <groupId>");
|
|
119
125
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
return groupId;
|
|
127
|
+
}
|
|
128
|
+
async function runGetIssue(client, commandArgs, ctx) {
|
|
129
|
+
const issueIdentifier = commandArgs[0];
|
|
130
|
+
if (!issueIdentifier) {
|
|
131
|
+
fail("Usage: corelayer issues get <issueId|slug>");
|
|
125
132
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (sub === "list") {
|
|
130
|
-
const groupId = readFlag(args, "--group") || config.defaults?.group;
|
|
131
|
-
if (!groupId) {
|
|
132
|
-
fail("groupId is required. Use --group <groupId> or set default-group via corelayer config set default-group <groupId>");
|
|
133
|
-
}
|
|
134
|
-
const result = await client.listIssues(groupId, {
|
|
135
|
-
status: readFlag(args, "--status"),
|
|
136
|
-
severity: readFlag(args, "--severity"),
|
|
137
|
-
limit: readFlag(args, "--limit"),
|
|
138
|
-
page: readFlag(args, "--page"),
|
|
139
|
-
});
|
|
140
|
-
if (ctx.json) {
|
|
141
|
-
printJson(result);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const rows = result.issues.map((issue) => [
|
|
145
|
-
issue.id,
|
|
146
|
-
issue.slug || "-",
|
|
147
|
-
issue.severity || "-",
|
|
148
|
-
issue.status || "-",
|
|
149
|
-
issue.title || "-",
|
|
150
|
-
formatDate(issue.last_seen_at || issue.lastSeenAt),
|
|
151
|
-
]);
|
|
152
|
-
printTable(["ID", "SLUG", "SEVERITY", "STATUS", "TITLE", "LAST SEEN"], rows);
|
|
153
|
-
if (!ctx.quiet) {
|
|
154
|
-
process.stdout.write(`Showing ${result.issues.length} of ${result.total} issues\n`);
|
|
155
|
-
}
|
|
133
|
+
const issue = await client.getIssue(issueIdentifier);
|
|
134
|
+
if (ctx.json) {
|
|
135
|
+
printJson(issue);
|
|
156
136
|
return;
|
|
157
137
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
process.stdout.write(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
process.stdout.write(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
138
|
+
process.stdout.write(`Issue ${issue.slug || issue.id}: ${issue.title || ""}\n`);
|
|
139
|
+
process.stdout.write(`Status: ${issue.status || "-"}\n`);
|
|
140
|
+
process.stdout.write(`Severity: ${issue.severity || "-"}\n`);
|
|
141
|
+
process.stdout.write(`Created: ${formatDate(issue.createdAt || issue.created_at)}\n`);
|
|
142
|
+
process.stdout.write(`Last seen: ${formatDate(issue.lastSeenAt || issue.last_seen_at)}\n`);
|
|
143
|
+
process.stdout.write(`Events: ${issue.event_count || 0}\n`);
|
|
144
|
+
if (issue.whatHappened) {
|
|
145
|
+
process.stdout.write(`\nWhat happened:\n${issue.whatHappened}\n`);
|
|
146
|
+
}
|
|
147
|
+
if (issue.rootCause) {
|
|
148
|
+
process.stdout.write(`\nRoot cause:\n${issue.rootCause}\n`);
|
|
149
|
+
}
|
|
150
|
+
if (issue.nextSteps) {
|
|
151
|
+
process.stdout.write(`\nNext steps:\n${issue.nextSteps}\n`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function issueRows(issues) {
|
|
155
|
+
return issues.map((issue) => [
|
|
156
|
+
issue.id,
|
|
157
|
+
issue.slug || "-",
|
|
158
|
+
issue.severity || "-",
|
|
159
|
+
issue.status || "-",
|
|
160
|
+
issue.title || "-",
|
|
161
|
+
formatDate(issue.last_seen_at || issue.lastSeenAt),
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
async function runListIssues({ args, client, config, ctx, }) {
|
|
165
|
+
const groupId = resolveGroupId(args, config);
|
|
166
|
+
const result = await client.listIssues(groupId, {
|
|
167
|
+
status: readFlag(args, "--status"),
|
|
168
|
+
severity: readFlag(args, "--severity"),
|
|
169
|
+
limit: readFlag(args, "--limit"),
|
|
170
|
+
page: readFlag(args, "--page"),
|
|
171
|
+
});
|
|
172
|
+
if (ctx.json) {
|
|
173
|
+
printJson(result);
|
|
183
174
|
return;
|
|
184
175
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
176
|
+
printTable(["ID", "SLUG", "SEVERITY", "STATUS", "TITLE", "LAST SEEN"], issueRows(result.issues));
|
|
177
|
+
if (!ctx.quiet) {
|
|
178
|
+
process.stdout.write(`Showing ${result.issues.length} of ${result.total} issues\n`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function runCloseIssue({ args, client, commandArgs, ctx, }) {
|
|
182
|
+
const issueIdentifier = commandArgs[0];
|
|
183
|
+
if (!issueIdentifier) {
|
|
184
|
+
fail('Usage: corelayer issues close <issueId|slug> [--feedback "..."]');
|
|
185
|
+
}
|
|
186
|
+
const feedback = readFlag(args, "--feedback");
|
|
187
|
+
const issue = await client.getIssue(issueIdentifier);
|
|
188
|
+
if (!issue.id) {
|
|
189
|
+
fail("Could not resolve issue identifier to an issue ID.");
|
|
190
|
+
}
|
|
191
|
+
await client.closeIssue(issue.id, feedback);
|
|
192
|
+
if (!ctx.quiet) {
|
|
193
|
+
process.stdout.write(`Issue ${issue.slug || issue.id} closed.\n`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function runBulkCloseIssues({ args, client, config, ctx, }) {
|
|
197
|
+
const groupId = resolveGroupId(args, config);
|
|
198
|
+
const lastSeenBefore = readFlag(args, "--last-seen-before");
|
|
199
|
+
const lastSeenBeforeOrOn = readFlag(args, "--last-seen-before-or-on");
|
|
200
|
+
if (!lastSeenBefore && !lastSeenBeforeOrOn) {
|
|
201
|
+
fail('Usage: corelayer issues bulk-close --group <groupId> (--last-seen-before <date|preset> | --last-seen-before-or-on <date|preset>) [--feedback "..."]');
|
|
202
|
+
}
|
|
203
|
+
const feedback = readFlag(args, "--feedback");
|
|
204
|
+
const result = await client.bulkIssueAction({
|
|
205
|
+
action: "close",
|
|
206
|
+
groupId,
|
|
207
|
+
feedback,
|
|
208
|
+
filters: {
|
|
209
|
+
status: readFlag(args, "--status") || "Open",
|
|
210
|
+
severity: readFlag(args, "--severity"),
|
|
211
|
+
lastSeenBefore,
|
|
212
|
+
lastSeenBeforeOrOn,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
if (ctx.json) {
|
|
216
|
+
printJson(result);
|
|
199
217
|
return;
|
|
200
218
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
219
|
+
process.stdout.write(`Bulk close complete. Closed ${result.affected} issues`);
|
|
220
|
+
if (result.skipped > 0) {
|
|
221
|
+
process.stdout.write(`, skipped ${result.skipped}`);
|
|
222
|
+
}
|
|
223
|
+
process.stdout.write(".\n");
|
|
224
|
+
}
|
|
225
|
+
async function runReopenIssue({ client, commandArgs, ctx, }) {
|
|
226
|
+
const issueId = commandArgs[0];
|
|
227
|
+
if (!issueId) {
|
|
228
|
+
fail("Usage: corelayer issues reopen <issueId>");
|
|
229
|
+
}
|
|
230
|
+
await client.reopenIssue(issueId);
|
|
231
|
+
if (!ctx.quiet) {
|
|
232
|
+
process.stdout.write(`Issue ${issueId} reopened.\n`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function runDeleteIssue({ args, client, commandArgs, ctx, }) {
|
|
236
|
+
const issueId = commandArgs[0];
|
|
237
|
+
if (!issueId) {
|
|
238
|
+
fail("Usage: corelayer issues delete <issueId> [--yes]");
|
|
239
|
+
}
|
|
240
|
+
const yes = hasFlag(args, "--yes");
|
|
241
|
+
if (!yes) {
|
|
242
|
+
const confirmed = await confirmDelete();
|
|
243
|
+
if (!confirmed) {
|
|
244
|
+
process.stdout.write("Aborted.\n");
|
|
225
245
|
return;
|
|
226
246
|
}
|
|
227
|
-
process.stdout.write(`Bulk close complete. Closed ${result.affected} issues`);
|
|
228
|
-
if (result.skipped > 0) {
|
|
229
|
-
process.stdout.write(`, skipped ${result.skipped}`);
|
|
230
|
-
}
|
|
231
|
-
process.stdout.write(".\n");
|
|
232
|
-
return;
|
|
233
247
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
fail("Usage: corelayer issues reopen <issueId>");
|
|
238
|
-
}
|
|
239
|
-
await client.reopenIssue(issueId);
|
|
240
|
-
if (!ctx.quiet) {
|
|
241
|
-
process.stdout.write(`Issue ${issueId} reopened.\n`);
|
|
242
|
-
}
|
|
243
|
-
return;
|
|
248
|
+
await client.deleteIssue(issueId);
|
|
249
|
+
if (!ctx.quiet) {
|
|
250
|
+
process.stdout.write(`Issue ${issueId} deleted.\n`);
|
|
244
251
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
252
|
+
}
|
|
253
|
+
async function runIssueSummary({ args, client, config, }) {
|
|
254
|
+
const groupId = resolveGroupId(args, config);
|
|
255
|
+
const summary = await client.getIssueSummary(groupId);
|
|
256
|
+
printJson(summary);
|
|
257
|
+
}
|
|
258
|
+
function resolveDiffSince(args) {
|
|
259
|
+
const sinceFlag = readFlag(args, "--since");
|
|
260
|
+
const durationFlag = readFlag(args, "--duration");
|
|
261
|
+
if (!sinceFlag && !durationFlag) {
|
|
262
|
+
fail("One of --since <timestamp|preset> or --duration <duration> is required.\n" +
|
|
263
|
+
"Examples: --since 2026-06-01T00:00:00Z --since 24hours --duration 2hours");
|
|
264
|
+
}
|
|
265
|
+
if (!durationFlag) {
|
|
266
|
+
return sinceFlag;
|
|
267
|
+
}
|
|
268
|
+
const ms = parseDuration(durationFlag);
|
|
269
|
+
if (ms === null) {
|
|
270
|
+
fail(`Invalid --duration value: "${durationFlag}". Examples: 30m, 2hours, 4h, 1d, 7days`);
|
|
271
|
+
}
|
|
272
|
+
return new Date(Date.now() - ms).toISOString();
|
|
273
|
+
}
|
|
274
|
+
async function runDiffIssues({ args, client, config, ctx, }) {
|
|
275
|
+
const groupId = resolveGroupId(args, config);
|
|
276
|
+
const result = await client.diffIssues(groupId, resolveDiffSince(args), {
|
|
277
|
+
limit: readFlag(args, "--limit"),
|
|
278
|
+
page: readFlag(args, "--page"),
|
|
279
|
+
});
|
|
280
|
+
if (ctx.json) {
|
|
281
|
+
printJson(result);
|
|
262
282
|
return;
|
|
263
283
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
284
|
+
process.stdout.write(`DIFF SINCE ${result.since}\n`);
|
|
285
|
+
printTable(["ID", "SLUG", "SEVERITY", "STATUS", "TITLE", "LAST SEEN"], issueRows(result.issues));
|
|
286
|
+
if (!ctx.quiet) {
|
|
287
|
+
process.stdout.write(`${result.total} issue${result.total === 1 ? "" : "s"} changed since ${result.since}\n`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const ISSUE_COMMAND_HANDLERS = {
|
|
291
|
+
"bulk-close": runBulkCloseIssues,
|
|
292
|
+
close: runCloseIssue,
|
|
293
|
+
delete: runDeleteIssue,
|
|
294
|
+
diff: runDiffIssues,
|
|
295
|
+
get: ({ client, commandArgs, ctx }) => runGetIssue(client, commandArgs, ctx),
|
|
296
|
+
list: runListIssues,
|
|
297
|
+
reopen: runReopenIssue,
|
|
298
|
+
summary: runIssueSummary,
|
|
299
|
+
};
|
|
300
|
+
export async function runIssues(args, ctx) {
|
|
301
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
302
|
+
printHelp();
|
|
271
303
|
return;
|
|
272
304
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
let sinceValue;
|
|
285
|
-
if (durationFlag) {
|
|
286
|
-
const ms = parseDuration(durationFlag);
|
|
287
|
-
if (ms === null) {
|
|
288
|
-
fail(`Invalid --duration value: "${durationFlag}". Examples: 30m, 2hours, 4h, 1d, 7days`);
|
|
289
|
-
}
|
|
290
|
-
sinceValue = new Date(Date.now() - ms).toISOString();
|
|
291
|
-
}
|
|
292
|
-
else {
|
|
293
|
-
sinceValue = sinceFlag;
|
|
294
|
-
}
|
|
295
|
-
const result = await client.diffIssues(groupId, sinceValue, {
|
|
296
|
-
limit: readFlag(args, "--limit"),
|
|
297
|
-
page: readFlag(args, "--page"),
|
|
298
|
-
});
|
|
299
|
-
if (ctx.json) {
|
|
300
|
-
printJson(result);
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
process.stdout.write(`DIFF SINCE ${result.since}\n`);
|
|
304
|
-
const rows = result.issues.map((issue) => [
|
|
305
|
-
issue.id,
|
|
306
|
-
issue.slug || "-",
|
|
307
|
-
issue.severity || "-",
|
|
308
|
-
issue.status || "-",
|
|
309
|
-
issue.title || "-",
|
|
310
|
-
formatDate(issue.last_seen_at || issue.lastSeenAt),
|
|
311
|
-
]);
|
|
312
|
-
printTable(["ID", "SLUG", "SEVERITY", "STATUS", "TITLE", "LAST SEEN"], rows);
|
|
313
|
-
if (!ctx.quiet) {
|
|
314
|
-
process.stdout.write(`${result.total} issue${result.total === 1 ? "" : "s"} changed since ${result.since}\n`);
|
|
315
|
-
}
|
|
305
|
+
const sub = args[0];
|
|
306
|
+
const config = readConfig();
|
|
307
|
+
const token = resolveToken();
|
|
308
|
+
if (!token) {
|
|
309
|
+
fail("Not authenticated. Set CORELAYER_API_KEY or run: corelayer login");
|
|
310
|
+
}
|
|
311
|
+
const apiUrl = resolveApiUrl(ctx.apiUrlOverride);
|
|
312
|
+
const client = new CorelayerClient(apiUrl, token);
|
|
313
|
+
if (sub === "ticket") {
|
|
314
|
+
await runIssueTickets(args.slice(1), client, config, ctx);
|
|
316
315
|
return;
|
|
317
316
|
}
|
|
318
|
-
|
|
317
|
+
const commandArgs = removeFlags(args.slice(1));
|
|
318
|
+
const handler = ISSUE_COMMAND_HANDLERS[sub];
|
|
319
|
+
if (!handler) {
|
|
320
|
+
fail("Usage: corelayer issues <list|get|close|bulk-close|reopen|delete|summary|diff> [options]");
|
|
321
|
+
}
|
|
322
|
+
await handler({ args, client, commandArgs, config, ctx });
|
|
319
323
|
}
|
package/dist/commands/login.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
+
import { readFlag } from "../lib/args.js";
|
|
1
2
|
import { loginWithBrowser, loginWithCodeDirect } from "../lib/auth.js";
|
|
2
3
|
import { updateConfig } from "../lib/config.js";
|
|
3
4
|
import { fail } from "../lib/output.js";
|
|
4
|
-
function readFlag(args, flag) {
|
|
5
|
-
const index = args.indexOf(flag);
|
|
6
|
-
if (index === -1)
|
|
7
|
-
return undefined;
|
|
8
|
-
return args[index + 1];
|
|
9
|
-
}
|
|
10
5
|
function hasFlag(args, flag) {
|
|
11
6
|
return args.includes(flag);
|
|
12
7
|
}
|
package/dist/commands/logout.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CorelayerClient } from "../lib/api-client.js";
|
|
2
|
+
import { clearToken, readConfig } from "../lib/config.js";
|
|
2
3
|
function printHelp() {
|
|
3
|
-
process.stdout
|
|
4
|
+
process.stdout
|
|
5
|
+
.write(`corelayer logout - Revoke the saved credential and clear it locally
|
|
4
6
|
|
|
5
7
|
USAGE:
|
|
6
8
|
corelayer logout
|
|
7
9
|
|
|
8
10
|
OPTIONS:
|
|
11
|
+
--local-only Clear the saved credential without server revocation
|
|
9
12
|
-h, --help Print help
|
|
10
13
|
`);
|
|
11
14
|
}
|
|
@@ -14,8 +17,23 @@ export async function runLogout(args, ctx) {
|
|
|
14
17
|
printHelp();
|
|
15
18
|
return;
|
|
16
19
|
}
|
|
20
|
+
const config = readConfig();
|
|
21
|
+
if (config.token && !args.includes("--local-only")) {
|
|
22
|
+
try {
|
|
23
|
+
await new CorelayerClient(config.apiUrl, config.token).logout();
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const message = error instanceof Error ? error.message : "Request failed";
|
|
27
|
+
throw new Error(`Could not revoke the saved credential: ${message}. Local credentials were kept. Retry, or use --local-only to clear them without revocation.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
17
30
|
clearToken();
|
|
31
|
+
if (process.env.CORELAYER_API_KEY) {
|
|
32
|
+
process.stderr.write("CORELAYER_API_KEY is still set. Unset it to stop using that credential.\n");
|
|
33
|
+
}
|
|
18
34
|
if (!ctx.quiet) {
|
|
19
|
-
process.stdout.write("
|
|
35
|
+
process.stdout.write(args.includes("--local-only")
|
|
36
|
+
? "Local credentials cleared; the server credential was not revoked.\n"
|
|
37
|
+
: "Logged out.\n");
|
|
20
38
|
}
|
|
21
39
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { CorelayerClient } from "../lib/api-client.js";
|
|
3
|
-
import {
|
|
3
|
+
import { readFlag } from "../lib/args.js";
|
|
4
|
+
import { resolveApiUrl, resolveToken } from "../lib/config.js";
|
|
4
5
|
import { fail, printJson } from "../lib/output.js";
|
|
5
6
|
const MAX_DIFF_CHARS = 12_000;
|
|
6
7
|
function printHelp() {
|
|
7
|
-
process.stdout
|
|
8
|
+
process.stdout
|
|
9
|
+
.write(`corelayer preflight - Check a change against org memory before you push
|
|
8
10
|
|
|
9
11
|
Surfaces failure modes and things to double-check that your org has hit before for
|
|
10
12
|
changes like this one. Run inside a git repo with no arguments and it derives the PR
|
|
@@ -28,15 +30,9 @@ EXAMPLES:
|
|
|
28
30
|
corelayer preflight --description "Add Redis cache to the payments worker" --repo payments-api
|
|
29
31
|
`);
|
|
30
32
|
}
|
|
31
|
-
function readFlag(args, flag) {
|
|
32
|
-
const index = args.indexOf(flag);
|
|
33
|
-
if (index === -1)
|
|
34
|
-
return undefined;
|
|
35
|
-
return args[index + 1];
|
|
36
|
-
}
|
|
37
33
|
function git(args) {
|
|
38
34
|
try {
|
|
39
|
-
return
|
|
35
|
+
return execFileSync("git", args, {
|
|
40
36
|
encoding: "utf8",
|
|
41
37
|
stdio: ["ignore", "pipe", "ignore"],
|
|
42
38
|
}).trim();
|
|
@@ -46,18 +42,23 @@ function git(args) {
|
|
|
46
42
|
}
|
|
47
43
|
}
|
|
48
44
|
function detectRepo() {
|
|
49
|
-
const url = git("config --get remote.origin.url");
|
|
45
|
+
const url = git(["config", "--get", "remote.origin.url"]);
|
|
50
46
|
if (!url)
|
|
51
47
|
return undefined;
|
|
52
48
|
const match = url.replace(/\.git$/, "").match(/([^/:]+)$/);
|
|
53
49
|
return match ? match[1] : undefined;
|
|
54
50
|
}
|
|
55
51
|
function detectBase() {
|
|
56
|
-
const head = git("rev-parse --abbrev-ref origin/HEAD");
|
|
52
|
+
const head = git(["rev-parse", "--abbrev-ref", "origin/HEAD"]);
|
|
57
53
|
if (head && head.startsWith("origin/"))
|
|
58
|
-
return head
|
|
54
|
+
return head;
|
|
59
55
|
for (const candidate of ["main", "master"]) {
|
|
60
|
-
if (git(
|
|
56
|
+
if (git([
|
|
57
|
+
"rev-parse",
|
|
58
|
+
"--verify",
|
|
59
|
+
"--end-of-options",
|
|
60
|
+
`${candidate}^{commit}`,
|
|
61
|
+
]))
|
|
61
62
|
return candidate;
|
|
62
63
|
}
|
|
63
64
|
return "main";
|
|
@@ -81,15 +82,28 @@ function printChecks(checks, noColor) {
|
|
|
81
82
|
process.stdout.write(` ${severityColor(check.severity, noColor)} · "${check.title}" · ${where}${seen}\n`);
|
|
82
83
|
});
|
|
83
84
|
}
|
|
84
|
-
|
|
85
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
86
|
-
printHelp();
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
85
|
+
function buildPreflightContext(args) {
|
|
89
86
|
const base = readFlag(args, "--base") || detectBase();
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
const baseCommit = git([
|
|
88
|
+
"rev-parse",
|
|
89
|
+
"--verify",
|
|
90
|
+
"--end-of-options",
|
|
91
|
+
`${base}^{commit}`,
|
|
92
|
+
]);
|
|
93
|
+
const commits = baseCommit
|
|
94
|
+
? git(["log", "--pretty=format:%s%n%b", `${baseCommit}..HEAD`, "--"]) || ""
|
|
95
|
+
: "";
|
|
96
|
+
const files = baseCommit
|
|
97
|
+
? git([
|
|
98
|
+
"diff",
|
|
99
|
+
"--no-ext-diff",
|
|
100
|
+
"--no-textconv",
|
|
101
|
+
"--name-only",
|
|
102
|
+
`${baseCommit}...HEAD`,
|
|
103
|
+
"--",
|
|
104
|
+
]) || ""
|
|
105
|
+
: "";
|
|
106
|
+
const branch = git(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
93
107
|
const repo = readFlag(args, "--repo") || detectRepo();
|
|
94
108
|
const title = readFlag(args, "--title") || commits.split("\n")[0]?.trim() || branch;
|
|
95
109
|
let description = readFlag(args, "--description");
|
|
@@ -101,11 +115,41 @@ export async function runPreflight(args, ctx) {
|
|
|
101
115
|
parts.push(`Changed files:\n${files.trim()}`);
|
|
102
116
|
description = parts.join("\n\n");
|
|
103
117
|
}
|
|
104
|
-
if (args.includes("--diff")) {
|
|
105
|
-
const diff = git(
|
|
118
|
+
if (args.includes("--diff") && baseCommit) {
|
|
119
|
+
const diff = git([
|
|
120
|
+
"diff",
|
|
121
|
+
"--no-ext-diff",
|
|
122
|
+
"--no-textconv",
|
|
123
|
+
`${baseCommit}...HEAD`,
|
|
124
|
+
"--",
|
|
125
|
+
]);
|
|
106
126
|
if (diff)
|
|
107
127
|
description += `\n\nDiff:\n${diff.slice(0, MAX_DIFF_CHARS)}`;
|
|
108
128
|
}
|
|
129
|
+
return { base, description, repo, title };
|
|
130
|
+
}
|
|
131
|
+
function printPreflightResult(result, ctx) {
|
|
132
|
+
if (result.degraded) {
|
|
133
|
+
process.stderr.write(`⚠ Preflight could not run (${result.reason ?? "unknown reason"}). This is not an all-clear.\n`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (result.checks.length === 0) {
|
|
137
|
+
if (!ctx.quiet) {
|
|
138
|
+
process.stdout.write("✓ Preflight: nothing in org memory to double-check for this change.\n");
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!ctx.quiet) {
|
|
143
|
+
process.stdout.write(`Preflight — ${result.checks.length} thing(s) to double-check before you push:\n\n`);
|
|
144
|
+
}
|
|
145
|
+
printChecks(result.checks, ctx.noColor);
|
|
146
|
+
}
|
|
147
|
+
export async function runPreflight(args, ctx) {
|
|
148
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
149
|
+
printHelp();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const { base, description, repo, title } = buildPreflightContext(args);
|
|
109
153
|
if (!description.trim()) {
|
|
110
154
|
fail(`No PR context found. Run inside a git repo with commits ahead of "${base}", or pass --description.`);
|
|
111
155
|
}
|
|
@@ -124,14 +168,5 @@ export async function runPreflight(args, ctx) {
|
|
|
124
168
|
printJson(result);
|
|
125
169
|
return;
|
|
126
170
|
}
|
|
127
|
-
|
|
128
|
-
if (!ctx.quiet) {
|
|
129
|
-
process.stdout.write("✓ Preflight: nothing in org memory to double-check for this change.\n");
|
|
130
|
-
}
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (!ctx.quiet) {
|
|
134
|
-
process.stdout.write(`Preflight — ${result.checks.length} thing(s) to double-check before you push:\n\n`);
|
|
135
|
-
}
|
|
136
|
-
printChecks(result.checks, ctx.noColor);
|
|
171
|
+
printPreflightResult(result, ctx);
|
|
137
172
|
}
|