premanmcp 0.15.2 → 0.16.0
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/bin/integrations.js +133 -5
- package/package.json +1 -1
package/bin/integrations.js
CHANGED
|
@@ -374,6 +374,106 @@ export async function slackCommand(args) {
|
|
|
374
374
|
* that asks defaults to yes today; the desktop app, which used to be the
|
|
375
375
|
* exception, no longer asks at all.
|
|
376
376
|
*/
|
|
377
|
+
/**
|
|
378
|
+
* Ask, once, before PreMan writes into an SDK repository or a docs site.
|
|
379
|
+
*
|
|
380
|
+
* Two questions rather than one. Agreeing to a generated client library is not
|
|
381
|
+
* agreeing to have your documentation site rewritten (a docs release replaces
|
|
382
|
+
* mkdocs.yml), and a single answer used to buy both.
|
|
383
|
+
*
|
|
384
|
+
* Says nothing when there is nothing to ask: no connected repository, no
|
|
385
|
+
* detected target, or an answer already on file. A walk that re-asks a question
|
|
386
|
+
* it has already had answered teaches people that answering buys them nothing,
|
|
387
|
+
* which is the whole reason this moved out of the delivery run.
|
|
388
|
+
*/
|
|
389
|
+
async function offerReleaseTargets(args, checkout, { assumeYes }) {
|
|
390
|
+
const integrationId = checkout?.match?.integration_id;
|
|
391
|
+
if (!integrationId) return { state: "skipped" };
|
|
392
|
+
|
|
393
|
+
const token = resolveApiKey(args);
|
|
394
|
+
const path = `/sdk/integrations/${integrationId}/distribution`;
|
|
395
|
+
let detected = null;
|
|
396
|
+
try {
|
|
397
|
+
detected = await callBackendJson(args, "GET", path, { token });
|
|
398
|
+
} catch {
|
|
399
|
+
// Detection costs one root listing per repository in the installation. It
|
|
400
|
+
// is the least important thing in this walk and never worth failing setup
|
|
401
|
+
// over.
|
|
402
|
+
return { state: "skipped" };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (detected?.confirmed) {
|
|
406
|
+
process.stdout.write(`${MARK.ok()} Release targets already answered.\n`);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const sdkRepo = detected?.sdk_repo || "";
|
|
411
|
+
const docsRepo = detected?.docs_repo || "";
|
|
412
|
+
if (!sdkRepo && !docsRepo) return { state: "skipped" };
|
|
413
|
+
|
|
414
|
+
const body = {};
|
|
415
|
+
const decided = [];
|
|
416
|
+
for (const [repo, field, flag, question] of [
|
|
417
|
+
[
|
|
418
|
+
sdkRepo,
|
|
419
|
+
"sdk_repo",
|
|
420
|
+
"publish_sdk",
|
|
421
|
+
`${sdkRepo} looks like your client library. Open pull requests there with generated SDKs?`,
|
|
422
|
+
],
|
|
423
|
+
[
|
|
424
|
+
docsRepo,
|
|
425
|
+
"docs_repo",
|
|
426
|
+
"publish_docs",
|
|
427
|
+
// The wider blast radius is named in the question, because it is the part
|
|
428
|
+
// somebody would resent discovering afterwards.
|
|
429
|
+
`${docsRepo} looks like your docs site. Open pull requests there? Releases rewrite mkdocs.yml.`,
|
|
430
|
+
],
|
|
431
|
+
]) {
|
|
432
|
+
if (!repo) continue;
|
|
433
|
+
const answer = await askRelease(question, assumeYes);
|
|
434
|
+
if (answer === null) continue;
|
|
435
|
+
body[field] = repo;
|
|
436
|
+
body[flag] = answer;
|
|
437
|
+
decided.push([repo, answer]);
|
|
438
|
+
}
|
|
439
|
+
if (!decided.length) return { state: "skipped" };
|
|
440
|
+
|
|
441
|
+
await callBackendJson(args, "PUT", path, { token, json: body });
|
|
442
|
+
|
|
443
|
+
for (const [repo, allowed] of decided) {
|
|
444
|
+
// A no is reported as recorded, not as a step that failed. It is an answer,
|
|
445
|
+
// and PreMan will not come back to it.
|
|
446
|
+
if (allowed) process.stdout.write(`${MARK.ok()} Publishing to ${repo}.\n`);
|
|
447
|
+
else
|
|
448
|
+
process.stdout.write(
|
|
449
|
+
`${MARK.skip()} Not publishing to ${repo}. PreMan will not ask again.\n`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Yes, no, or nobody answered.
|
|
456
|
+
*
|
|
457
|
+
* Three outcomes rather than two, and the third is the whole reason this does
|
|
458
|
+
* not use `confirm`. That helper reads a closed stdin as a no, which is right
|
|
459
|
+
* for a step that then simply does less work. A no here is *recorded*, and a
|
|
460
|
+
* recorded no is never asked again, so reading silence as refusal would retire
|
|
461
|
+
* a question the customer never saw.
|
|
462
|
+
*
|
|
463
|
+
* The same argument applies to --yes: accepting defaults on someone's behalf is
|
|
464
|
+
* fine for a hook or a download, and is not a way to acquire write access to a
|
|
465
|
+
* repository nobody mentioned. So an unattended run leaves both questions open.
|
|
466
|
+
*
|
|
467
|
+
* The default is no, for the reason silence means no everywhere else PreMan
|
|
468
|
+
* writes to somebody's repository.
|
|
469
|
+
*/
|
|
470
|
+
async function askRelease(question, assumeYes) {
|
|
471
|
+
if (assumeYes) return null;
|
|
472
|
+
const answer = (await promptText(`${question} [y/N]: `)).trim().toLowerCase();
|
|
473
|
+
if (answer === "") return null;
|
|
474
|
+
return answer === "y" || answer === "yes";
|
|
475
|
+
}
|
|
476
|
+
|
|
377
477
|
async function askStep(question, { assumeYes, canGoBack, defaultYes = true }) {
|
|
378
478
|
const fallback = defaultYes ? "yes" : "no";
|
|
379
479
|
if (assumeYes) return fallback;
|
|
@@ -525,6 +625,15 @@ export async function onboardCommand(
|
|
|
525
625
|
});
|
|
526
626
|
}
|
|
527
627
|
|
|
628
|
+
// The only questions in this walk about repositories *other* than the one
|
|
629
|
+
// being connected. They are asked here, while PreMan has just read the
|
|
630
|
+
// installation and can name what it found, rather than weeks later from
|
|
631
|
+
// inside a delivery run, on a screen most people never opened.
|
|
632
|
+
steps.push({
|
|
633
|
+
name: "Releases",
|
|
634
|
+
run: () => offerReleaseTargets(args, checkout, { assumeYes }),
|
|
635
|
+
});
|
|
636
|
+
|
|
528
637
|
steps.push({
|
|
529
638
|
name: "Push checks",
|
|
530
639
|
question: "Check your endpoints on every git push in this repo?",
|
|
@@ -615,7 +724,9 @@ export async function onboardCommand(
|
|
|
615
724
|
// why they are looking at a login screen.
|
|
616
725
|
process.stdout.write("Opened PreMan \u2014 sign in with the account you just used.\n");
|
|
617
726
|
}
|
|
618
|
-
|
|
727
|
+
// Whether the app is on screen decides what the last line of the walk
|
|
728
|
+
// should say, and only this closure knows.
|
|
729
|
+
return { ...installed, appOpened: opened.state !== "not-installed" };
|
|
619
730
|
} finally {
|
|
620
731
|
stopOpening?.();
|
|
621
732
|
}
|
|
@@ -665,10 +776,18 @@ export async function onboardCommand(
|
|
|
665
776
|
}
|
|
666
777
|
|
|
667
778
|
try {
|
|
668
|
-
// A step that caught its own failure and already explained it says so
|
|
669
|
-
//
|
|
779
|
+
// A step that caught its own failure and already explained it says so; a step
|
|
780
|
+
// with nothing to do says that; anything else that returns is done.
|
|
781
|
+
//
|
|
782
|
+
// Translated rather than spread over, because the summary knows three words
|
|
783
|
+
// and a step may report any of its own. Spreading put `Desktop app: undefined`
|
|
784
|
+
// at the end of a successful install: the install reports state "installed",
|
|
785
|
+
// which is none of the three, so it printed as a failure with no detail.
|
|
670
786
|
const reported = await step.run();
|
|
671
|
-
|
|
787
|
+
const state =
|
|
788
|
+
{ failed: "failed", skipped: "skipped", unsupported: "skipped" }[reported?.state] ||
|
|
789
|
+
"done";
|
|
790
|
+
outcome.set(step.name, { ...(reported || {}), state });
|
|
672
791
|
} catch (err) {
|
|
673
792
|
// Report and carry on: a failed Slack install must not cost the customer
|
|
674
793
|
// the AWS connection they just finished.
|
|
@@ -687,7 +806,16 @@ export async function onboardCommand(
|
|
|
687
806
|
else if (result.state === "skipped") process.stdout.write(`${MARK.skip()} ${step.name} (skipped)\n`);
|
|
688
807
|
else process.stdout.write(`${MARK.fail()} ${step.name}: ${result.detail}\n`);
|
|
689
808
|
}
|
|
690
|
-
|
|
809
|
+
// The app is on screen by the time this prints, so telling them to go and open
|
|
810
|
+
// a website is a step backwards. The website still earns its mention -- it is
|
|
811
|
+
// the way in from a machine that has no app, and the account is the same one --
|
|
812
|
+
// but as an alternative, not as the next thing to do.
|
|
813
|
+
const appOpened = outcome.get("Desktop app")?.appOpened === true;
|
|
814
|
+
process.stdout.write(
|
|
815
|
+
appOpened
|
|
816
|
+
? `\nPreMan is open. You can also sign in at ${frontendUrl(args)}.\n`
|
|
817
|
+
: `\nOpen ${frontendUrl(args)} to see your logs and endpoints.\n`
|
|
818
|
+
);
|
|
691
819
|
}
|
|
692
820
|
|
|
693
821
|
export const INTEGRATIONS_HELP = `
|