@theholocron/cli 5.0.0-alpha.194 → 5.0.0-alpha.196

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/dist/cli.mjs CHANGED
@@ -2898,11 +2898,6 @@ async function runNotificationsChannelAdd(input) {
2898
2898
  */
2899
2899
  /** Features not built yet, in the order of the table in holocron#1098. Move an entry into a real check when it lands. */
2900
2900
  const PLANNED_CHECKS = [
2901
- {
2902
- step: 11,
2903
- id: "welcome",
2904
- title: "welcome screen and onboarding set"
2905
- },
2906
2901
  {
2907
2902
  step: 12,
2908
2903
  id: "webhook-appearance",
@@ -2932,8 +2927,21 @@ const PLANNED_CHECKS = [
2932
2927
  const same = (a, b) => a.toLowerCase() === b.toLowerCase();
2933
2928
  /** `everyone`, or the roles sorted and lower-cased, so order and case do not matter when comparing. */
2934
2929
  const accessKey = (v) => v === "everyone" ? "everyone" : JSON.stringify([...new Set(v.map((r) => r.toLowerCase()))].sort());
2930
+ /** The prompts of an onboarding in a form two of them can be compared in: the order of the channels and roles an answer gives, and case, do not matter. */
2931
+ const promptKey = (p) => JSON.stringify([
2932
+ p.singleSelect ?? false,
2933
+ p.required ?? false,
2934
+ (p.options ?? []).map((o) => [
2935
+ o.title,
2936
+ o.description ?? "",
2937
+ o.emoji ?? "",
2938
+ [...new Set((o.channels ?? []).map((c) => c.toLowerCase()))].sort(),
2939
+ [...new Set((o.roles ?? []).map((r) => r.toLowerCase()))].sort()
2940
+ ])
2941
+ ]);
2935
2942
  const accessText = (v) => v === "everyone" ? "everyone" : v.length === 0 ? "nobody (bots and webhooks only)" : v.join(", ");
2936
2943
  const shown = (s) => sanitizeDetail(s);
2944
+ const WELCOME_TITLE = "welcome screen and onboarding set";
2937
2945
  const SERVER_TITLE = "server verification, notifications and community set";
2938
2946
  const ACCESS_TITLE = "channel and category access set";
2939
2947
  const ROLE_APPEARANCE_TITLE = "role colour, hoist and permissions set";
@@ -3055,7 +3063,8 @@ async function runDoctorNotifications(input) {
3055
3063
  ["roles", "roles exist and are mentionable"],
3056
3064
  ["role-appearance", ROLE_APPEARANCE_TITLE],
3057
3065
  ["access", ACCESS_TITLE],
3058
- ["server-settings", SERVER_TITLE]
3066
+ ["server-settings", SERVER_TITLE],
3067
+ ["welcome-onboarding", WELCOME_TITLE]
3059
3068
  ]) failed(id, title, "not checked: could not read the server");
3060
3069
  } else {
3061
3070
  const inspected = found;
@@ -3186,6 +3195,57 @@ async function runDoctorNotifications(input) {
3186
3195
  }
3187
3196
  const serverFields = wantServer ? Object.values(wantServer).filter((v) => v !== void 0).length : 0;
3188
3197
  add("server-settings", SERVER_TITLE, serverProblems, serverFields === 0 ? "none declared" : `${serverFields} of ${serverFields} as declared`);
3198
+ const wantWelcome = layout.welcome;
3199
+ const wantOnboarding = layout.onboarding;
3200
+ const welcomeProblems = [];
3201
+ if (wantWelcome) {
3202
+ if (inspected.welcomeError) welcomeProblems.push(`not checked: could not read the welcome screen (${shown(inspected.welcomeError)})`);
3203
+ else if (!inspected.welcome) welcomeProblems.push("the provider did not report the welcome screen");
3204
+ else {
3205
+ const have = inspected.welcome;
3206
+ const enabled = wantWelcome.enabled ?? true;
3207
+ if (have.enabled !== enabled) welcomeProblems.push(`welcome screen is ${have.enabled ? "on" : "off"} but the layout says ${enabled ? "on" : "off"}`);
3208
+ if (have.description !== wantWelcome.description) welcomeProblems.push(`welcome description is "${shown(have.description)}" but the layout says "${shown(wantWelcome.description)}"`);
3209
+ const key = (list) => JSON.stringify(list.map((c) => [
3210
+ c.channel.toLowerCase(),
3211
+ c.description,
3212
+ c.emoji ?? ""
3213
+ ]));
3214
+ if (key(have.channels) !== key(wantWelcome.channels)) {
3215
+ const names = (list) => list.length === 0 ? "none" : list.map((c) => shown(c.channel)).join(", ");
3216
+ welcomeProblems.push(`welcome channels are ${names(have.channels)} but the layout says ${names(wantWelcome.channels)} (or their descriptions or emoji differ)`);
3217
+ }
3218
+ }
3219
+ }
3220
+ const onboardingProblems = [];
3221
+ if (wantOnboarding) {
3222
+ if (inspected.onboardingError) onboardingProblems.push(`not checked: could not read onboarding (${shown(inspected.onboardingError)})`);
3223
+ else if (!inspected.onboarding) onboardingProblems.push("the provider did not report onboarding");
3224
+ else {
3225
+ const have = inspected.onboarding;
3226
+ if (wantOnboarding.enabled !== void 0 && have.enabled !== wantOnboarding.enabled) onboardingProblems.push(`onboarding is ${have.enabled ? "on" : "off"} but the layout says ${wantOnboarding.enabled ? "on" : "off"}`);
3227
+ if (wantOnboarding.defaultChannels !== void 0) {
3228
+ const set = (list) => [...new Set(list.map((c) => c.toLowerCase()))].sort().join(",");
3229
+ if (set(have.defaultChannels ?? []) !== set(wantOnboarding.defaultChannels)) onboardingProblems.push(`default channels are ${(have.defaultChannels ?? []).map(shown).join(", ") || "none"} but the layout says ${wantOnboarding.defaultChannels.map(shown).join(", ") || "none"}`);
3230
+ }
3231
+ if (wantOnboarding.prompts !== void 0) {
3232
+ const found = new Map((have.prompts ?? []).map((p) => [p.title, p]));
3233
+ for (const p of wantOnboarding.prompts) {
3234
+ const actual = found.get(p.title);
3235
+ if (!actual) onboardingProblems.push(`question "${shown(p.title)}" is missing`);
3236
+ else if (promptKey(actual) !== promptKey(p)) onboardingProblems.push(`question "${shown(p.title)}" differs from the layout`);
3237
+ found.delete(p.title);
3238
+ }
3239
+ for (const title of found.keys()) onboardingProblems.push(`question "${shown(title)}" is not in the layout`);
3240
+ const order = (titles) => titles.join("\0");
3241
+ const wantedTitles = wantOnboarding.prompts.map((p) => p.title);
3242
+ const haveTitles = (have.prompts ?? []).map((p) => p.title).filter((t) => wantedTitles.includes(t));
3243
+ if (haveTitles.length === wantedTitles.length && order(haveTitles) !== order(wantedTitles)) onboardingProblems.push("the questions are in a different order from the layout");
3244
+ }
3245
+ }
3246
+ }
3247
+ const welcomeParts = (wantWelcome ? 1 : 0) + (wantOnboarding ? 1 : 0);
3248
+ add("welcome-onboarding", WELCOME_TITLE, [...welcomeProblems, ...onboardingProblems], welcomeParts === 0 ? "none declared" : `${welcomeParts} of ${welcomeParts} as declared`);
3189
3249
  }
3190
3250
  const wanted = [...declaredChannels.filter(({ channel }) => channel.webhook).map(({ channel }) => channel.webhook !== true && channel.webhook && channel.webhook.secret ? channel.webhook.secret : defaultSecretName(channel.name)), ...(layout.roles ?? []).flatMap((r) => r.secret ? [r.secret] : [])];
3191
3251
  if (wanted.length === 0) checks.push({
@@ -3884,24 +3944,49 @@ function accessRoleProblem(layout) {
3884
3944
  }
3885
3945
  }
3886
3946
  }
3887
- /** The first problem with the layout's `server` section that needs no knowledge of the provider, or undefined. */
3947
+ /**
3948
+ * The first problem with the layout's `server`, `welcome` and `onboarding` sections that needs no knowledge of
3949
+ * the provider, or undefined: every channel or role they name must be in the layout, and a channel name must
3950
+ * not be used twice (it could mean either, so they would pick one at random).
3951
+ */
3888
3952
  function serverLayoutProblem(layout) {
3889
- const server = layout.server;
3890
- if (!server) return void 0;
3891
3953
  const counts = /* @__PURE__ */ new Map();
3892
3954
  for (const name of (layout.categories ?? []).flatMap((c) => (c.channels ?? []).map((ch) => ch.name.toLowerCase()))) counts.set(name, (counts.get(name) ?? 0) + 1);
3893
- for (const field of [
3894
- "systemChannel",
3895
- "rulesChannel",
3896
- "updatesChannel"
3897
- ]) {
3898
- const name = server[field];
3899
- if (name === void 0) continue;
3955
+ const roles = new Set((layout.roles ?? []).map((r) => r.name.toLowerCase()));
3956
+ const channel = (where, name) => {
3900
3957
  const count = counts.get(name.toLowerCase()) ?? 0;
3901
- if (count === 0) return `server: \`${field}\` names the channel \`${name}\`, which is not in the layout's channels`;
3902
- if (count > 1) return `server: \`${field}\` names \`${name}\`, but more than one channel in the layout has that name`;
3958
+ if (count === 0) return `${where} names the channel \`${name}\`, which is not in the layout's channels`;
3959
+ if (count > 1) return `${where} names \`${name}\`, but more than one channel in the layout has that name`;
3960
+ };
3961
+ const server = layout.server;
3962
+ if (server) {
3963
+ for (const field of [
3964
+ "systemChannel",
3965
+ "rulesChannel",
3966
+ "updatesChannel"
3967
+ ]) {
3968
+ const name = server[field];
3969
+ const problem = name === void 0 ? void 0 : channel(`server: \`${field}\``, name);
3970
+ if (problem) return problem;
3971
+ }
3972
+ if (server.community === true && (!server.rulesChannel || !server.updatesChannel)) return "server: `community: true` needs both `rulesChannel` and `updatesChannel`";
3973
+ }
3974
+ for (const entry of layout.welcome?.channels ?? []) {
3975
+ const problem = channel("welcome:", entry.channel);
3976
+ if (problem) return problem;
3977
+ }
3978
+ const onboarding = layout.onboarding;
3979
+ for (const name of onboarding?.defaultChannels ?? []) {
3980
+ const problem = channel("onboarding: `defaultChannels`", name);
3981
+ if (problem) return problem;
3982
+ }
3983
+ for (const prompt of onboarding?.prompts ?? []) for (const option of prompt.options) {
3984
+ for (const name of option.channels ?? []) {
3985
+ const problem = channel(`onboarding: the option \`${option.title}\``, name);
3986
+ if (problem) return problem;
3987
+ }
3988
+ for (const role of option.roles ?? []) if (!roles.has(role.toLowerCase())) return `onboarding: the option \`${option.title}\` gives the role \`${role}\`, which is not in the layout's roles`;
3903
3989
  }
3904
- if (server.community === true && (!server.rulesChannel || !server.updatesChannel)) return "server: `community: true` needs both `rulesChannel` and `updatesChannel`";
3905
3990
  }
3906
3991
  async function runNotificationsSetup(input) {
3907
3992
  const print = input.print ?? ((line) => console.log(line));
@@ -3961,6 +4046,8 @@ async function runNotificationsSetup(input) {
3961
4046
  const serverProblem = serverLayoutProblem(layout);
3962
4047
  if (serverProblem) return refuse(serverProblem);
3963
4048
  if (layout.server && !notifications.ensureServer) return refuse(`the layout declares \`server\` settings, but the \`${notifications.providerName}\` provider cannot manage them`);
4049
+ if (layout.welcome && !notifications.ensureWelcome) return refuse(`the layout declares a \`welcome\` screen, but the \`${notifications.providerName}\` provider cannot manage one`);
4050
+ if (layout.onboarding && !notifications.ensureOnboarding) return refuse(`the layout declares \`onboarding\`, but the \`${notifications.providerName}\` provider cannot manage it`);
3964
4051
  const needsCategories = (layout.categories ?? []).find((c) => c.access);
3965
4052
  if (needsCategories && !notifications.ensureCategory) return refuse(`category \`${needsCategories.name}\` declares \`access\`, but the \`${notifications.providerName}\` provider cannot manage categories`);
3966
4053
  const providerProblem = notifications.validateLayout?.(layout);
@@ -4073,19 +4160,25 @@ async function runNotificationsSetup(input) {
4073
4160
  });
4074
4161
  });
4075
4162
  }
4076
- if (layout.server) {
4163
+ if (layout.server || layout.welcome || layout.onboarding) {
4077
4164
  print(`\n${style.header("Server")}`);
4078
- await attempt("server settings", async () => {
4079
- if (dryRun) {
4080
- print(` ${style.skip("would ensure server settings")}`);
4081
- tally.skip++;
4082
- return;
4083
- }
4084
- const result = await notifications.ensureServer(layout.server);
4085
- const changed = result.updated.length > 0;
4086
- print(` ${(changed ? style.success : style.skip)(`${changed ? "updated" : "exists "} server settings${changed ? `: ${result.updated.join(", ")}` : ""}`)}`);
4087
- made(changed);
4088
- });
4165
+ const apply = async (label, declared, run) => {
4166
+ if (!declared) return;
4167
+ await attempt(label, async () => {
4168
+ if (dryRun) {
4169
+ print(` ${style.skip(`would ensure ${label}`)}`);
4170
+ tally.skip++;
4171
+ return;
4172
+ }
4173
+ const result = await run();
4174
+ const changed = result.updated.length > 0;
4175
+ print(` ${(changed ? style.success : style.skip)(`${changed ? "updated" : "exists "} ${label}${changed ? `: ${result.updated.join(", ")}` : ""}`)}`);
4176
+ made(changed);
4177
+ });
4178
+ };
4179
+ await apply("server settings", !!layout.server, () => notifications.ensureServer(layout.server));
4180
+ await apply("welcome screen", !!layout.welcome, () => notifications.ensureWelcome(layout.welcome));
4181
+ await apply("onboarding", !!layout.onboarding, () => notifications.ensureOnboarding(layout.onboarding));
4089
4182
  }
4090
4183
  if (report.failures.length > 0) report.status = "fail";
4091
4184
  else if (dryRun) report.status = "dry-run";