@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 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.head_ref,
215
- body.head_sha,
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
- { head_sha: view.head_sha },
233
- view.head_ref,
234
- view.head_sha,
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 { forgePacket, forgeErrorPacket, ERROR_CODES, forgeError } from '@remogram/core';
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 = err.forgeError || {
9
- code: ERROR_CODES.API_ERROR,
10
- message: err.message,
11
- status: err.status,
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
- return {
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.6",
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.6",
31
- "@remogram/provider-gitea-api": "0.1.0-beta.6",
32
- "@remogram/provider-github-api": "0.1.0-beta.6",
33
- "@remogram/provider-gitlab-api": "0.1.0-beta.6",
34
- "@remogram/provider-gitea-tea": "0.1.0-beta.6",
35
- "@remogram/provider-github-gh": "0.1.0-beta.6"
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
  }