@theholocron/cli 5.0.0-alpha.196 → 5.0.0-alpha.198
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 +155 -19
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/plugin/capabilities.d.mts +69 -1
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -24,8 +24,8 @@ import search from "@inquirer/search";
|
|
|
24
24
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
25
25
|
import ora from "ora";
|
|
26
26
|
import { findRootSync, isWorkspaceRoot, walkFilesSync } from "@theholocron/fs-utils";
|
|
27
|
-
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
28
27
|
import { access, copyFile, mkdir, readFile, readdir, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
28
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
29
29
|
import { stringifyJson } from "@theholocron/json-utils";
|
|
30
30
|
import { generateReadme } from "@theholocron/components-doc/markdown";
|
|
31
31
|
import { getClients, getConfigs, getDocs, getPlugins, getSkills, getThemes, getUtils } from "@theholocron/registry-doc";
|
|
@@ -2634,6 +2634,59 @@ function mk(capability, provider, status, message, logMessage) {
|
|
|
2634
2634
|
};
|
|
2635
2635
|
}
|
|
2636
2636
|
//#endregion
|
|
2637
|
+
//#region src/commands/notifications-channel-add/avatar.ts
|
|
2638
|
+
/**
|
|
2639
|
+
* Read a webhook avatar named in a layout: a file path (from the directory the CLI runs in) or an
|
|
2640
|
+
* `https://` URL. Returns it as a `data:` URI, which is how providers take an image.
|
|
2641
|
+
*
|
|
2642
|
+
* The type comes from the first bytes, not from the name or the response header, so what is sent is
|
|
2643
|
+
* the image it claims to be.
|
|
2644
|
+
*/
|
|
2645
|
+
/** Providers cap avatars well below this; it only keeps a wrong file from filling memory. */
|
|
2646
|
+
const MAX_AVATAR_BYTES = 8388608;
|
|
2647
|
+
const FETCH_TIMEOUT_MS$2 = 15e3;
|
|
2648
|
+
const SIGNATURES = [
|
|
2649
|
+
["image/png", [
|
|
2650
|
+
137,
|
|
2651
|
+
80,
|
|
2652
|
+
78,
|
|
2653
|
+
71,
|
|
2654
|
+
13,
|
|
2655
|
+
10,
|
|
2656
|
+
26,
|
|
2657
|
+
10
|
|
2658
|
+
]],
|
|
2659
|
+
["image/jpeg", [
|
|
2660
|
+
255,
|
|
2661
|
+
216,
|
|
2662
|
+
255
|
|
2663
|
+
]],
|
|
2664
|
+
["image/gif", [
|
|
2665
|
+
71,
|
|
2666
|
+
73,
|
|
2667
|
+
70,
|
|
2668
|
+
56
|
|
2669
|
+
]]
|
|
2670
|
+
];
|
|
2671
|
+
const tooBig = () => /* @__PURE__ */ new Error(`it is larger than ${MAX_AVATAR_BYTES / 1024 / 1024} MB`);
|
|
2672
|
+
async function loadAvatar(source, baseDir = process.cwd(), fetchImpl = fetch) {
|
|
2673
|
+
let bytes;
|
|
2674
|
+
if (/^https:\/\//i.test(source)) {
|
|
2675
|
+
const res = await fetchImpl(source, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS$2) });
|
|
2676
|
+
if (!res.ok) throw new Error(`fetching it answered ${res.status}`);
|
|
2677
|
+
if (Number(res.headers.get("content-length")) > 8388608) throw tooBig();
|
|
2678
|
+
bytes = Buffer.from(await res.arrayBuffer());
|
|
2679
|
+
} else {
|
|
2680
|
+
const path = resolve(baseDir, source);
|
|
2681
|
+
if ((await stat(path)).size > 8388608) throw tooBig();
|
|
2682
|
+
bytes = await readFile(path);
|
|
2683
|
+
}
|
|
2684
|
+
if (bytes.length > 8388608) throw tooBig();
|
|
2685
|
+
const type = SIGNATURES.find(([, sig]) => sig.every((b, i) => bytes[i] === b))?.[0];
|
|
2686
|
+
if (!type) throw new Error("it is not a PNG, JPEG or GIF image, which are the formats providers accept");
|
|
2687
|
+
return `data:${type};base64,${bytes.toString("base64")}`;
|
|
2688
|
+
}
|
|
2689
|
+
//#endregion
|
|
2637
2690
|
//#region src/commands/notifications-channel-add/notifications-channel-add.ts
|
|
2638
2691
|
/**
|
|
2639
2692
|
* `holocron notifications channel add` — create one channel (and, with
|
|
@@ -2784,8 +2837,10 @@ async function provisionChannel(deps, spec) {
|
|
|
2784
2837
|
const hook = await notifications.ensureWebhook({
|
|
2785
2838
|
channel: spec.name,
|
|
2786
2839
|
name: webhookName,
|
|
2787
|
-
...spec.category ? { category: spec.category } : {}
|
|
2840
|
+
...spec.category ? { category: spec.category } : {},
|
|
2841
|
+
...webhookCfg.avatar ? { avatar: webhookCfg.avatar } : {}
|
|
2788
2842
|
});
|
|
2843
|
+
const avatarSet = hook.updated?.includes("avatar") ?? false;
|
|
2789
2844
|
if (hook.url) {
|
|
2790
2845
|
try {
|
|
2791
2846
|
await secrets.setSecret(scope, secretName, hook.url);
|
|
@@ -2798,7 +2853,8 @@ async function provisionChannel(deps, spec) {
|
|
|
2798
2853
|
name: webhookName,
|
|
2799
2854
|
created: hook.created,
|
|
2800
2855
|
secret: secretName,
|
|
2801
|
-
stored: true
|
|
2856
|
+
stored: true,
|
|
2857
|
+
...avatarSet ? { updated: ["avatar"] } : {}
|
|
2802
2858
|
};
|
|
2803
2859
|
} else {
|
|
2804
2860
|
const note = `webhook ${webhookName} already exists and its URL cannot be read back — delete it in the provider and re-run, or set ${secretName} yourself`;
|
|
@@ -2808,9 +2864,15 @@ async function provisionChannel(deps, spec) {
|
|
|
2808
2864
|
created: hook.created,
|
|
2809
2865
|
secret: secretName,
|
|
2810
2866
|
stored: false,
|
|
2811
|
-
note
|
|
2867
|
+
note,
|
|
2868
|
+
...avatarSet ? { updated: ["avatar"] } : {}
|
|
2812
2869
|
};
|
|
2813
2870
|
}
|
|
2871
|
+
if (avatarSet) {
|
|
2872
|
+
print(` ${style.success(`set avatar on webhook ${webhookName}`)}`);
|
|
2873
|
+
if (tally) tally.ok++;
|
|
2874
|
+
}
|
|
2875
|
+
if (hook.avatarError) throw new Error(`webhook ${webhookName} is ready but setting its avatar failed (${hook.avatarError})`);
|
|
2814
2876
|
return outcome;
|
|
2815
2877
|
}
|
|
2816
2878
|
async function runNotificationsChannelAdd(input) {
|
|
@@ -2898,16 +2960,6 @@ async function runNotificationsChannelAdd(input) {
|
|
|
2898
2960
|
*/
|
|
2899
2961
|
/** Features not built yet, in the order of the table in holocron#1098. Move an entry into a real check when it lands. */
|
|
2900
2962
|
const PLANNED_CHECKS = [
|
|
2901
|
-
{
|
|
2902
|
-
step: 12,
|
|
2903
|
-
id: "webhook-appearance",
|
|
2904
|
-
title: "webhook names and avatars set"
|
|
2905
|
-
},
|
|
2906
|
-
{
|
|
2907
|
-
step: 13,
|
|
2908
|
-
id: "app-settings",
|
|
2909
|
-
title: "bot description, tags, install link and URLs set"
|
|
2910
|
-
},
|
|
2911
2963
|
{
|
|
2912
2964
|
step: 14,
|
|
2913
2965
|
id: "role-assignment",
|
|
@@ -2942,6 +2994,8 @@ const promptKey = (p) => JSON.stringify([
|
|
|
2942
2994
|
const accessText = (v) => v === "everyone" ? "everyone" : v.length === 0 ? "nobody (bots and webhooks only)" : v.join(", ");
|
|
2943
2995
|
const shown = (s) => sanitizeDetail(s);
|
|
2944
2996
|
const WELCOME_TITLE = "welcome screen and onboarding set";
|
|
2997
|
+
const APP_TITLE = "bot description, tags, install link and URLs set";
|
|
2998
|
+
const AVATAR_TITLE = "webhook names and avatars set";
|
|
2945
2999
|
const SERVER_TITLE = "server verification, notifications and community set";
|
|
2946
3000
|
const ACCESS_TITLE = "channel and category access set";
|
|
2947
3001
|
const ROLE_APPEARANCE_TITLE = "role colour, hoist and permissions set";
|
|
@@ -3060,11 +3114,13 @@ async function runDoctorNotifications(input) {
|
|
|
3060
3114
|
["categories", "categories exist"],
|
|
3061
3115
|
["channels", "channels in their category, with topics"],
|
|
3062
3116
|
["webhooks", "webhooks on their channels"],
|
|
3117
|
+
["webhook-appearance", AVATAR_TITLE],
|
|
3063
3118
|
["roles", "roles exist and are mentionable"],
|
|
3064
3119
|
["role-appearance", ROLE_APPEARANCE_TITLE],
|
|
3065
3120
|
["access", ACCESS_TITLE],
|
|
3066
3121
|
["server-settings", SERVER_TITLE],
|
|
3067
|
-
["welcome-onboarding", WELCOME_TITLE]
|
|
3122
|
+
["welcome-onboarding", WELCOME_TITLE],
|
|
3123
|
+
["app-settings", APP_TITLE]
|
|
3068
3124
|
]) failed(id, title, "not checked: could not read the server");
|
|
3069
3125
|
} else {
|
|
3070
3126
|
const inspected = found;
|
|
@@ -3095,6 +3151,17 @@ async function runDoctorNotifications(input) {
|
|
|
3095
3151
|
else if (!match.webhooks.some((w) => same(w, name))) webhookProblems.push(`#${shown(channel.name)}: no webhook named ${shown(name)}`);
|
|
3096
3152
|
}
|
|
3097
3153
|
add("webhooks", "webhooks on their channels", webhookProblems, withWebhook.length === 0 ? "none declared" : `${withWebhook.length} of ${withWebhook.length} exist`);
|
|
3154
|
+
const avatarOf = (webhook) => (typeof webhook === "object" && webhook ? webhook.avatar : void 0) ?? layout.webhookAvatar;
|
|
3155
|
+
const wantAvatar = declaredChannels.filter(({ channel }) => channel.webhook && avatarOf(channel.webhook));
|
|
3156
|
+
const avatarProblems = [];
|
|
3157
|
+
for (const { cat, channel } of wantAvatar) {
|
|
3158
|
+
const name = (channel.webhook === true ? {} : channel.webhook).name ?? channel.name;
|
|
3159
|
+
const { match } = pick(channel.name, cat);
|
|
3160
|
+
if (!match) avatarProblems.push(`#${shown(channel.name)}: no channel to hold the webhook`);
|
|
3161
|
+
else if (match.webhooksError) avatarProblems.push(`#${shown(channel.name)}: could not list its webhooks (${shown(match.webhooksError)})`);
|
|
3162
|
+
else if (match.webhookAvatars && !match.webhookAvatars.some((w) => same(w, name))) avatarProblems.push(`#${shown(channel.name)}: webhook ${shown(name)} has no avatar`);
|
|
3163
|
+
}
|
|
3164
|
+
add("webhook-appearance", AVATAR_TITLE, avatarProblems, wantAvatar.length === 0 ? "none declared" : `${wantAvatar.length} of ${wantAvatar.length} set`);
|
|
3098
3165
|
const roles = layout.roles ?? [];
|
|
3099
3166
|
const roleProblems = [];
|
|
3100
3167
|
if (inspected.rolesError) roleProblems.push(`could not read the roles (${shown(inspected.rolesError)})`);
|
|
@@ -3128,8 +3195,21 @@ async function runDoctorNotifications(input) {
|
|
|
3128
3195
|
}
|
|
3129
3196
|
}
|
|
3130
3197
|
}
|
|
3131
|
-
const
|
|
3132
|
-
|
|
3198
|
+
const everyone = layout.everyone;
|
|
3199
|
+
const everyoneProblems = [];
|
|
3200
|
+
if (everyone) {
|
|
3201
|
+
if (inspected.everyoneError) everyoneProblems.push(`@everyone: not checked, could not read its permissions (${shown(inspected.everyoneError)})`);
|
|
3202
|
+
else if (!inspected.everyone) everyoneProblems.push("@everyone: the provider did not report its permissions");
|
|
3203
|
+
else {
|
|
3204
|
+
const have = new Set(inspected.everyone.permissions.map((n) => n.toLowerCase()));
|
|
3205
|
+
const lacking = (everyone.allow ?? []).filter((n) => !have.has(n.toLowerCase()));
|
|
3206
|
+
const keeping = (everyone.deny ?? []).filter((n) => have.has(n.toLowerCase()));
|
|
3207
|
+
if (lacking.length > 0) everyoneProblems.push(`@everyone: missing permissions ${lacking.map(shown).join(", ")}`);
|
|
3208
|
+
if (keeping.length > 0) everyoneProblems.push(`@everyone: still has permissions the layout takes away: ${keeping.map(shown).join(", ")}`);
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
const styledCount = roles.filter((r) => r.color !== void 0 || r.hoist !== void 0 || r.permissions !== void 0).length + (everyone ? 1 : 0);
|
|
3212
|
+
add("role-appearance", ROLE_APPEARANCE_TITLE, inspected.rolesError ? [`not checked: could not read the roles`, ...everyoneProblems] : [...appearance, ...everyoneProblems], styledCount === 0 ? "none declared" : `${styledCount} of ${styledCount} as declared`);
|
|
3133
3213
|
const accessProblems = [];
|
|
3134
3214
|
const compare = (label, want, have) => {
|
|
3135
3215
|
if (!have) {
|
|
@@ -3246,6 +3326,35 @@ async function runDoctorNotifications(input) {
|
|
|
3246
3326
|
}
|
|
3247
3327
|
const welcomeParts = (wantWelcome ? 1 : 0) + (wantOnboarding ? 1 : 0);
|
|
3248
3328
|
add("welcome-onboarding", WELCOME_TITLE, [...welcomeProblems, ...onboardingProblems], welcomeParts === 0 ? "none declared" : `${welcomeParts} of ${welcomeParts} as declared`);
|
|
3329
|
+
const wantApp = layout.application;
|
|
3330
|
+
const appProblems = [];
|
|
3331
|
+
if (wantApp) {
|
|
3332
|
+
if (inspected.applicationError) appProblems.push(`not checked: could not read the application (${shown(inspected.applicationError)})`);
|
|
3333
|
+
else if (!inspected.application) appProblems.push("the provider did not report the application");
|
|
3334
|
+
else {
|
|
3335
|
+
const have = inspected.application;
|
|
3336
|
+
const set = (list) => JSON.stringify([...new Set(list.map((n) => n.toLowerCase()))].sort());
|
|
3337
|
+
const exact = (list) => JSON.stringify([...new Set(list)].sort());
|
|
3338
|
+
if (wantApp.description !== void 0 && (have.description ?? "") !== wantApp.description) appProblems.push("description differs from the layout");
|
|
3339
|
+
if (wantApp.tags !== void 0 && set(have.tags ?? []) !== set(wantApp.tags)) appProblems.push(`tags are ${(have.tags ?? []).map(shown).join(", ") || "none"} but the layout says ${wantApp.tags.map(shown).join(", ") || "none"}`);
|
|
3340
|
+
if (wantApp.install !== void 0) {
|
|
3341
|
+
if (!have.install) appProblems.push("no install link is set");
|
|
3342
|
+
else {
|
|
3343
|
+
if (exact(have.install.scopes) !== exact(wantApp.install.scopes)) appProblems.push(`install scopes are ${have.install.scopes.map(shown).join(", ")} but the layout says ${wantApp.install.scopes.map(shown).join(", ")}`);
|
|
3344
|
+
if (set(have.install.permissions) !== set(wantApp.install.permissions)) appProblems.push("install permissions differ from the layout");
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
for (const [field, label] of [
|
|
3348
|
+
["termsUrl", "terms URL"],
|
|
3349
|
+
["privacyUrl", "privacy URL"],
|
|
3350
|
+
["roleConnectionsUrl", "role-connections URL"]
|
|
3351
|
+
]) {
|
|
3352
|
+
const wanted = wantApp[field];
|
|
3353
|
+
if (wanted !== void 0 && (have[field] ?? "") !== wanted) appProblems.push(`${label} is ${shown(have[field] || "not set")} but the layout says ${shown(wanted)}`);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
add("app-settings", APP_TITLE, appProblems, wantApp ? "1 of 1 as declared" : "none declared");
|
|
3249
3358
|
}
|
|
3250
3359
|
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] : [])];
|
|
3251
3360
|
if (wanted.length === 0) checks.push({
|
|
@@ -3907,6 +4016,10 @@ async function runNew(input) {
|
|
|
3907
4016
|
* shared preset would apply it from every repo.
|
|
3908
4017
|
*/
|
|
3909
4018
|
/** A channel's access: its category's, with any field the channel states itself laid over it. */
|
|
4019
|
+
/** The picture a channel's webhook should have: its own, else the layout's, else none. */
|
|
4020
|
+
function webhookAvatarOf(layout, webhook) {
|
|
4021
|
+
return (typeof webhook === "object" ? webhook.avatar : void 0) ?? layout.webhookAvatar;
|
|
4022
|
+
}
|
|
3910
4023
|
function effectiveAccess(category, channel) {
|
|
3911
4024
|
if (!category && !channel) return void 0;
|
|
3912
4025
|
return {
|
|
@@ -3958,6 +4071,9 @@ function serverLayoutProblem(layout) {
|
|
|
3958
4071
|
if (count === 0) return `${where} names the channel \`${name}\`, which is not in the layout's channels`;
|
|
3959
4072
|
if (count > 1) return `${where} names \`${name}\`, but more than one channel in the layout has that name`;
|
|
3960
4073
|
};
|
|
4074
|
+
const everyone = layout.everyone;
|
|
4075
|
+
const both = (everyone?.allow ?? []).find((p) => (everyone?.deny ?? []).some((d) => d.toLowerCase() === p.toLowerCase()));
|
|
4076
|
+
if (both !== void 0) return `everyone: \`${both}\` is in both \`allow\` and \`deny\``;
|
|
3961
4077
|
const server = layout.server;
|
|
3962
4078
|
if (server) {
|
|
3963
4079
|
for (const field of [
|
|
@@ -4034,6 +4150,17 @@ async function runNotificationsSetup(input) {
|
|
|
4034
4150
|
name: org
|
|
4035
4151
|
} : void 0);
|
|
4036
4152
|
if (wantsSecrets && !scope && !dryRun) return refuse("no org to store secrets under — pass --scope or set `org` in holocron.config.ts");
|
|
4153
|
+
const avatars = /* @__PURE__ */ new Map();
|
|
4154
|
+
const read = input.loadAvatar ?? loadAvatar;
|
|
4155
|
+
for (const channel of (layout.categories ?? []).flatMap((c) => c.channels ?? [])) {
|
|
4156
|
+
const source = channel.webhook ? webhookAvatarOf(layout, channel.webhook) : void 0;
|
|
4157
|
+
if (source === void 0 || avatars.has(source)) continue;
|
|
4158
|
+
try {
|
|
4159
|
+
avatars.set(source, await read(source));
|
|
4160
|
+
} catch (err) {
|
|
4161
|
+
return refuse(`webhook avatar \`${source}\`: ${errorMessage(err)}`);
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4037
4164
|
if (secrets && scope && !dryRun) try {
|
|
4038
4165
|
await assertSecretsReachable(secrets, scope);
|
|
4039
4166
|
} catch (err) {
|
|
@@ -4048,6 +4175,8 @@ async function runNotificationsSetup(input) {
|
|
|
4048
4175
|
if (layout.server && !notifications.ensureServer) return refuse(`the layout declares \`server\` settings, but the \`${notifications.providerName}\` provider cannot manage them`);
|
|
4049
4176
|
if (layout.welcome && !notifications.ensureWelcome) return refuse(`the layout declares a \`welcome\` screen, but the \`${notifications.providerName}\` provider cannot manage one`);
|
|
4050
4177
|
if (layout.onboarding && !notifications.ensureOnboarding) return refuse(`the layout declares \`onboarding\`, but the \`${notifications.providerName}\` provider cannot manage it`);
|
|
4178
|
+
if (layout.application && !notifications.ensureApplication) return refuse(`the layout declares \`application\` settings, but the \`${notifications.providerName}\` provider cannot manage them`);
|
|
4179
|
+
if (layout.everyone && !notifications.ensureEveryone) return refuse(`the layout declares \`everyone\` permissions, but the \`${notifications.providerName}\` provider cannot manage them`);
|
|
4051
4180
|
const needsCategories = (layout.categories ?? []).find((c) => c.access);
|
|
4052
4181
|
if (needsCategories && !notifications.ensureCategory) return refuse(`category \`${needsCategories.name}\` declares \`access\`, but the \`${notifications.providerName}\` provider cannot manage categories`);
|
|
4053
4182
|
const providerProblem = notifications.validateLayout?.(layout);
|
|
@@ -4139,6 +4268,11 @@ async function runNotificationsSetup(input) {
|
|
|
4139
4268
|
continue;
|
|
4140
4269
|
}
|
|
4141
4270
|
for (const channel of channels) await attempt(`channel ${channel.name}`, async () => {
|
|
4271
|
+
const source = channel.webhook ? webhookAvatarOf(layout, channel.webhook) : void 0;
|
|
4272
|
+
const webhook = channel.webhook ? {
|
|
4273
|
+
...channel.webhook === true ? {} : channel.webhook,
|
|
4274
|
+
...source ? { avatar: avatars.get(source) } : {}
|
|
4275
|
+
} : void 0;
|
|
4142
4276
|
const outcome = await provisionChannel({
|
|
4143
4277
|
notifications,
|
|
4144
4278
|
...secrets ? { secrets } : {},
|
|
@@ -4151,7 +4285,7 @@ async function runNotificationsSetup(input) {
|
|
|
4151
4285
|
category: category.name,
|
|
4152
4286
|
...channel.topic ? { topic: channel.topic } : {},
|
|
4153
4287
|
...effectiveAccess(category.access, channel.access) ? { access: effectiveAccess(category.access, channel.access) } : {},
|
|
4154
|
-
...
|
|
4288
|
+
...webhook ? { webhook } : {}
|
|
4155
4289
|
});
|
|
4156
4290
|
report.channels.push(outcome);
|
|
4157
4291
|
if (outcome.webhook && !outcome.webhook.stored) report.failures.push({
|
|
@@ -4160,7 +4294,7 @@ async function runNotificationsSetup(input) {
|
|
|
4160
4294
|
});
|
|
4161
4295
|
});
|
|
4162
4296
|
}
|
|
4163
|
-
if (layout.server || layout.welcome || layout.onboarding) {
|
|
4297
|
+
if (layout.server || layout.welcome || layout.onboarding || layout.application || layout.everyone) {
|
|
4164
4298
|
print(`\n${style.header("Server")}`);
|
|
4165
4299
|
const apply = async (label, declared, run) => {
|
|
4166
4300
|
if (!declared) return;
|
|
@@ -4176,9 +4310,11 @@ async function runNotificationsSetup(input) {
|
|
|
4176
4310
|
made(changed);
|
|
4177
4311
|
});
|
|
4178
4312
|
};
|
|
4313
|
+
await apply("@everyone permissions", !!layout.everyone, () => notifications.ensureEveryone(layout.everyone));
|
|
4179
4314
|
await apply("server settings", !!layout.server, () => notifications.ensureServer(layout.server));
|
|
4180
4315
|
await apply("welcome screen", !!layout.welcome, () => notifications.ensureWelcome(layout.welcome));
|
|
4181
4316
|
await apply("onboarding", !!layout.onboarding, () => notifications.ensureOnboarding(layout.onboarding));
|
|
4317
|
+
await apply("bot application", !!layout.application, () => notifications.ensureApplication(layout.application));
|
|
4182
4318
|
}
|
|
4183
4319
|
if (report.failures.length > 0) report.status = "fail";
|
|
4184
4320
|
else if (dryRun) report.status = "dry-run";
|