@remogram/cli 0.1.0-beta.8 → 0.1.0-beta.9
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 +98 -13
- package/cli-doctor.js +73 -22
- package/cli-io.js +2 -0
- package/index.js +1 -1
- package/package.json +7 -7
package/cli-dispatch.js
CHANGED
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
forgeFactInventoryPacket,
|
|
12
12
|
assertWriteCommandConfigured,
|
|
13
13
|
parseSinceObservedAt,
|
|
14
|
+
decodeForgeChangesCursor,
|
|
15
|
+
paginateForgeChangesBody,
|
|
16
|
+
DEFAULT_FORGE_CHANGES_PAGE_SIZE,
|
|
14
17
|
normalizeAllowedPaths,
|
|
15
18
|
assertExpectedSha,
|
|
16
19
|
buildMergeExecuteBeforeFacts,
|
|
@@ -21,6 +24,7 @@ import {
|
|
|
21
24
|
buildMergeExecuteMergeFacts,
|
|
22
25
|
mergeExecuteViewFacts,
|
|
23
26
|
isOpenPrState,
|
|
27
|
+
bindIdempotencyScope,
|
|
24
28
|
} from '@remogram/core';
|
|
25
29
|
import { parseAllowedPathFlags, parsePositiveInt } from './cli-argv.js';
|
|
26
30
|
|
|
@@ -77,8 +81,9 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
77
81
|
slice_ref: flags.slice_ref,
|
|
78
82
|
limit: parsePositiveInt(flags.limit, '--limit'),
|
|
79
83
|
sort: flags.sort,
|
|
84
|
+
cursor: flags.cursor,
|
|
80
85
|
});
|
|
81
|
-
if (inventoryBody.list_truncated === true) {
|
|
86
|
+
if (inventoryBody.list_truncated === true && !flags.cursor) {
|
|
82
87
|
throw Object.assign(new Error('Open CR list incomplete'), {
|
|
83
88
|
forgeError: forgeError(
|
|
84
89
|
ERROR_CODES.INVENTORY_LIST_INCOMPLETE,
|
|
@@ -141,7 +146,6 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
141
146
|
);
|
|
142
147
|
}
|
|
143
148
|
if (group === 'forge' && sub === 'changes') {
|
|
144
|
-
const sinceIso = parseSinceObservedAt(flags.since);
|
|
145
149
|
if (typeof provider.forgeChanges !== 'function') {
|
|
146
150
|
throw Object.assign(new Error('forge changes not implemented for provider'), {
|
|
147
151
|
forgeError: forgeError(
|
|
@@ -150,11 +154,19 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
150
154
|
),
|
|
151
155
|
});
|
|
152
156
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
let sinceIso;
|
|
158
|
+
let cursorOffset = 0;
|
|
159
|
+
const pageLimit = parsePositiveInt(flags.limit, '--limit') ?? DEFAULT_FORGE_CHANGES_PAGE_SIZE;
|
|
160
|
+
if (flags.cursor) {
|
|
161
|
+
const decoded = decodeForgeChangesCursor(flags.cursor, { since: flags.since });
|
|
162
|
+
sinceIso = decoded.since;
|
|
163
|
+
cursorOffset = decoded.offset;
|
|
164
|
+
} else {
|
|
165
|
+
sinceIso = parseSinceObservedAt(flags.since);
|
|
166
|
+
}
|
|
167
|
+
const body = await provider.forgeChanges(ctx, { since: sinceIso });
|
|
168
|
+
const paginated = paginateForgeChangesBody(body, { offset: cursorOffset, limit: pageLimit });
|
|
169
|
+
return forgePacket(PACKET_TYPES.FORGE_CHANGES, ctx, paginated);
|
|
158
170
|
}
|
|
159
171
|
if (group === 'cr' && sub === 'open') {
|
|
160
172
|
if (typeof provider.crOpen !== 'function') {
|
|
@@ -176,6 +188,9 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
176
188
|
assertGitRef(flags.head, '--head');
|
|
177
189
|
assertGitRef(flags.base, '--base');
|
|
178
190
|
assertWriteCommandConfigured(ctx.config, 'cr_open');
|
|
191
|
+
const idempotencyFingerprint = flags.idempotency_key
|
|
192
|
+
? bindIdempotencyScope(ctx.repo_id, flags.idempotency_key, [flags.head, flags.base])
|
|
193
|
+
: null;
|
|
179
194
|
return forgePacket(
|
|
180
195
|
PACKET_TYPES.CHANGE_REQUEST_OPENED,
|
|
181
196
|
ctx,
|
|
@@ -184,6 +199,35 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
184
199
|
base: flags.base,
|
|
185
200
|
title: flags.title,
|
|
186
201
|
body: flags.body,
|
|
202
|
+
idempotencyFingerprint,
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
if (group === 'issue' && sub === 'open') {
|
|
207
|
+
if (typeof provider.issueOpen !== 'function') {
|
|
208
|
+
throw Object.assign(new Error('issue open not implemented for provider'), {
|
|
209
|
+
forgeError: forgeError(
|
|
210
|
+
ERROR_CODES.PROVIDER_UNSUPPORTED,
|
|
211
|
+
'issue open not implemented for provider',
|
|
212
|
+
),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (!flags.title) {
|
|
216
|
+
throw Object.assign(new Error('--title required'), {
|
|
217
|
+
forgeError: forgeError(ERROR_CODES.INVALID_ARGS, '--title required for issue open'),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
assertWriteCommandConfigured(ctx.config, 'issue_open');
|
|
221
|
+
const idempotencyFingerprint = flags.idempotency_key
|
|
222
|
+
? bindIdempotencyScope(ctx.repo_id, flags.idempotency_key, [flags.title])
|
|
223
|
+
: null;
|
|
224
|
+
return forgePacket(
|
|
225
|
+
PACKET_TYPES.ISSUE_OPENED,
|
|
226
|
+
ctx,
|
|
227
|
+
await provider.issueOpen(ctx, {
|
|
228
|
+
title: flags.title,
|
|
229
|
+
body: flags.body,
|
|
230
|
+
idempotencyFingerprint,
|
|
187
231
|
}),
|
|
188
232
|
);
|
|
189
233
|
}
|
|
@@ -197,6 +241,13 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
197
241
|
});
|
|
198
242
|
}
|
|
199
243
|
assertWriteCommandConfigured(ctx.config, 'status_set');
|
|
244
|
+
const idempotencyFingerprint = flags.idempotency_key
|
|
245
|
+
? bindIdempotencyScope(ctx.repo_id, flags.idempotency_key, [
|
|
246
|
+
flags.sha,
|
|
247
|
+
flags.context,
|
|
248
|
+
flags.state,
|
|
249
|
+
])
|
|
250
|
+
: null;
|
|
200
251
|
return forgePacket(
|
|
201
252
|
PACKET_TYPES.COMMIT_STATUS_SET,
|
|
202
253
|
ctx,
|
|
@@ -206,6 +257,7 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
206
257
|
state: flags.state,
|
|
207
258
|
target_url: flags.target_url,
|
|
208
259
|
description: flags.description,
|
|
260
|
+
idempotencyFingerprint,
|
|
209
261
|
}),
|
|
210
262
|
);
|
|
211
263
|
}
|
|
@@ -312,7 +364,13 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
312
364
|
let forgeHeadRefSha = null;
|
|
313
365
|
const headRef = viewFacts.sourceBranchRef ? String(viewFacts.sourceBranchRef).trim() : '';
|
|
314
366
|
if (!headRef && isOpenPrState(view.state)) {
|
|
315
|
-
const before = buildMergeExecuteBeforeFacts(
|
|
367
|
+
const before = buildMergeExecuteBeforeFacts(
|
|
368
|
+
view,
|
|
369
|
+
checks,
|
|
370
|
+
mergePlanBody,
|
|
371
|
+
null,
|
|
372
|
+
ctx.mergePolicy,
|
|
373
|
+
);
|
|
316
374
|
return forgePacket(
|
|
317
375
|
PACKET_TYPES.CR_MERGE_BLOCKED,
|
|
318
376
|
ctx,
|
|
@@ -327,7 +385,13 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
327
385
|
}
|
|
328
386
|
if (headRef) {
|
|
329
387
|
if (typeof provider.branchHeadSha !== 'function') {
|
|
330
|
-
const before = buildMergeExecuteBeforeFacts(
|
|
388
|
+
const before = buildMergeExecuteBeforeFacts(
|
|
389
|
+
view,
|
|
390
|
+
checks,
|
|
391
|
+
mergePlanBody,
|
|
392
|
+
null,
|
|
393
|
+
ctx.mergePolicy,
|
|
394
|
+
);
|
|
331
395
|
return forgePacket(
|
|
332
396
|
PACKET_TYPES.CR_MERGE_BLOCKED,
|
|
333
397
|
ctx,
|
|
@@ -346,7 +410,13 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
346
410
|
try {
|
|
347
411
|
assertGitRef(headRef, 'head_ref');
|
|
348
412
|
} catch (err) {
|
|
349
|
-
const before = buildMergeExecuteBeforeFacts(
|
|
413
|
+
const before = buildMergeExecuteBeforeFacts(
|
|
414
|
+
view,
|
|
415
|
+
checks,
|
|
416
|
+
mergePlanBody,
|
|
417
|
+
null,
|
|
418
|
+
ctx.mergePolicy,
|
|
419
|
+
);
|
|
350
420
|
return forgePacket(
|
|
351
421
|
PACKET_TYPES.CR_MERGE_BLOCKED,
|
|
352
422
|
ctx,
|
|
@@ -368,7 +438,16 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
368
438
|
repoId: view.forge_source_repo_id ?? null,
|
|
369
439
|
});
|
|
370
440
|
} catch (err) {
|
|
371
|
-
|
|
441
|
+
if (err.forgeError?.code === ERROR_CODES.INVALID_ARGS) {
|
|
442
|
+
throw err;
|
|
443
|
+
}
|
|
444
|
+
const before = buildMergeExecuteBeforeFacts(
|
|
445
|
+
view,
|
|
446
|
+
checks,
|
|
447
|
+
mergePlanBody,
|
|
448
|
+
null,
|
|
449
|
+
ctx.mergePolicy,
|
|
450
|
+
);
|
|
372
451
|
return forgePacket(
|
|
373
452
|
PACKET_TYPES.CR_MERGE_BLOCKED,
|
|
374
453
|
ctx,
|
|
@@ -387,13 +466,19 @@ export async function dispatchForgeCommand({ group, sub, flags, positional, ctx,
|
|
|
387
466
|
}
|
|
388
467
|
}
|
|
389
468
|
|
|
390
|
-
const before = buildMergeExecuteBeforeFacts(
|
|
469
|
+
const before = buildMergeExecuteBeforeFacts(
|
|
470
|
+
view,
|
|
471
|
+
checks,
|
|
472
|
+
mergePlanBody,
|
|
473
|
+
forgeHeadRefSha,
|
|
474
|
+
ctx.mergePolicy,
|
|
475
|
+
);
|
|
391
476
|
const blockers = collectMergeExecuteBlockers(
|
|
392
477
|
view,
|
|
393
478
|
checks,
|
|
394
479
|
mergePlanBody,
|
|
395
480
|
expected,
|
|
396
|
-
{ forgeHeadRefSha },
|
|
481
|
+
{ forgeHeadRefSha, mergePolicy: ctx.mergePolicy },
|
|
397
482
|
);
|
|
398
483
|
|
|
399
484
|
if (blockers.length > 0) {
|
package/cli-doctor.js
CHANGED
|
@@ -16,6 +16,12 @@ import {
|
|
|
16
16
|
getEffectiveIngestMaxBytes,
|
|
17
17
|
FORGE_INGEST_MAX_BYTES_ENV,
|
|
18
18
|
MAX_FORGE_INGEST_ENV_BYTES,
|
|
19
|
+
resolveMergePolicy,
|
|
20
|
+
ALLOW_MISSING_CHECKS_ENV,
|
|
21
|
+
ALLOW_PENDING_CHECKS_ENV,
|
|
22
|
+
buildWriteReadiness,
|
|
23
|
+
writeReadinessHasWarnings,
|
|
24
|
+
buildApiReachabilityCheck,
|
|
19
25
|
} from '@remogram/core';
|
|
20
26
|
import { contextFromConfig } from './cli-io.js';
|
|
21
27
|
|
|
@@ -34,25 +40,23 @@ export function doctorSummary(checks) {
|
|
|
34
40
|
return 'pass';
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
function finalizeDoctorPacket(ctx, checks, providerCapabilities) {
|
|
43
|
+
function finalizeDoctorPacket(ctx, checks, providerCapabilities, writeConfig = null) {
|
|
38
44
|
const summary = doctorSummary(checks);
|
|
39
45
|
const error =
|
|
40
46
|
summary === 'fail'
|
|
41
47
|
? forgeError(ERROR_CODES.CONFIG_INVALID, 'Doctor checks failed')
|
|
42
48
|
: null;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
},
|
|
51
|
-
error,
|
|
52
|
-
);
|
|
49
|
+
const body = {
|
|
50
|
+
summary,
|
|
51
|
+
checks,
|
|
52
|
+
provider_capabilities: providerCapabilities,
|
|
53
|
+
};
|
|
54
|
+
if (writeConfig) body.write_config = writeConfig;
|
|
55
|
+
return forgePacket(PACKET_TYPES.PROVIDER_DOCTOR, ctx, body, error);
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
export async function buildDoctorPacket(cwd, providers) {
|
|
58
|
+
export async function buildDoctorPacket(cwd, providers, options = {}) {
|
|
59
|
+
const { live = false } = options;
|
|
56
60
|
const checks = [];
|
|
57
61
|
const configPath = findConfigPath(cwd);
|
|
58
62
|
let loaded = null;
|
|
@@ -139,9 +143,12 @@ export async function buildDoctorPacket(cwd, providers) {
|
|
|
139
143
|
}
|
|
140
144
|
}
|
|
141
145
|
|
|
146
|
+
let writeConfig = null;
|
|
147
|
+
|
|
142
148
|
if (providerCapabilities) {
|
|
143
149
|
const envNames = providerCapabilities.auth_envs || [];
|
|
144
150
|
const presentEnv = envNames.find((name) => Boolean(process.env[name])) || null;
|
|
151
|
+
const authPresent = Boolean(presentEnv);
|
|
145
152
|
checks.push(
|
|
146
153
|
doctorCheck(
|
|
147
154
|
'auth',
|
|
@@ -152,17 +159,22 @@ export async function buildDoctorPacket(cwd, providers) {
|
|
|
152
159
|
);
|
|
153
160
|
|
|
154
161
|
if (providerCapabilities.write_support) {
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
const
|
|
162
|
+
writeConfig = buildWriteReadiness(config, providerCapabilities, { authPresent });
|
|
163
|
+
const warn = writeReadinessHasWarnings(writeConfig);
|
|
164
|
+
const notReady = writeConfig.commands.filter(
|
|
165
|
+
(entry) => entry.provider_supported && !entry.ready,
|
|
166
|
+
);
|
|
167
|
+
const missingConfig = notReady.filter((entry) => !entry.configured).map((entry) => entry.id);
|
|
158
168
|
checks.push(
|
|
159
169
|
doctorCheck(
|
|
160
170
|
'write_config',
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
?
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
warn ? 'warn' : 'pass',
|
|
172
|
+
warn
|
|
173
|
+
? missingConfig.length
|
|
174
|
+
? `Provider supports write commands but .remogram.json write_commands omits: ${missingConfig.join(', ')}. Add ids for Remogram CLI/MCP writes, or use forge/CI tooling for those actions outside Remogram.`
|
|
175
|
+
: 'One or more configured write commands are not ready (check auth or provider support)'
|
|
176
|
+
: 'All provider write commands are configured and ready',
|
|
177
|
+
writeConfig,
|
|
166
178
|
),
|
|
167
179
|
);
|
|
168
180
|
}
|
|
@@ -209,7 +221,46 @@ export async function buildDoctorPacket(cwd, providers) {
|
|
|
209
221
|
);
|
|
210
222
|
}
|
|
211
223
|
|
|
212
|
-
|
|
224
|
+
const mergePolicy = resolveMergePolicy(config);
|
|
225
|
+
if (mergePolicy.allow_missing_checks || mergePolicy.allow_pending_checks) {
|
|
226
|
+
checks.push(
|
|
227
|
+
doctorCheck(
|
|
228
|
+
'merge_policy',
|
|
229
|
+
'warn',
|
|
230
|
+
'Merge policy relaxes check blockers for merge plan and merge execute',
|
|
231
|
+
{
|
|
232
|
+
allow_missing_checks: mergePolicy.allow_missing_checks,
|
|
233
|
+
allow_pending_checks: mergePolicy.allow_pending_checks,
|
|
234
|
+
source: mergePolicy.source,
|
|
235
|
+
env_names: [ALLOW_MISSING_CHECKS_ENV, ALLOW_PENDING_CHECKS_ENV],
|
|
236
|
+
},
|
|
237
|
+
),
|
|
238
|
+
);
|
|
239
|
+
} else {
|
|
240
|
+
checks.push(
|
|
241
|
+
doctorCheck(
|
|
242
|
+
'merge_policy',
|
|
243
|
+
'pass',
|
|
244
|
+
'Default merge policy — missing and pending checks block merge',
|
|
245
|
+
{
|
|
246
|
+
allow_missing_checks: false,
|
|
247
|
+
allow_pending_checks: false,
|
|
248
|
+
source: mergePolicy.source,
|
|
249
|
+
},
|
|
250
|
+
),
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const hostBindingPass = checks.some(
|
|
255
|
+
(check) => check.name === 'host_binding' && check.status === 'pass',
|
|
256
|
+
);
|
|
257
|
+
const configPass = checks.some((check) => check.name === 'config' && check.status === 'pass');
|
|
258
|
+
checks.push(
|
|
259
|
+
await buildApiReachabilityCheck(ctx, provider, {
|
|
260
|
+
live,
|
|
261
|
+
prerequisitesPass: live && configPass && hostBindingPass && parsed != null,
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
213
264
|
|
|
214
|
-
return finalizeDoctorPacket(ctx, checks, providerCapabilities);
|
|
265
|
+
return finalizeDoctorPacket(ctx, checks, providerCapabilities, writeConfig);
|
|
215
266
|
}
|
package/cli-io.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ERROR_CODES,
|
|
6
6
|
normalizedForgeOrigin,
|
|
7
7
|
trustedBaseUrl,
|
|
8
|
+
resolveMergePolicy,
|
|
8
9
|
} from '@remogram/core';
|
|
9
10
|
|
|
10
11
|
export function output(packet, asJson) {
|
|
@@ -47,5 +48,6 @@ export function contextFromConfig(config, cwd, parsed = null) {
|
|
|
47
48
|
if (config.baseUrl && (!parsed || trustedBaseUrl(config, parsed.host))) {
|
|
48
49
|
ctx.baseUrl = normalizedForgeOrigin(config);
|
|
49
50
|
}
|
|
51
|
+
ctx.mergePolicy = resolveMergePolicy(config);
|
|
50
52
|
return ctx;
|
|
51
53
|
}
|
package/index.js
CHANGED
|
@@ -30,7 +30,7 @@ export async function runCli(argv, options = {}) {
|
|
|
30
30
|
const [group, sub] = positional;
|
|
31
31
|
|
|
32
32
|
if (group === 'doctor' && sub == null) {
|
|
33
|
-
const packet = await buildDoctorPacket(cwd, providers);
|
|
33
|
+
const packet = await buildDoctorPacket(cwd, providers, { live: flags.live === true });
|
|
34
34
|
output(packet, asJson);
|
|
35
35
|
if (!packet.ok) process.exitCode = 1;
|
|
36
36
|
return;
|
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.9",
|
|
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.9",
|
|
31
|
+
"@remogram/provider-gitea-api": "0.1.0-beta.9",
|
|
32
|
+
"@remogram/provider-github-api": "0.1.0-beta.9",
|
|
33
|
+
"@remogram/provider-gitlab-api": "0.1.0-beta.9",
|
|
34
|
+
"@remogram/provider-gitea-tea": "0.1.0-beta.9",
|
|
35
|
+
"@remogram/provider-github-gh": "0.1.0-beta.9"
|
|
36
36
|
}
|
|
37
37
|
}
|