@theholocron/cli 5.0.0-alpha.202 → 5.0.0-alpha.203
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 +8 -7
- package/dist/cli.mjs +28 -14
- package/dist/cli.mjs.map +1 -1
- package/dist/plugin/capabilities.d.mts +14 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -538,18 +538,19 @@ A `notifications` provider can declare a whole server in `holocron.config.ts`
|
|
|
538
538
|
onboarding, the bot application) and the CLI applies and checks it. The
|
|
539
539
|
commands and the config are provider-neutral; Discord is the first provider.
|
|
540
540
|
|
|
541
|
-
| Command | What it does
|
|
542
|
-
| ---------------------------------------------------------- |
|
|
543
|
-
| `holocron notifications setup` | Applies the layout; looks everything up by name first, so a re-run changes nothing that matches
|
|
544
|
-
| `holocron notifications channel add <name>` | Creates one channel (optionally with a webhook whose URL is stored as a secret)
|
|
545
|
-
| `holocron notifications link` | Links your account on the provider to your GitHub account by signing in with the provider (OAuth, nothing secret kept)
|
|
546
|
-
| `holocron notifications sync-roles` | Makes who holds a role match a GitHub team, through the links file (`--keep` only adds; `--provider` picks one)
|
|
547
|
-
| `holocron doctor notifications [--built-only] [--deliver]` |
|
|
541
|
+
| Command | What it does |
|
|
542
|
+
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
543
|
+
| `holocron notifications setup` | Applies the layout; looks everything up by name first, so a re-run changes nothing that matches |
|
|
544
|
+
| `holocron notifications channel add <name>` | Creates one channel (optionally with a webhook whose URL is stored as a secret) |
|
|
545
|
+
| `holocron notifications link` | Links your account on the provider to your GitHub account by signing in with the provider (OAuth, nothing secret kept) |
|
|
546
|
+
| `holocron notifications sync-roles` | Makes who holds a role match a GitHub team, through the links file (`--keep` only adds; `--provider` picks one) |
|
|
547
|
+
| `holocron doctor notifications [--built-only] [--deliver]` | Step by step: does the server match the layout, and do the secrets exist. A full run also leaves one `🧪 test:` post in the layout's `testChannel`; `--built-only` posts nothing; `--deliver` posts to every channel and removes each post |
|
|
548
548
|
|
|
549
549
|
Config shape added for role sync and sign-in (all under the provider's `layout`):
|
|
550
550
|
|
|
551
551
|
| Field | Meaning |
|
|
552
552
|
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
553
|
+
| `testChannel` | The channel (it must declare a webhook) the doctor posts its one self-removing test message to. |
|
|
553
554
|
| `roles[].team` | A GitHub team slug. The role follows that team: given to linked members, taken from linked people who left. Never touches holders nobody has linked. |
|
|
554
555
|
| `links.hostedUrl` | A hosted sign-in service (Sentinel). `link` asks `<hostedUrl>/api/link` and uses it when it answers `{ "service": "link" }`; otherwise it signs in locally. |
|
|
555
556
|
| `links.localPort` | The port of the local sign-in listener (default `53682`, bound to `127.0.0.1` only). |
|
package/dist/cli.mjs
CHANGED
|
@@ -3551,7 +3551,7 @@ const shown = (s) => sanitizeDetail(s);
|
|
|
3551
3551
|
const WELCOME_TITLE = "welcome screen and onboarding set";
|
|
3552
3552
|
const APP_TITLE = "bot description, tags, install link and URLs set";
|
|
3553
3553
|
const AVATAR_TITLE = "webhook names and avatars set";
|
|
3554
|
-
const DELIVERY_TITLE = "test message delivered through
|
|
3554
|
+
const DELIVERY_TITLE = "test message delivered through a webhook";
|
|
3555
3555
|
const SERVER_TITLE = "server verification, notifications and community set";
|
|
3556
3556
|
const ACCESS_TITLE = "channel and category access set";
|
|
3557
3557
|
const ROLE_APPEARANCE_TITLE = "role colour, hoist and permissions set";
|
|
@@ -3584,11 +3584,10 @@ async function runDoctorNotifications(input) {
|
|
|
3584
3584
|
problems: []
|
|
3585
3585
|
});
|
|
3586
3586
|
};
|
|
3587
|
-
let deliverSkipped = false;
|
|
3588
3587
|
const summarise = () => ({
|
|
3589
3588
|
pass: checks.filter((c) => c.status === "pass").length,
|
|
3590
3589
|
fail: checks.filter((c) => c.status === "fail").length,
|
|
3591
|
-
skip:
|
|
3590
|
+
skip: input.builtOnly ? PLANNED_CHECKS.length : 0
|
|
3592
3591
|
});
|
|
3593
3592
|
const finish = () => {
|
|
3594
3593
|
const summary = summarise();
|
|
@@ -3985,8 +3984,12 @@ async function runDoctorNotifications(input) {
|
|
|
3985
3984
|
cat,
|
|
3986
3985
|
channel
|
|
3987
3986
|
})));
|
|
3988
|
-
|
|
3989
|
-
|
|
3987
|
+
const testName = layout.testChannel;
|
|
3988
|
+
const mode = input.deliver ? "all" : !input.builtOnly && testName !== void 0 ? "test" : "none";
|
|
3989
|
+
const targets = mode === "all" ? withHook : mode === "test" ? withHook.filter(({ channel }) => same(channel.name, testName)).slice(0, 1) : [];
|
|
3990
|
+
let deliveryNote;
|
|
3991
|
+
if (mode === "none") deliveryNote = !input.builtOnly && testName === void 0 ? "the delivery check needs a test channel: name one in the layout's `testChannel` (a channel with a webhook)" : "the delivery check posts test messages, so --built-only skips it; add --deliver to post to every channel";
|
|
3992
|
+
else if (targets.length === 0) checks.push({
|
|
3990
3993
|
id: "post-works",
|
|
3991
3994
|
title: DELIVERY_TITLE,
|
|
3992
3995
|
status: "pass",
|
|
@@ -3997,22 +4000,24 @@ async function runDoctorNotifications(input) {
|
|
|
3997
4000
|
else if (!notifications.checkDelivery) failed("post-works", DELIVERY_TITLE, `the \`${notifications.providerName}\` provider cannot check delivery`);
|
|
3998
4001
|
else {
|
|
3999
4002
|
const problems = [];
|
|
4000
|
-
for (const { cat, channel } of
|
|
4003
|
+
for (const { cat, channel } of targets) {
|
|
4001
4004
|
const cfg = channel.webhook === true ? {} : channel.webhook;
|
|
4005
|
+
const keep = mode === "test";
|
|
4002
4006
|
try {
|
|
4003
4007
|
const result = await notifications.checkDelivery({
|
|
4004
4008
|
channel: channel.name,
|
|
4005
4009
|
name: cfg.name ?? channel.name,
|
|
4006
4010
|
category: cat.name,
|
|
4007
|
-
text: `holocron doctor
|
|
4011
|
+
text: keep ? "🧪 test: `holocron doctor notifications` check for {channel}. This post was a test of the delivery system; it is safe to delete." : "🧪 test: `holocron doctor notifications --deliver` check for {channel}. This post was a test of the delivery system; it removes itself.",
|
|
4012
|
+
...keep ? { keep: true } : {}
|
|
4008
4013
|
});
|
|
4009
4014
|
for (const problem of result.problems) problems.push(`#${shown(channel.name)}: ${shown(problem)}`);
|
|
4010
|
-
if (result.problems.length === 0 && !(result.posted && result.readBack && result.removed)) problems.push(`#${shown(channel.name)}: the check did not complete`);
|
|
4015
|
+
if (result.problems.length === 0 && !(result.posted && result.readBack && (keep ? result.kept : result.removed))) problems.push(`#${shown(channel.name)}: the check did not complete`);
|
|
4011
4016
|
} catch (err) {
|
|
4012
4017
|
problems.push(`#${shown(channel.name)}: ${shown(describeProviderError(err))}`);
|
|
4013
4018
|
}
|
|
4014
4019
|
}
|
|
4015
|
-
add("post-works", DELIVERY_TITLE, problems, `${
|
|
4020
|
+
add("post-works", DELIVERY_TITLE, problems, mode === "all" ? `${targets.length} of ${targets.length} delivered` : `delivered through #${shown(targets[0].channel.name)} (left there)`);
|
|
4016
4021
|
}
|
|
4017
4022
|
if (!input.builtOnly) for (const planned of PLANNED_CHECKS) checks.push({
|
|
4018
4023
|
id: planned.id,
|
|
@@ -4027,7 +4032,10 @@ async function runDoctorNotifications(input) {
|
|
|
4027
4032
|
print(statusRow(check.status === "pass" ? "ok" : "fail", [check.title.padEnd(width), check.detail]));
|
|
4028
4033
|
for (const problem of check.problems) print(` - ${problem}`);
|
|
4029
4034
|
}
|
|
4030
|
-
if (
|
|
4035
|
+
if (deliveryNote) {
|
|
4036
|
+
print("");
|
|
4037
|
+
print(` ${style.skip(`note: ${deliveryNote}`)}`);
|
|
4038
|
+
}
|
|
4031
4039
|
const summary = summarise();
|
|
4032
4040
|
print("");
|
|
4033
4041
|
print(style.header(`${summary.pass} ok, ${summary.fail} fail, ${summary.skip} skipped`));
|
|
@@ -4700,12 +4708,18 @@ function accessRoleProblem(layout) {
|
|
|
4700
4708
|
function serverLayoutProblem(layout) {
|
|
4701
4709
|
const counts = /* @__PURE__ */ new Map();
|
|
4702
4710
|
for (const name of (layout.categories ?? []).flatMap((c) => (c.channels ?? []).map((ch) => ch.name.toLowerCase()))) counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
4711
|
+
const declared = (layout.categories ?? []).flatMap((c) => c.channels ?? []);
|
|
4703
4712
|
const roles = new Set((layout.roles ?? []).map((r) => r.name.toLowerCase()));
|
|
4704
4713
|
const channel = (where, name) => {
|
|
4705
4714
|
const count = counts.get(name.toLowerCase()) ?? 0;
|
|
4706
4715
|
if (count === 0) return `${where} names the channel \`${name}\`, which is not in the layout's channels`;
|
|
4707
4716
|
if (count > 1) return `${where} names \`${name}\`, but more than one channel in the layout has that name`;
|
|
4708
4717
|
};
|
|
4718
|
+
if (layout.testChannel !== void 0) {
|
|
4719
|
+
const problem = channel("`testChannel`", layout.testChannel);
|
|
4720
|
+
if (problem) return problem;
|
|
4721
|
+
if (!declared.some((ch) => ch.name.toLowerCase() === layout.testChannel.toLowerCase() && ch.webhook)) return `\`testChannel\` names \`${layout.testChannel}\`, which declares no webhook`;
|
|
4722
|
+
}
|
|
4709
4723
|
const roleTeams = roleTeamProblem(layout);
|
|
4710
4724
|
if (roleTeams) return roleTeams;
|
|
4711
4725
|
const emptyTeam = (layout.roles ?? []).find((r) => r.team !== void 0 && r.team.trim() === "");
|
|
@@ -11114,14 +11128,14 @@ const COMMAND_REGISTRY = [
|
|
|
11114
11128
|
},
|
|
11115
11129
|
{
|
|
11116
11130
|
name: "doctor notifications",
|
|
11117
|
-
description: "Check the layout against the server and the secrets store, step by step (
|
|
11131
|
+
description: "Check the layout against the server and the secrets store, step by step (a full run leaves one test post in the test channel; --built-only posts nothing unless --deliver)",
|
|
11118
11132
|
positionals: [],
|
|
11119
11133
|
options: [{
|
|
11120
11134
|
key: "deliver",
|
|
11121
11135
|
flag: "--deliver",
|
|
11122
11136
|
type: "confirm",
|
|
11123
11137
|
default: false,
|
|
11124
|
-
message: "
|
|
11138
|
+
message: "Post a test message through every channel's webhook (each removes itself)?"
|
|
11125
11139
|
}],
|
|
11126
11140
|
supportsDryRun: false
|
|
11127
11141
|
},
|
|
@@ -11982,7 +11996,7 @@ try {
|
|
|
11982
11996
|
}).command("doctor", "Load the config and run a smoke check against every provider", (y) => y.option("repo", {
|
|
11983
11997
|
type: "string",
|
|
11984
11998
|
describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
|
|
11985
|
-
}).command("notifications", "Check the layout against the server and the secrets store, step by step (
|
|
11999
|
+
}).command("notifications", "Check the layout against the server and the secrets store, step by step (a full run leaves one test post in the test channel; --built-only posts nothing unless --deliver)", (y) => y.option("scope", {
|
|
11986
12000
|
type: "string",
|
|
11987
12001
|
describe: "Where the layout's secrets live: \"repo\", \"env=<name>\" or \"org=<name>\" (default: the org)"
|
|
11988
12002
|
}).option("provider", {
|
|
@@ -11995,7 +12009,7 @@ try {
|
|
|
11995
12009
|
}).option("deliver", {
|
|
11996
12010
|
type: "boolean",
|
|
11997
12011
|
default: false,
|
|
11998
|
-
describe: "
|
|
12012
|
+
describe: "Post a test message through every channel's webhook, read it back and remove it (works with --built-only)"
|
|
11999
12013
|
}), async (argv) => {
|
|
12000
12014
|
const tokens = tokenContext(argv.token);
|
|
12001
12015
|
if (!tokens) return;
|