@remogram/cli 0.1.0-beta.6 → 0.1.0-beta.8
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/cli-dispatch.js +193 -6
- package/cli-doctor.js +4 -0
- package/cli-io.js +22 -7
- package/index.js +1 -0
- package/package.json +7 -7
package/cli-dispatch.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
PACKET_TYPES,
|
|
4
4
|
ERROR_CODES,
|
|
5
5
|
forgeError,
|
|
6
|
+
sanitizeField,
|
|
6
7
|
assertGitRef,
|
|
7
8
|
assertGitRemote,
|
|
8
9
|
throwIfStaleHeadByNumber,
|
|
@@ -11,6 +12,15 @@ import {
|
|
|
11
12
|
assertWriteCommandConfigured,
|
|
12
13
|
parseSinceObservedAt,
|
|
13
14
|
normalizeAllowedPaths,
|
|
15
|
+
assertExpectedSha,
|
|
16
|
+
buildMergeExecuteBeforeFacts,
|
|
17
|
+
collectMergeExecuteBlockers,
|
|
18
|
+
buildCrMergeBlockedBody,
|
|
19
|
+
buildCrMergedBody,
|
|
20
|
+
buildMergeExecuteAfterFacts,
|
|
21
|
+
buildMergeExecuteMergeFacts,
|
|
22
|
+
mergeExecuteViewFacts,
|
|
23
|
+
isOpenPrState,
|
|
14
24
|
} from '@remogram/core';
|
|
15
25
|
import { parseAllowedPathFlags, parsePositiveInt } from './cli-argv.js';
|
|
16
26
|
|
|
@@ -211,8 +221,8 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
211
221
|
ctx,
|
|
212
222
|
PACKET_TYPES.PR_STATUS,
|
|
213
223
|
body,
|
|
214
|
-
body.
|
|
215
|
-
body.
|
|
224
|
+
body.forge_source_branch_ref,
|
|
225
|
+
body.forge_source_sha,
|
|
216
226
|
);
|
|
217
227
|
return forgePacket(PACKET_TYPES.PR_STATUS, ctx, body);
|
|
218
228
|
}
|
|
@@ -229,9 +239,9 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
229
239
|
throwIfStaleHeadByNumber(
|
|
230
240
|
ctx,
|
|
231
241
|
PACKET_TYPES.PR_CHECKS,
|
|
232
|
-
{
|
|
233
|
-
view.
|
|
234
|
-
view.
|
|
242
|
+
{ forge_source_sha: view.forge_source_sha },
|
|
243
|
+
view.forge_source_branch_ref,
|
|
244
|
+
view.forge_source_sha,
|
|
235
245
|
);
|
|
236
246
|
}
|
|
237
247
|
return forgePacket(
|
|
@@ -257,6 +267,183 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
257
267
|
}),
|
|
258
268
|
);
|
|
259
269
|
}
|
|
270
|
+
if (group === 'merge' && sub === 'execute') {
|
|
271
|
+
if (typeof provider.mergeExecute !== 'function') {
|
|
272
|
+
throw Object.assign(new Error('merge execute not implemented for provider'), {
|
|
273
|
+
forgeError: forgeError(
|
|
274
|
+
ERROR_CODES.PROVIDER_UNSUPPORTED,
|
|
275
|
+
'merge execute not implemented for provider',
|
|
276
|
+
),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const number = parsePositiveInt(flags.number, '--number');
|
|
280
|
+
if (number == null) {
|
|
281
|
+
throw Object.assign(new Error('--number required'), {
|
|
282
|
+
forgeError: forgeError(ERROR_CODES.INVALID_ARGS, '--number required for merge execute'),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (!flags.expected_base_sha || !flags.expected_head_sha) {
|
|
286
|
+
throw Object.assign(new Error('expected SHAs required'), {
|
|
287
|
+
forgeError: forgeError(
|
|
288
|
+
ERROR_CODES.INVALID_ARGS,
|
|
289
|
+
'--expected-base-sha and --expected-head-sha required for merge execute',
|
|
290
|
+
),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const method = flags.method ? String(flags.method).toLowerCase() : 'merge';
|
|
294
|
+
if (method !== 'merge') {
|
|
295
|
+
throw Object.assign(new Error('Unsupported merge method'), {
|
|
296
|
+
forgeError: forgeError(
|
|
297
|
+
ERROR_CODES.INVALID_ARGS,
|
|
298
|
+
'Only --method merge is supported in v1',
|
|
299
|
+
),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const expectedBaseSha = assertExpectedSha(flags.expected_base_sha, '--expected-base-sha');
|
|
303
|
+
const expectedHeadSha = assertExpectedSha(flags.expected_head_sha, '--expected-head-sha');
|
|
304
|
+
assertWriteCommandConfigured(ctx.config, 'merge');
|
|
305
|
+
|
|
306
|
+
const view = await provider.prView(ctx, { number });
|
|
307
|
+
const checks = await provider.prChecks(ctx, { number });
|
|
308
|
+
const mergePlanBody = await provider.mergePlan(ctx, { number });
|
|
309
|
+
const expected = { baseSha: expectedBaseSha, headSha: expectedHeadSha };
|
|
310
|
+
const viewFacts = mergeExecuteViewFacts(view);
|
|
311
|
+
|
|
312
|
+
let forgeHeadRefSha = null;
|
|
313
|
+
const headRef = viewFacts.sourceBranchRef ? String(viewFacts.sourceBranchRef).trim() : '';
|
|
314
|
+
if (!headRef && isOpenPrState(view.state)) {
|
|
315
|
+
const before = buildMergeExecuteBeforeFacts(view, checks, mergePlanBody, null);
|
|
316
|
+
return forgePacket(
|
|
317
|
+
PACKET_TYPES.CR_MERGE_BLOCKED,
|
|
318
|
+
ctx,
|
|
319
|
+
buildCrMergeBlockedBody({
|
|
320
|
+
prNumber: number,
|
|
321
|
+
expected,
|
|
322
|
+
before,
|
|
323
|
+
blockers: ['head_ref_missing'],
|
|
324
|
+
}),
|
|
325
|
+
forgeError(ERROR_CODES.MERGE_BLOCKED, 'Open change request missing head branch ref'),
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
if (headRef) {
|
|
329
|
+
if (typeof provider.branchHeadSha !== 'function') {
|
|
330
|
+
const before = buildMergeExecuteBeforeFacts(view, checks, mergePlanBody, null);
|
|
331
|
+
return forgePacket(
|
|
332
|
+
PACKET_TYPES.CR_MERGE_BLOCKED,
|
|
333
|
+
ctx,
|
|
334
|
+
buildCrMergeBlockedBody({
|
|
335
|
+
prNumber: number,
|
|
336
|
+
expected,
|
|
337
|
+
before,
|
|
338
|
+
blockers: ['head_ref_unverified'],
|
|
339
|
+
}),
|
|
340
|
+
forgeError(
|
|
341
|
+
ERROR_CODES.MERGE_BLOCKED,
|
|
342
|
+
'Forge head branch verification not implemented for provider',
|
|
343
|
+
),
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
assertGitRef(headRef, 'head_ref');
|
|
348
|
+
} catch (err) {
|
|
349
|
+
const before = buildMergeExecuteBeforeFacts(view, checks, mergePlanBody, null);
|
|
350
|
+
return forgePacket(
|
|
351
|
+
PACKET_TYPES.CR_MERGE_BLOCKED,
|
|
352
|
+
ctx,
|
|
353
|
+
buildCrMergeBlockedBody({
|
|
354
|
+
prNumber: number,
|
|
355
|
+
expected,
|
|
356
|
+
before,
|
|
357
|
+
blockers: ['head_ref_invalid'],
|
|
358
|
+
}),
|
|
359
|
+
forgeError(
|
|
360
|
+
ERROR_CODES.INVALID_ARGS,
|
|
361
|
+
sanitizeField(err.forgeError?.message || err.message || err.invalidArgs)
|
|
362
|
+
|| 'Head branch ref invalid',
|
|
363
|
+
),
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
forgeHeadRefSha = await provider.branchHeadSha(ctx, headRef, {
|
|
368
|
+
repoId: view.forge_source_repo_id ?? null,
|
|
369
|
+
});
|
|
370
|
+
} catch (err) {
|
|
371
|
+
const before = buildMergeExecuteBeforeFacts(view, checks, mergePlanBody, null);
|
|
372
|
+
return forgePacket(
|
|
373
|
+
PACKET_TYPES.CR_MERGE_BLOCKED,
|
|
374
|
+
ctx,
|
|
375
|
+
buildCrMergeBlockedBody({
|
|
376
|
+
prNumber: number,
|
|
377
|
+
expected,
|
|
378
|
+
before,
|
|
379
|
+
blockers: ['head_ref_unreadable'],
|
|
380
|
+
}),
|
|
381
|
+
forgeError(
|
|
382
|
+
ERROR_CODES.MERGE_BLOCKED,
|
|
383
|
+
sanitizeField(err.forgeError?.message || err.message) || 'Head branch ref unreadable',
|
|
384
|
+
err.status ?? err.forgeError?.status ?? null,
|
|
385
|
+
),
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const before = buildMergeExecuteBeforeFacts(view, checks, mergePlanBody, forgeHeadRefSha);
|
|
391
|
+
const blockers = collectMergeExecuteBlockers(
|
|
392
|
+
view,
|
|
393
|
+
checks,
|
|
394
|
+
mergePlanBody,
|
|
395
|
+
expected,
|
|
396
|
+
{ forgeHeadRefSha },
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
if (blockers.length > 0) {
|
|
400
|
+
return forgePacket(
|
|
401
|
+
PACKET_TYPES.CR_MERGE_BLOCKED,
|
|
402
|
+
ctx,
|
|
403
|
+
buildCrMergeBlockedBody({ prNumber: number, expected, before, blockers }),
|
|
404
|
+
forgeError(ERROR_CODES.MERGE_BLOCKED, 'Merge blocked by preflight'),
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
const providerResult = await provider.mergeExecute(ctx, {
|
|
410
|
+
number,
|
|
411
|
+
method,
|
|
412
|
+
expectedHeadSha: expected.headSha,
|
|
413
|
+
});
|
|
414
|
+
const merge = buildMergeExecuteMergeFacts(method, providerResult);
|
|
415
|
+
const after = buildMergeExecuteAfterFacts(view, providerResult);
|
|
416
|
+
return forgePacket(
|
|
417
|
+
PACKET_TYPES.CR_MERGED,
|
|
418
|
+
ctx,
|
|
419
|
+
buildCrMergedBody({ prNumber: number, expected, before, merge, after }),
|
|
420
|
+
);
|
|
421
|
+
} catch (err) {
|
|
422
|
+
const status = err.status ?? err.forgeError?.status ?? null;
|
|
423
|
+
const blockers =
|
|
424
|
+
Array.isArray(err.mergeBlockedBlockers) && err.mergeBlockedBlockers.length > 0
|
|
425
|
+
? err.mergeBlockedBlockers
|
|
426
|
+
: ['merge_endpoint_failed'];
|
|
427
|
+
const fe =
|
|
428
|
+
err.forgeError
|
|
429
|
+
?? forgeError(
|
|
430
|
+
ERROR_CODES.MERGE_ENDPOINT_FAILED,
|
|
431
|
+
sanitizeField(err.message) || 'Forge merge request failed',
|
|
432
|
+
status,
|
|
433
|
+
);
|
|
434
|
+
return forgePacket(
|
|
435
|
+
PACKET_TYPES.CR_MERGE_BLOCKED,
|
|
436
|
+
ctx,
|
|
437
|
+
buildCrMergeBlockedBody({
|
|
438
|
+
prNumber: number,
|
|
439
|
+
expected,
|
|
440
|
+
before,
|
|
441
|
+
blockers,
|
|
442
|
+
}),
|
|
443
|
+
fe,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
260
447
|
if (group === 'branch' && sub === 'protection') {
|
|
261
448
|
const branchRef = flags.branch_ref;
|
|
262
449
|
if (!branchRef) {
|
|
@@ -306,7 +493,7 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
306
493
|
throw Object.assign(new Error(`Unknown command: ${positional.join(' ')}`), {
|
|
307
494
|
forgeError: forgeError(
|
|
308
495
|
ERROR_CODES.INVALID_ARGS,
|
|
309
|
-
'Unknown command. Try: provider capabilities, repo status, refs compare, refs inventory, cr inventory, cr files, cr comments, cr open, status set, forge changes, pr view, pr checks, merge plan, sync plan, whoami, branch protection',
|
|
496
|
+
'Unknown command. Try: provider capabilities, repo status, refs compare, refs inventory, cr inventory, cr files, cr comments, cr open, status set, forge changes, pr view, pr checks, merge plan, merge execute, sync plan, whoami, branch protection',
|
|
310
497
|
),
|
|
311
498
|
});
|
|
312
499
|
}
|
package/cli-doctor.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
gitRemoteUrl,
|
|
5
5
|
parseRemoteUrl,
|
|
6
6
|
trustedBaseUrl,
|
|
7
|
+
normalizedForgeOrigin,
|
|
7
8
|
assertConfigMatchesRemote,
|
|
8
9
|
forgePacket,
|
|
9
10
|
unknownForgeContext,
|
|
@@ -132,6 +133,9 @@ export async function buildDoctorPacket(cwd, providers) {
|
|
|
132
133
|
);
|
|
133
134
|
} else {
|
|
134
135
|
checks.push(doctorCheck('host_binding', 'pass', 'Configured host binding is trusted'));
|
|
136
|
+
if (config.baseUrl) {
|
|
137
|
+
ctx = { ...ctx, baseUrl: normalizedForgeOrigin(config) };
|
|
138
|
+
}
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
|
package/cli-io.js
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
forgePacket,
|
|
3
|
+
forgeErrorPacket,
|
|
4
|
+
forgeError,
|
|
5
|
+
ERROR_CODES,
|
|
6
|
+
normalizedForgeOrigin,
|
|
7
|
+
trustedBaseUrl,
|
|
8
|
+
} from '@remogram/core';
|
|
2
9
|
|
|
3
10
|
export function output(packet, asJson) {
|
|
4
11
|
console.log(JSON.stringify(packet, null, asJson ? 2 : 0));
|
|
5
12
|
}
|
|
6
13
|
|
|
7
14
|
export function handleError(err, ctx, asJson) {
|
|
8
|
-
const fe =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
const fe =
|
|
16
|
+
err.forgeError
|
|
17
|
+
|| (err.invalidArgs
|
|
18
|
+
? forgeError(ERROR_CODES.INVALID_ARGS, err.invalidArgs)
|
|
19
|
+
: {
|
|
20
|
+
code: ERROR_CODES.API_ERROR,
|
|
21
|
+
message: err.message,
|
|
22
|
+
status: err.status,
|
|
23
|
+
});
|
|
13
24
|
const baseCtx = ctx || {
|
|
14
25
|
providerId: 'unknown',
|
|
15
26
|
remoteName: 'origin',
|
|
@@ -25,7 +36,7 @@ export function handleError(err, ctx, asJson) {
|
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
export function contextFromConfig(config, cwd, parsed = null) {
|
|
28
|
-
|
|
39
|
+
const ctx = {
|
|
29
40
|
providerId: config.provider,
|
|
30
41
|
remoteName: config.remote,
|
|
31
42
|
repoId: parsed ? `${parsed.owner}/${parsed.repo}` : `${config.owner}/${config.repo}`,
|
|
@@ -33,4 +44,8 @@ export function contextFromConfig(config, cwd, parsed = null) {
|
|
|
33
44
|
cwd,
|
|
34
45
|
parsed,
|
|
35
46
|
};
|
|
47
|
+
if (config.baseUrl && (!parsed || trustedBaseUrl(config, parsed.host))) {
|
|
48
|
+
ctx.baseUrl = normalizedForgeOrigin(config);
|
|
49
|
+
}
|
|
50
|
+
return ctx;
|
|
36
51
|
}
|
package/index.js
CHANGED
|
@@ -64,6 +64,7 @@ export async function runCli(argv, options = {}) {
|
|
|
64
64
|
try {
|
|
65
65
|
const packet = await dispatchForgeCommand({ group, sub, flags, positional, ctx, provider });
|
|
66
66
|
output(packet, asJson);
|
|
67
|
+
if (!packet.ok) process.exitCode = 1;
|
|
67
68
|
} catch (err) {
|
|
68
69
|
handleError(err, ctx, asJson);
|
|
69
70
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remogram/cli",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.8",
|
|
4
4
|
"description": "Remogram forge boundary CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"node": ">=20"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@remogram/core": "0.1.0-beta.
|
|
31
|
-
"@remogram/provider-gitea-api": "0.1.0-beta.
|
|
32
|
-
"@remogram/provider-github-api": "0.1.0-beta.
|
|
33
|
-
"@remogram/provider-gitlab-api": "0.1.0-beta.
|
|
34
|
-
"@remogram/provider-gitea-tea": "0.1.0-beta.
|
|
35
|
-
"@remogram/provider-github-gh": "0.1.0-beta.
|
|
30
|
+
"@remogram/core": "0.1.0-beta.8",
|
|
31
|
+
"@remogram/provider-gitea-api": "0.1.0-beta.8",
|
|
32
|
+
"@remogram/provider-github-api": "0.1.0-beta.8",
|
|
33
|
+
"@remogram/provider-gitlab-api": "0.1.0-beta.8",
|
|
34
|
+
"@remogram/provider-gitea-tea": "0.1.0-beta.8",
|
|
35
|
+
"@remogram/provider-github-gh": "0.1.0-beta.8"
|
|
36
36
|
}
|
|
37
37
|
}
|