seedcord 0.23.0-next.2 → 0.23.0-next.3
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
CHANGED
|
@@ -50,6 +50,7 @@ Run it through your project's scripts, like `pnpm dev`. A global `seedcord` thro
|
|
|
50
50
|
| `seedcord codegen` | writes the typed augmentations for your commands and config |
|
|
51
51
|
| `seedcord commands` | deploys, lists and cleans up your commands on Discord |
|
|
52
52
|
| `seedcord make` | writes new files from one of your Handlebars templates, or one an installed plugin ships |
|
|
53
|
+
| `seedcord ask` | searches the guide for a question, a pasted error, or an error code, and works outside a project |
|
|
53
54
|
|
|
54
55
|
<!-- prettier-ignore-end -->
|
|
55
56
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { n as SEEDCORD_CONFIG_FILENAMES, t as version } from "./version-
|
|
1
|
+
import { n as SEEDCORD_CONFIG_FILENAMES, t as version } from "./version-Xg0cBcsP.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { Command, Option } from "@commander-js/extra-typings";
|
|
4
4
|
import { SeedcordErrorCode, isSeedcordError, paint } from "@seedcord/errors";
|
|
5
5
|
import { formatBody, installNodeConsole, installNodeDefaults } from "@seedcord/logger/node";
|
|
6
|
+
import uFuzzy from "@leeoniya/ufuzzy";
|
|
7
|
+
import { BOT_FILES_KEY, BUILT_FILES_READY_KEY, BotLayout, asError, encodeFile, hasErrorCode, importFailed, importSpecifier, isInside, isInteractive, isPlainObject, isPortInUse, isStringArray, pluralize, posixRelative, recordThrownBy, routeLeavesOf, setCliFiles, setDevFiles, toPosixPath, unlessAborted, vitePluginThrew, withoutQuery, workerdPath } from "@seedcord/utils/internal";
|
|
6
8
|
import wrapAnsi from "wrap-ansi";
|
|
7
9
|
import { SeedcordError, SeedcordRangeError, SeedcordTypeError, WORDMARK, displayPath, throwSingleOrAggregate } from "@seedcord/errors/internal";
|
|
8
10
|
import { Box, Text, render, useAnimation, useBoxMetrics, useInput, useWindowSize } from "ink";
|
|
9
11
|
import React, { Fragment, useEffect, useEffectEvent, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
|
|
10
|
-
import uFuzzy from "@leeoniya/ufuzzy";
|
|
11
12
|
import cliSpinners from "cli-spinners";
|
|
12
13
|
import { LEVEL_COLOR, Logger, LoggerChannelRegistry } from "@seedcord/logger";
|
|
13
14
|
import chalk from "chalk";
|
|
14
15
|
import { basename, delimiter, dirname, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
15
|
-
import { BOT_FILES_KEY, BUILT_FILES_READY_KEY, BotLayout, asError, encodeFile, hasErrorCode, importFailed, importSpecifier, isInside, isInteractive, isPlainObject, isPortInUse, isStringArray, pluralize, posixRelative, recordThrownBy, routeLeavesOf, setCliFiles, setDevFiles, toPosixPath, unlessAborted, vitePluginThrew, withoutQuery, workerdPath } from "@seedcord/utils/internal";
|
|
16
16
|
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
17
17
|
import { CONSOLE_LEVEL, CONSOLE_METHODS, consoleRecord, logsFolderIn, routeConsole, traceError } from "@seedcord/logger/internal";
|
|
18
18
|
import { spawn } from "node:child_process";
|
|
@@ -42,6 +42,274 @@ import { Writable } from "node:stream";
|
|
|
42
42
|
import { camelCase, debounce, kebabCase, pascalCase, snakeCase } from "es-toolkit";
|
|
43
43
|
import Handlebars from "handlebars";
|
|
44
44
|
|
|
45
|
+
//#region ../../tooling/brand/dist/index.mjs
|
|
46
|
+
const BRAND_COLORS = {
|
|
47
|
+
pith: "#f8f6e8",
|
|
48
|
+
seedDark: "#2d3328",
|
|
49
|
+
flesh: "#f04e36",
|
|
50
|
+
fleshDeep: "#c8341f",
|
|
51
|
+
rind: "#6fab49",
|
|
52
|
+
rindDeep: "#4d7d33"
|
|
53
|
+
};
|
|
54
|
+
var SiteAddress$1 = class {
|
|
55
|
+
url;
|
|
56
|
+
constructor(url) {
|
|
57
|
+
this.url = url.replace(/\/+$/, "");
|
|
58
|
+
}
|
|
59
|
+
get label() {
|
|
60
|
+
return this.url.replace(/^[a-z]+:\/\//, "");
|
|
61
|
+
}
|
|
62
|
+
get path() {
|
|
63
|
+
return new URL(this.url).pathname.replace(/\/$/, "");
|
|
64
|
+
}
|
|
65
|
+
get exportDir() {
|
|
66
|
+
return `dist${this.path}`;
|
|
67
|
+
}
|
|
68
|
+
at(path) {
|
|
69
|
+
if (path === "") return this.url;
|
|
70
|
+
return `${this.url}/${path.replace(/^\/+/, "")}`;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const PRODUCTION_ORIGIN$1 = "https://seedcord.org";
|
|
74
|
+
const PRODUCTION_SITES$1 = {
|
|
75
|
+
home: new SiteAddress$1(PRODUCTION_ORIGIN$1),
|
|
76
|
+
guide: new SiteAddress$1(`${PRODUCTION_ORIGIN$1}/guide`),
|
|
77
|
+
docs: new SiteAddress$1(`${PRODUCTION_ORIGIN$1}/docs`)
|
|
78
|
+
};
|
|
79
|
+
const NEXT_GUIDE = new SiteAddress$1(`https://next.seedcord.org${PRODUCTION_SITES$1.guide.path}`);
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region ../../tooling/docs-search/dist/index.mjs
|
|
83
|
+
const ASK_WAIT_STATUS = {
|
|
84
|
+
tooManySearches: 429,
|
|
85
|
+
outOfAllowance: 503
|
|
86
|
+
};
|
|
87
|
+
const WAIT_REASONS = {
|
|
88
|
+
[ASK_WAIT_STATUS.tooManySearches]: "Too many searches.",
|
|
89
|
+
[ASK_WAIT_STATUS.outOfAllowance]: "The guide search hit its daily limit."
|
|
90
|
+
};
|
|
91
|
+
const isAskWaitStatus = (status) => status in WAIT_REASONS;
|
|
92
|
+
const UNITS = [{
|
|
93
|
+
unit: "hour",
|
|
94
|
+
seconds: 3600
|
|
95
|
+
}, {
|
|
96
|
+
unit: "minute",
|
|
97
|
+
seconds: 60
|
|
98
|
+
}];
|
|
99
|
+
const relative$1 = new Intl.RelativeTimeFormat("en", { numeric: "always" });
|
|
100
|
+
function tryAgainIn(seconds) {
|
|
101
|
+
const fits = UNITS.find((size) => seconds >= size.seconds);
|
|
102
|
+
return fits ? relative$1.format(Math.ceil(seconds / fits.seconds), fits.unit) : relative$1.format(seconds, "second");
|
|
103
|
+
}
|
|
104
|
+
const waitMessage = (status, seconds) => `${WAIT_REASONS[status]} Try again ${tryAgainIn(seconds)}.`;
|
|
105
|
+
var SearchUnavailable = class extends Error {
|
|
106
|
+
constructor(status, retryAfterSeconds) {
|
|
107
|
+
super(waitMessage(status, retryAfterSeconds));
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const DocKind = {
|
|
111
|
+
Project: 1,
|
|
112
|
+
Module: 2,
|
|
113
|
+
Namespace: 4,
|
|
114
|
+
Enum: 8,
|
|
115
|
+
EnumMember: 16,
|
|
116
|
+
Variable: 32,
|
|
117
|
+
Function: 64,
|
|
118
|
+
Class: 128,
|
|
119
|
+
Interface: 256,
|
|
120
|
+
Constructor: 512,
|
|
121
|
+
Property: 1024,
|
|
122
|
+
Method: 2048,
|
|
123
|
+
CallSignature: 4096,
|
|
124
|
+
IndexSignature: 8192,
|
|
125
|
+
ConstructorSignature: 16384,
|
|
126
|
+
Parameter: 32768,
|
|
127
|
+
TypeLiteral: 65536,
|
|
128
|
+
TypeParameter: 131072,
|
|
129
|
+
Accessor: 262144,
|
|
130
|
+
GetSignature: 524288,
|
|
131
|
+
SetSignature: 1048576,
|
|
132
|
+
TypeAlias: 2097152,
|
|
133
|
+
Reference: 4194304
|
|
134
|
+
};
|
|
135
|
+
const KIND_SCORE_TABLE = {
|
|
136
|
+
[DocKind.Class]: 18,
|
|
137
|
+
[DocKind.Interface]: 16,
|
|
138
|
+
[DocKind.TypeAlias]: 15,
|
|
139
|
+
[DocKind.Enum]: 14,
|
|
140
|
+
[DocKind.Function]: 13,
|
|
141
|
+
[DocKind.Constructor]: 12,
|
|
142
|
+
[DocKind.Method]: 11,
|
|
143
|
+
[DocKind.Accessor]: 10,
|
|
144
|
+
[DocKind.Property]: 9,
|
|
145
|
+
[DocKind.EnumMember]: 7,
|
|
146
|
+
[DocKind.Variable]: 7,
|
|
147
|
+
[DocKind.TypeParameter]: 6,
|
|
148
|
+
[DocKind.Project]: 1,
|
|
149
|
+
[DocKind.Module]: 6,
|
|
150
|
+
[DocKind.Namespace]: 6,
|
|
151
|
+
[DocKind.CallSignature]: 10,
|
|
152
|
+
[DocKind.ConstructorSignature]: 10,
|
|
153
|
+
[DocKind.IndexSignature]: 8,
|
|
154
|
+
[DocKind.GetSignature]: 10,
|
|
155
|
+
[DocKind.SetSignature]: 10
|
|
156
|
+
};
|
|
157
|
+
const nameOnly = (_digits, name) => name;
|
|
158
|
+
const digitsAndName = (digits, name) => `${digits} ${name}`;
|
|
159
|
+
const CERTAIN_CODES = [
|
|
160
|
+
{
|
|
161
|
+
pattern: /\[(\d{4})\]/g,
|
|
162
|
+
rewrite: nameOnly
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
pattern: /^(\d{4})$/g,
|
|
166
|
+
rewrite: nameOnly
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
pattern: /(?<=\berror(?: code)?\W{0,3})(\d{4})\b/gi,
|
|
170
|
+
rewrite: digitsAndName
|
|
171
|
+
}
|
|
172
|
+
];
|
|
173
|
+
const FOUR_DIGITS_ON_THEIR_OWN = /(?<![\w.:/#-])\d{4}(?![\w:/-]|\.\d)/g;
|
|
174
|
+
var ErrorCodeReader = class {
|
|
175
|
+
codeNames;
|
|
176
|
+
constructor(codeNames) {
|
|
177
|
+
this.codeNames = codeNames;
|
|
178
|
+
}
|
|
179
|
+
read(question) {
|
|
180
|
+
const swapped = /* @__PURE__ */ new Map();
|
|
181
|
+
let withNames = question;
|
|
182
|
+
for (const { pattern, rewrite } of CERTAIN_CODES) withNames = withNames.replace(pattern, (written, digits) => {
|
|
183
|
+
const mention = this.mentionOf(digits);
|
|
184
|
+
if (mention === void 0) return written;
|
|
185
|
+
swapped.set(mention.code, mention);
|
|
186
|
+
return rewrite(digits, mention.name);
|
|
187
|
+
});
|
|
188
|
+
const hints = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const [digits] of withNames.matchAll(FOUR_DIGITS_ON_THEIR_OWN)) {
|
|
190
|
+
const mention = this.mentionOf(digits);
|
|
191
|
+
if (mention !== void 0 && !swapped.has(mention.code)) hints.set(mention.code, mention);
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
question: withNames,
|
|
195
|
+
swapped: [...swapped.values()],
|
|
196
|
+
hints: [...hints.values()]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
mentionOf(digits) {
|
|
200
|
+
const code = Number(digits);
|
|
201
|
+
const name = this.codeNames[code];
|
|
202
|
+
return name === void 0 ? void 0 : {
|
|
203
|
+
code,
|
|
204
|
+
name
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const errorCodeQuestion = (code) => `error ${code}`;
|
|
209
|
+
var SiteAddress = class {
|
|
210
|
+
url;
|
|
211
|
+
constructor(url) {
|
|
212
|
+
this.url = url.replace(/\/+$/, "");
|
|
213
|
+
}
|
|
214
|
+
get label() {
|
|
215
|
+
return this.url.replace(/^[a-z]+:\/\//, "");
|
|
216
|
+
}
|
|
217
|
+
get path() {
|
|
218
|
+
return new URL(this.url).pathname.replace(/\/$/, "");
|
|
219
|
+
}
|
|
220
|
+
get exportDir() {
|
|
221
|
+
return `dist${this.path}`;
|
|
222
|
+
}
|
|
223
|
+
at(path) {
|
|
224
|
+
if (path === "") return this.url;
|
|
225
|
+
return `${this.url}/${path.replace(/^\/+/, "")}`;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const PRODUCTION_ORIGIN = "https://seedcord.org";
|
|
229
|
+
const PRODUCTION_SITES = {
|
|
230
|
+
home: new SiteAddress(PRODUCTION_ORIGIN),
|
|
231
|
+
guide: new SiteAddress(`${PRODUCTION_ORIGIN}/guide`),
|
|
232
|
+
docs: new SiteAddress(`${PRODUCTION_ORIGIN}/docs`)
|
|
233
|
+
};
|
|
234
|
+
new SiteAddress(`https://next.seedcord.org${PRODUCTION_SITES.guide.path}`);
|
|
235
|
+
const EXTENSION = ".json";
|
|
236
|
+
const REFERENCE_SEARCH_URL = PRODUCTION_SITES.docs.at("/api/search");
|
|
237
|
+
const GUIDE_ASK_PATH = "/api/ask";
|
|
238
|
+
const searchFileName = (version) => `${version}${EXTENSION}`;
|
|
239
|
+
const PACKAGES_FILE_NAME = searchFileName("packages");
|
|
240
|
+
const guideResult = (hit, found, site) => ({
|
|
241
|
+
...hit,
|
|
242
|
+
url: site.at(hit.path),
|
|
243
|
+
...found
|
|
244
|
+
});
|
|
245
|
+
const fetchPath = (path) => fetch(path);
|
|
246
|
+
const withoutQuery$1 = (path) => path.replace(/\?.*$/, "");
|
|
247
|
+
var SiteUnreachable = class extends Error {
|
|
248
|
+
constructor(path, cause) {
|
|
249
|
+
super(`${withoutQuery$1(path)} failed to load because the request never reached the server`, { cause });
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
async function requestReachingServer(path, read) {
|
|
253
|
+
try {
|
|
254
|
+
return await read(path);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
if (error instanceof TypeError) throw new SiteUnreachable(path, error);
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function readAsset(path, read = fetchPath) {
|
|
261
|
+
const response = await requestReachingServer(path, read);
|
|
262
|
+
if (!response.ok) throw new Error(`${withoutQuery$1(path)} failed to load with status ${response.status}`);
|
|
263
|
+
return response;
|
|
264
|
+
}
|
|
265
|
+
async function readJson$1(path, read = fetchPath) {
|
|
266
|
+
return await (await readAsset(path, read)).json();
|
|
267
|
+
}
|
|
268
|
+
async function fetchAnswer(url, signal) {
|
|
269
|
+
const response = await fetch(url, { signal });
|
|
270
|
+
const retryAfter = Number(response.headers.get("retry-after"));
|
|
271
|
+
if (isAskWaitStatus(response.status) && Number.isSafeInteger(retryAfter) && retryAfter > 0) throw new SearchUnavailable(response.status, retryAfter);
|
|
272
|
+
return response;
|
|
273
|
+
}
|
|
274
|
+
var GuideSearch = class {
|
|
275
|
+
site;
|
|
276
|
+
constructor(site) {
|
|
277
|
+
this.site = site;
|
|
278
|
+
}
|
|
279
|
+
async search(question, signal) {
|
|
280
|
+
const url = new URL(this.site.at(GUIDE_ASK_PATH));
|
|
281
|
+
url.searchParams.set("q", question);
|
|
282
|
+
return (await readJson$1(url.href, (path) => fetchAnswer(path, signal))).map((hit) => guideResult(hit, { by: "meaning" }, this.site));
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
const QUESTION_LENGTH = {
|
|
286
|
+
min: 2,
|
|
287
|
+
max: 500
|
|
288
|
+
};
|
|
289
|
+
const QUESTION_RULE = `${QUESTION_LENGTH.min} to ${QUESTION_LENGTH.max} characters, with a letter or a number in them`;
|
|
290
|
+
const LETTER_OR_NUMBER = /[\p{L}\p{N}]/u;
|
|
291
|
+
const isQuestion = (text) => text.length >= QUESTION_LENGTH.min && text.length <= QUESTION_LENGTH.max && LETTER_OR_NUMBER.test(text);
|
|
292
|
+
const collapseSpaces = (text) => text.replaceAll(/\s+/g, " ");
|
|
293
|
+
function parseQuestion(input) {
|
|
294
|
+
const question = collapseSpaces(input.trim());
|
|
295
|
+
return isQuestion(question) ? question : void 0;
|
|
296
|
+
}
|
|
297
|
+
const STACK_FRAME$1 = /^\s*at .*(?::\d+:\d+\)?|<anonymous>\)?|\(index \d+\))\s*$/gm;
|
|
298
|
+
function cutAtWord(text, max) {
|
|
299
|
+
if (text.length <= max) return text;
|
|
300
|
+
const lastSpace = text.lastIndexOf(" ", max);
|
|
301
|
+
return text.slice(0, lastSpace > 0 ? lastSpace : max);
|
|
302
|
+
}
|
|
303
|
+
function questionFromText(text) {
|
|
304
|
+
const withoutFrames = collapseSpaces(text.replaceAll(STACK_FRAME$1, "").trim());
|
|
305
|
+
const kept = cutAtWord(withoutFrames, QUESTION_LENGTH.max);
|
|
306
|
+
return {
|
|
307
|
+
question: parseQuestion(kept),
|
|
308
|
+
shortened: kept.length < withoutFrames.length
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
//#endregion
|
|
45
313
|
//#region src/core/output/wrapLines.ts
|
|
46
314
|
function wrapLines(text, width, prefix) {
|
|
47
315
|
return text.split("\n").flatMap((line) => {
|
|
@@ -817,12 +1085,14 @@ var StepPrinter = class {
|
|
|
817
1085
|
command;
|
|
818
1086
|
labelWidth;
|
|
819
1087
|
verbose;
|
|
1088
|
+
rows;
|
|
820
1089
|
stdout;
|
|
821
1090
|
stderr;
|
|
822
|
-
constructor({ command, labels, verbose, stdout = process.stdout, stderr = process.stderr }) {
|
|
1091
|
+
constructor({ command, labels, verbose, rows = true, stdout = process.stdout, stderr = process.stderr }) {
|
|
823
1092
|
this.command = command;
|
|
824
1093
|
this.labelWidth = Math.max(...labels.map((label) => label.length));
|
|
825
1094
|
this.verbose = verbose;
|
|
1095
|
+
this.rows = rows;
|
|
826
1096
|
this.stdout = stdout;
|
|
827
1097
|
this.stderr = stderr;
|
|
828
1098
|
}
|
|
@@ -830,6 +1100,7 @@ var StepPrinter = class {
|
|
|
830
1100
|
this.stdout.write(`\n${WORDMARK} ${paint.mute(`v${version}`)} ${this.command}\n\n`);
|
|
831
1101
|
}
|
|
832
1102
|
async step(label, task, note, lines) {
|
|
1103
|
+
if (!this.rows) return task();
|
|
833
1104
|
const write = this.stdout.write.bind(this.stdout);
|
|
834
1105
|
const row = new StepRow({
|
|
835
1106
|
write,
|
|
@@ -887,17 +1158,33 @@ function exitFor(error) {
|
|
|
887
1158
|
var BaseCommand = class {
|
|
888
1159
|
name;
|
|
889
1160
|
description;
|
|
890
|
-
|
|
1161
|
+
needsProject;
|
|
1162
|
+
constructor(name, description, { needsProject = true } = {}) {
|
|
891
1163
|
this.name = name;
|
|
892
1164
|
this.description = description;
|
|
1165
|
+
this.needsProject = needsProject;
|
|
893
1166
|
}
|
|
894
|
-
|
|
1167
|
+
define(program) {
|
|
1168
|
+
return program.command(this.name).description(this.description);
|
|
1169
|
+
}
|
|
1170
|
+
runSteps(command, labels, run) {
|
|
895
1171
|
const printer = new StepPrinter({
|
|
896
1172
|
command: this.name,
|
|
897
1173
|
labels,
|
|
898
1174
|
verbose: isVerbose(command)
|
|
899
1175
|
});
|
|
900
1176
|
printer.header();
|
|
1177
|
+
return this.runOn(printer, run);
|
|
1178
|
+
}
|
|
1179
|
+
runQuietSteps(command, labels, run) {
|
|
1180
|
+
return this.runOn(new StepPrinter({
|
|
1181
|
+
command: this.name,
|
|
1182
|
+
labels,
|
|
1183
|
+
verbose: isVerbose(command),
|
|
1184
|
+
rows: false
|
|
1185
|
+
}), run);
|
|
1186
|
+
}
|
|
1187
|
+
async runOn(printer, run) {
|
|
901
1188
|
try {
|
|
902
1189
|
await run(printer);
|
|
903
1190
|
} catch (error) {
|
|
@@ -918,6 +1205,162 @@ var BaseCommand = class {
|
|
|
918
1205
|
}
|
|
919
1206
|
};
|
|
920
1207
|
|
|
1208
|
+
//#endregion
|
|
1209
|
+
//#region ../../tooling/ui/dist/PageAsset.mjs
|
|
1210
|
+
const ROOT = "index";
|
|
1211
|
+
const toSegments = (path) => path.split("/").filter(Boolean);
|
|
1212
|
+
var PageAsset = class {
|
|
1213
|
+
extension;
|
|
1214
|
+
directory;
|
|
1215
|
+
constructor(extension, directory) {
|
|
1216
|
+
this.extension = extension;
|
|
1217
|
+
this.directory = directory;
|
|
1218
|
+
}
|
|
1219
|
+
static forPath(pathname) {
|
|
1220
|
+
return PAGE_ASSETS.find((asset) => pathname.endsWith(asset.extension));
|
|
1221
|
+
}
|
|
1222
|
+
fileSegments(pageSegments) {
|
|
1223
|
+
const last = pageSegments.at(-1) ?? ROOT;
|
|
1224
|
+
return [...pageSegments.slice(0, -1), `${last}${this.extension}`];
|
|
1225
|
+
}
|
|
1226
|
+
pageSegments(fileSegments) {
|
|
1227
|
+
const last = fileSegments.at(-1);
|
|
1228
|
+
if (last?.endsWith(this.extension) !== true) return void 0;
|
|
1229
|
+
const name = last.slice(0, -this.extension.length);
|
|
1230
|
+
const folders = fileSegments.slice(0, -1);
|
|
1231
|
+
return folders.length === 0 && name === ROOT ? [] : [...folders, name];
|
|
1232
|
+
}
|
|
1233
|
+
publicPath(pagePath) {
|
|
1234
|
+
const page = pagePath.endsWith(this.extension) ? pagePath.slice(0, -this.extension.length) : pagePath;
|
|
1235
|
+
return `/${this.fileSegments(toSegments(page)).join("/")}`;
|
|
1236
|
+
}
|
|
1237
|
+
exportPath(pagePath) {
|
|
1238
|
+
return `/${this.directory}${this.publicPath(pagePath)}`;
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
const TWIN = new PageAsset(".md", "llms");
|
|
1242
|
+
const CARD = new PageAsset(".png", "og");
|
|
1243
|
+
const PAGE_ASSETS = [TWIN, CARD];
|
|
1244
|
+
|
|
1245
|
+
//#endregion
|
|
1246
|
+
//#region src/commands/commands/StepsPresenter.ts
|
|
1247
|
+
var StepsPresenter = class {
|
|
1248
|
+
output;
|
|
1249
|
+
constructor(output) {
|
|
1250
|
+
this.output = output;
|
|
1251
|
+
}
|
|
1252
|
+
step(label, task, note) {
|
|
1253
|
+
return this.output.step(label, task, note);
|
|
1254
|
+
}
|
|
1255
|
+
detail(key, value) {
|
|
1256
|
+
this.output.detail(key, value);
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1260
|
+
//#endregion
|
|
1261
|
+
//#region src/commands/ask/AskPresenter.ts
|
|
1262
|
+
const ASK_STEPS = ["search"];
|
|
1263
|
+
const SHORTENED = `Your text is over ${QUESTION_LENGTH.max} characters, so seedcord ask searched only the start of it.`;
|
|
1264
|
+
const FOR_PERSON = {
|
|
1265
|
+
pageLines: ({ title, sections }) => [paint.bold(title), ...sections.flatMap(({ heading, url }) => [...heading === void 0 ? [] : [` ${heading}`], ` ${paint.sky(url)}`])],
|
|
1266
|
+
wholeGuide: (site) => site.url
|
|
1267
|
+
};
|
|
1268
|
+
const FOR_AGENT = {
|
|
1269
|
+
pageLines: ({ title, path, sections }, site) => [`${paint.bold(title)}: ${paint.sky(site.at(TWIN.publicPath(path)))}`, ...sections.flatMap(({ heading }) => heading === void 0 ? [] : [` - ${heading}`])],
|
|
1270
|
+
wholeGuide: (site) => site.at("llms.txt")
|
|
1271
|
+
};
|
|
1272
|
+
function pagesOf(results) {
|
|
1273
|
+
const pages = /* @__PURE__ */ new Map();
|
|
1274
|
+
for (const result of results) {
|
|
1275
|
+
const path = result.path.replace(/#.*$/, "");
|
|
1276
|
+
const title = result.page === result.tab ? result.page : `${result.page} (${result.tab})`;
|
|
1277
|
+
const page = pages.get(path) ?? {
|
|
1278
|
+
title,
|
|
1279
|
+
path,
|
|
1280
|
+
sections: []
|
|
1281
|
+
};
|
|
1282
|
+
if (result.heading === void 0) page.sections.unshift(result);
|
|
1283
|
+
else page.sections.push(result);
|
|
1284
|
+
pages.set(path, page);
|
|
1285
|
+
}
|
|
1286
|
+
return [...pages.values()];
|
|
1287
|
+
}
|
|
1288
|
+
function codeNotes({ swapped, hints }) {
|
|
1289
|
+
return [...swapped.map(({ code, name }) => paint.italic(`${code} is ${paint.bold(name)}`)), ...hints.map(({ code, name }) => paint.italic(`${code} is also the error code ${paint.bold(name)}. If you meant that, run: ${paint.bold(`seedcord ask "${errorCodeQuestion(code)}"`)}`))];
|
|
1290
|
+
}
|
|
1291
|
+
var AskPresenter = class extends StepsPresenter {
|
|
1292
|
+
format;
|
|
1293
|
+
site;
|
|
1294
|
+
constructor(output, format, site) {
|
|
1295
|
+
super(output);
|
|
1296
|
+
this.format = format;
|
|
1297
|
+
this.site = site;
|
|
1298
|
+
}
|
|
1299
|
+
answer({ codes, shortened, results }) {
|
|
1300
|
+
const notes = [...codeNotes(codes), ...shortened ? [SHORTENED] : []];
|
|
1301
|
+
const pages = pagesOf(results).map((page) => this.format.pageLines(page, this.site));
|
|
1302
|
+
const answer = pages.length > 0 ? pages : [[this.noMatches()]];
|
|
1303
|
+
this.output.line();
|
|
1304
|
+
for (const block of [notes, ...answer]) {
|
|
1305
|
+
if (block.length === 0) continue;
|
|
1306
|
+
for (const line of block) this.output.line(line);
|
|
1307
|
+
this.output.line();
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
noMatches() {
|
|
1311
|
+
return `Nothing in the guide matches. Try a full sentence, or read ${paint.sky(this.format.wholeGuide(this.site))}`;
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
//#endregion
|
|
1316
|
+
//#region src/commands/ask/AskRunner.ts
|
|
1317
|
+
const SEARCH_TIMEOUT_MS = 1e4;
|
|
1318
|
+
const CODE_READER = new ErrorCodeReader(SeedcordErrorCode);
|
|
1319
|
+
var AskRunner = class {
|
|
1320
|
+
site;
|
|
1321
|
+
guide;
|
|
1322
|
+
constructor(site, guide) {
|
|
1323
|
+
this.site = site;
|
|
1324
|
+
this.guide = guide;
|
|
1325
|
+
}
|
|
1326
|
+
async run(output, format, text) {
|
|
1327
|
+
const { question, shortened } = questionFromText(text);
|
|
1328
|
+
if (question === void 0) throw new SeedcordTypeError(SeedcordErrorCode.CliAskBadQuestion, [QUESTION_LENGTH.min]);
|
|
1329
|
+
const presenter = new AskPresenter(output, format, this.site);
|
|
1330
|
+
presenter.detail("site", this.site.url);
|
|
1331
|
+
const results = await presenter.step("search", () => this.search(question));
|
|
1332
|
+
presenter.answer({
|
|
1333
|
+
codes: CODE_READER.read(question),
|
|
1334
|
+
shortened,
|
|
1335
|
+
results
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
async search(question) {
|
|
1339
|
+
try {
|
|
1340
|
+
return await this.guide.search(question, AbortSignal.timeout(SEARCH_TIMEOUT_MS));
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
if (error instanceof SearchUnavailable) throw new SeedcordError(SeedcordErrorCode.CliAskHasToWait, [error.message, this.site.url], { cause: error });
|
|
1343
|
+
throw new SeedcordError(SeedcordErrorCode.CliAskFailed, [asError(error).message, this.site.url], { cause: error });
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
//#endregion
|
|
1349
|
+
//#region src/commands/ask/AskCommand.ts
|
|
1350
|
+
const GUIDE_SITE = "0.23.0-next.3".includes("-") ? NEXT_GUIDE : PRODUCTION_SITES$1.guide;
|
|
1351
|
+
var AskCommand = class extends BaseCommand {
|
|
1352
|
+
runner = new AskRunner(GUIDE_SITE, new GuideSearch(GUIDE_SITE));
|
|
1353
|
+
constructor() {
|
|
1354
|
+
super("ask", "Search the guide", { needsProject: false });
|
|
1355
|
+
}
|
|
1356
|
+
register(program) {
|
|
1357
|
+
return this.define(program).argument("<question...>", "What to search for, or an error code or message to look up").allowUnknownOption().action((words, _options, command) => {
|
|
1358
|
+
const question = words.join(" ");
|
|
1359
|
+
return isInteractive() ? this.runSteps(command, ASK_STEPS, (printer) => this.runner.run(printer, FOR_PERSON, question)) : this.runQuietSteps(command, ASK_STEPS, (printer) => this.runner.run(printer, FOR_AGENT, question));
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
|
|
921
1364
|
//#endregion
|
|
922
1365
|
//#region src/core/config/openProjectWithSteps.ts
|
|
923
1366
|
async function openProjectWithSteps(steps, loader, { projectDir, check } = {}) {
|
|
@@ -3316,7 +3759,7 @@ var BuildCommand = class extends BaseCommand {
|
|
|
3316
3759
|
super("build", "Type check the bot and bundle it from the config file");
|
|
3317
3760
|
}
|
|
3318
3761
|
register(program) {
|
|
3319
|
-
|
|
3762
|
+
return this.define(program).action((_options, command) => this.runSteps(command, BUILD_STEPS, async (printer) => {
|
|
3320
3763
|
printBuildSummary(printer, await BuildRunner.create(printer).run());
|
|
3321
3764
|
}));
|
|
3322
3765
|
}
|
|
@@ -3814,7 +4257,7 @@ var CodegenCommand = class extends BaseCommand {
|
|
|
3814
4257
|
super("codegen", "Generate typed augmentations from your commands and config");
|
|
3815
4258
|
}
|
|
3816
4259
|
register(program) {
|
|
3817
|
-
|
|
4260
|
+
return this.define(program).option("--check", "Verify the committed augmentations are up to date instead of writing them").action((options, command) => this.runSteps(command, CODEGEN_STEPS, async (printer) => {
|
|
3818
4261
|
const check = options.check ?? false;
|
|
3819
4262
|
const { outputPath, changed } = await CodegenRunner.create(printer, cliLogger("Codegen")).run(check);
|
|
3820
4263
|
printer.line();
|
|
@@ -3945,21 +4388,6 @@ function printSections(output, sections) {
|
|
|
3945
4388
|
}
|
|
3946
4389
|
}
|
|
3947
4390
|
|
|
3948
|
-
//#endregion
|
|
3949
|
-
//#region src/commands/commands/StepsPresenter.ts
|
|
3950
|
-
var StepsPresenter = class {
|
|
3951
|
-
output;
|
|
3952
|
-
constructor(output) {
|
|
3953
|
-
this.output = output;
|
|
3954
|
-
}
|
|
3955
|
-
step(label, task, note) {
|
|
3956
|
-
return this.output.step(label, task, note);
|
|
3957
|
-
}
|
|
3958
|
-
detail(key, value) {
|
|
3959
|
-
this.output.detail(key, value);
|
|
3960
|
-
}
|
|
3961
|
-
};
|
|
3962
|
-
|
|
3963
4391
|
//#endregion
|
|
3964
4392
|
//#region src/commands/commands/deploy/DeployPresenter.ts
|
|
3965
4393
|
function deployedNote(scopes) {
|
|
@@ -4664,7 +5092,7 @@ var CommandsCommand = class extends BaseCommand {
|
|
|
4664
5092
|
this.prompter = prompter;
|
|
4665
5093
|
}
|
|
4666
5094
|
register(program) {
|
|
4667
|
-
const commands =
|
|
5095
|
+
const commands = this.define(program).action((_options, command) => this.openMenu(command));
|
|
4668
5096
|
commands.command("deploy").description("Deploy the commands in your code to Discord").option("--yes", "Deploy without asking first").option("--force", "Also deploy the scopes that match the last deploy").action((options, command) => this.runSteps(command, DEPLOY_STEPS, (printer) => this.deploy(printer, command, options)));
|
|
4669
5097
|
commands.command("list").description("List the commands Discord has for your bot").option("--guilds <ids...>", "Also list these guilds").addOption(allGuildsOption("Also list every guild the bot is in")).option("--json", "Print the listing as JSON for scripts").action((options, command) => {
|
|
4670
5098
|
const scope = {
|
|
@@ -4676,6 +5104,7 @@ var CommandsCommand = class extends BaseCommand {
|
|
|
4676
5104
|
});
|
|
4677
5105
|
commands.command("clean").description("Delete guild commands that duplicate a global command").option("--guilds <ids...>", "Guild ids to scan").addOption(allGuildsOption("Scan every guild the bot is in")).option("--yes", "Delete the duplicates without asking").action((flags, command) => this.remove(command, flags, new CleanMode(), askClean));
|
|
4678
5106
|
commands.command("purge").description("Delete every command globally or in guilds").option("--global", "Delete every global command").option("--guilds <ids...>", "Delete every command in these guilds").addOption(allGuildsOption("Delete every command in every guild the bot is in")).option("--yes", "Delete the commands without asking").action((flags, command) => this.remove(command, flags, new PurgeMode(flags.global ?? false), askPurge));
|
|
5107
|
+
return commands;
|
|
4679
5108
|
}
|
|
4680
5109
|
async openMenu(command) {
|
|
4681
5110
|
if (!isInteractive()) {
|
|
@@ -5959,42 +6388,6 @@ function ScrollableLogView({ visible, layout, viewportHeight, measured }) {
|
|
|
5959
6388
|
})));
|
|
5960
6389
|
}
|
|
5961
6390
|
|
|
5962
|
-
//#endregion
|
|
5963
|
-
//#region ../../tooling/brand/dist/index.mjs
|
|
5964
|
-
const BRAND_COLORS = {
|
|
5965
|
-
pith: "#f8f6e8",
|
|
5966
|
-
seedDark: "#2d3328",
|
|
5967
|
-
flesh: "#f04e36",
|
|
5968
|
-
fleshDeep: "#c8341f",
|
|
5969
|
-
rind: "#6fab49",
|
|
5970
|
-
rindDeep: "#4d7d33"
|
|
5971
|
-
};
|
|
5972
|
-
var SiteAddress = class {
|
|
5973
|
-
url;
|
|
5974
|
-
constructor(url) {
|
|
5975
|
-
this.url = url.replace(/\/+$/, "");
|
|
5976
|
-
}
|
|
5977
|
-
get label() {
|
|
5978
|
-
return this.url.replace(/^[a-z]+:\/\//, "");
|
|
5979
|
-
}
|
|
5980
|
-
get path() {
|
|
5981
|
-
return new URL(this.url).pathname.replace(/\/$/, "");
|
|
5982
|
-
}
|
|
5983
|
-
get exportDir() {
|
|
5984
|
-
return `dist${this.path}`;
|
|
5985
|
-
}
|
|
5986
|
-
at(path) {
|
|
5987
|
-
if (path === "") return this.url;
|
|
5988
|
-
return `${this.url}/${path.replace(/^\/+/, "")}`;
|
|
5989
|
-
}
|
|
5990
|
-
};
|
|
5991
|
-
const PRODUCTION_ORIGIN = "https://seedcord.org";
|
|
5992
|
-
const PRODUCTION_SITES = {
|
|
5993
|
-
home: new SiteAddress(PRODUCTION_ORIGIN),
|
|
5994
|
-
guide: new SiteAddress(`${PRODUCTION_ORIGIN}/guide`),
|
|
5995
|
-
docs: new SiteAddress(`${PRODUCTION_ORIGIN}/docs`)
|
|
5996
|
-
};
|
|
5997
|
-
|
|
5998
6391
|
//#endregion
|
|
5999
6392
|
//#region src/ui/components/Banner.tsx
|
|
6000
6393
|
function Banner({ version }) {
|
|
@@ -8036,7 +8429,7 @@ var TunnelCoordinator = class {
|
|
|
8036
8429
|
//#region src/commands/dev/tunnel/createTunnelCoordinator.ts
|
|
8037
8430
|
const PASTE_HINT = "The tunnel is still up, so try pasting that URL into the dashboard.";
|
|
8038
8431
|
const RESTART_HINT = "Restart for a fresh hostname, or set `tunnel` to an https URL you already serve.";
|
|
8039
|
-
const NO_PORT_HINT = `seedcord didn't start a tunnel, since the bot runs with ${paint.bold("port: false")} and cloudflared doesn't have a port to forward to. Give the bot a port while you develop, or set ${paint.amber("tunnel")} to the URL of the server that serves your bot: ${paint.sky(PRODUCTION_SITES.guide.at("build/embed"))}`;
|
|
8432
|
+
const NO_PORT_HINT = `seedcord didn't start a tunnel, since the bot runs with ${paint.bold("port: false")} and cloudflared doesn't have a port to forward to. Give the bot a port while you develop, or set ${paint.amber("tunnel")} to the URL of the server that serves your bot: ${paint.sky(PRODUCTION_SITES$1.guide.at("build/embed"))}`;
|
|
8040
8433
|
function missingCloudflaredHint(platform) {
|
|
8041
8434
|
const arrow = paint.mute("→");
|
|
8042
8435
|
return [
|
|
@@ -8357,7 +8750,7 @@ var DevCommand = class extends BaseCommand {
|
|
|
8357
8750
|
super("dev", "Run the bot from the config file");
|
|
8358
8751
|
}
|
|
8359
8752
|
register(program) {
|
|
8360
|
-
|
|
8753
|
+
return this.define(program).action(async () => {
|
|
8361
8754
|
profileStdout();
|
|
8362
8755
|
const store = new DevStore();
|
|
8363
8756
|
const stateFolder = new StateFolder(process.cwd(), this.logger);
|
|
@@ -8966,7 +9359,7 @@ var MakeCommand = class extends BaseCommand {
|
|
|
8966
9359
|
super("make", "Write new files from one of your templates");
|
|
8967
9360
|
}
|
|
8968
9361
|
register(program) {
|
|
8969
|
-
|
|
9362
|
+
return this.define(program).action((_options, command) => this.runSteps(command, MAKE_STEPS, (printer) => unlessCancelled(printer, () => MakeRunner.create(printer, new Prompter(), isInteractive()).run())));
|
|
8970
9363
|
}
|
|
8971
9364
|
};
|
|
8972
9365
|
|
|
@@ -8988,14 +9381,20 @@ function assertProjectInstall(projectDir, cliDir) {
|
|
|
8988
9381
|
async function main() {
|
|
8989
9382
|
installNodeConsole();
|
|
8990
9383
|
const program = addVerboseOption(new Command().name("seedcord").description("seedcord CLI").version(version));
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
9384
|
+
const commands = [
|
|
9385
|
+
new AskCommand(),
|
|
9386
|
+
new DevCommand(),
|
|
9387
|
+
new BuildCommand(),
|
|
9388
|
+
new CodegenCommand(),
|
|
9389
|
+
new CommandsCommand(),
|
|
9390
|
+
new MakeCommand()
|
|
9391
|
+
];
|
|
9392
|
+
for (const command of commands) {
|
|
9393
|
+
const registered = command.register(program);
|
|
9394
|
+
if (command.needsProject) registered.hook("preAction", () => {
|
|
9395
|
+
assertProjectInstall(process.cwd(), import.meta.dirname);
|
|
9396
|
+
});
|
|
9397
|
+
}
|
|
8999
9398
|
try {
|
|
9000
9399
|
await program.parseAsync(process.argv);
|
|
9001
9400
|
} catch (error) {
|