@wolfstar/cli 0.1.0-next-20260904111115
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/LICENSE +201 -0
- package/README.md +151 -0
- package/dist/args-Ch1KXUMn.js +24 -0
- package/dist/args-Ch1KXUMn.js.map +1 -0
- package/dist/build-Ct6zabKg.js +22 -0
- package/dist/build-Ct6zabKg.js.map +1 -0
- package/dist/build-DU76s-Ec.js +39 -0
- package/dist/build-DU76s-Ec.js.map +1 -0
- package/dist/builders-BzLMIm9Y.js +44 -0
- package/dist/builders-BzLMIm9Y.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +58 -0
- package/dist/cli.js.map +1 -0
- package/dist/codegen-DjPOn7cp.js +95 -0
- package/dist/codegen-DjPOn7cp.js.map +1 -0
- package/dist/codegen-mYkL-GBf.js +36 -0
- package/dist/codegen-mYkL-GBf.js.map +1 -0
- package/dist/commands-BkGKs_Bg.js +88 -0
- package/dist/commands-BkGKs_Bg.js.map +1 -0
- package/dist/commands-Dw2LFWV-.js +233 -0
- package/dist/commands-Dw2LFWV-.js.map +1 -0
- package/dist/dev-DWg8l23a.js +604 -0
- package/dist/dev-DWg8l23a.js.map +1 -0
- package/dist/dev-QUxuLcXj.js +30 -0
- package/dist/dev-QUxuLcXj.js.map +1 -0
- package/dist/errors-Bket6XFn.js +50 -0
- package/dist/errors-Bket6XFn.js.map +1 -0
- package/dist/external-Bk4EESdT.js +59 -0
- package/dist/external-Bk4EESdT.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/info-B-8rAIn6.js +106 -0
- package/dist/info-B-8rAIn6.js.map +1 -0
- package/dist/info-DRTYYvVB.js +30 -0
- package/dist/info-DRTYYvVB.js.map +1 -0
- package/dist/none-DEo7zEr9.js +50 -0
- package/dist/none-DEo7zEr9.js.map +1 -0
- package/dist/output-mode-ChrOf0LO.js +46 -0
- package/dist/output-mode-ChrOf0LO.js.map +1 -0
- package/dist/plain-CEe4X_Cz.js +43 -0
- package/dist/plain-CEe4X_Cz.js.map +1 -0
- package/dist/prepare-BXcm2Ukp.js +3 -0
- package/dist/prepare-CVf88_Or.js +36 -0
- package/dist/prepare-CVf88_Or.js.map +1 -0
- package/dist/prepare-D5NZ0w4t.js +68 -0
- package/dist/prepare-D5NZ0w4t.js.map +1 -0
- package/dist/process-supervisor-CJOdmp0W.js +128 -0
- package/dist/process-supervisor-CJOdmp0W.js.map +1 -0
- package/dist/project-DHF3qdm-.js +66 -0
- package/dist/project-DHF3qdm-.js.map +1 -0
- package/dist/tsc-BqZfDTD0.js +133 -0
- package/dist/tsc-BqZfDTD0.js.map +1 -0
- package/dist/tsc-CovVAtfo.js +3 -0
- package/dist/tsdown-DlAs0rSI.js +104 -0
- package/dist/tsdown-DlAs0rSI.js.map +1 -0
- package/dist/tui-BTRFsLQR.js +770 -0
- package/dist/tui-BTRFsLQR.js.map +1 -0
- package/dist/tunnel-B8Id4-lR.js +205 -0
- package/dist/tunnel-B8Id4-lR.js.map +1 -0
- package/dist/version-DN6Uho-Y.js +30 -0
- package/dist/version-DN6Uho-Y.js.map +1 -0
- package/dist/vite-D3vYFsqa.js +93 -0
- package/dist/vite-D3vYFsqa.js.map +1 -0
- package/package.json +95 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { n as ExitCode, t as CliError } from "./errors-Bket6XFn.js";
|
|
2
|
+
import { i as shouldUseColor } from "./output-mode-ChrOf0LO.js";
|
|
3
|
+
import { n as resolveCwd } from "./args-Ch1KXUMn.js";
|
|
4
|
+
import { i as resolveFromProject } from "./project-DHF3qdm-.js";
|
|
5
|
+
import { displayPath, loadStarsConfig } from "@wolfstar/http-framework/config";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { createColors } from "colorette";
|
|
9
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
|
|
12
|
+
//#region src/lib/tasks/codegen.ts
|
|
13
|
+
async function runCodegen(options) {
|
|
14
|
+
const stdout = options.stdout ?? process.stdout;
|
|
15
|
+
const colors = createColors({ useColor: shouldUseColor() && !options.json });
|
|
16
|
+
const config = await loadStarsConfig({
|
|
17
|
+
cwd: resolveCwd(options),
|
|
18
|
+
configFile: options.config
|
|
19
|
+
});
|
|
20
|
+
const results = [];
|
|
21
|
+
if (config.codegen.i18n) results.push(await runI18n(config, Boolean(options.check)));
|
|
22
|
+
if (options.json) stdout.write(`${JSON.stringify({
|
|
23
|
+
check: Boolean(options.check),
|
|
24
|
+
results
|
|
25
|
+
}, null, 2)}\n`);
|
|
26
|
+
else if (results.length === 0) stdout.write(`${colors.dim("stars")} nothing to generate (no code generators are configured)\n`);
|
|
27
|
+
else for (const result of results) {
|
|
28
|
+
const paint = result.status === "outdated" ? colors.red : colors.green;
|
|
29
|
+
stdout.write(`${colors.dim("stars")} ${result.generator}: ${paint(result.status)} ${displayPath(config.root, result.output)}\n`);
|
|
30
|
+
}
|
|
31
|
+
if (results.some((result) => result.status === "outdated")) throw new CliError("Generated files are out of date, run `stars codegen` to update them.", {
|
|
32
|
+
code: "CODEGEN_OUTDATED",
|
|
33
|
+
exitCode: ExitCode.Error
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function runI18n(config, check) {
|
|
37
|
+
const { locales, output } = config.codegen.i18n;
|
|
38
|
+
const cli = resolveFromProject(config.root, "@wolfstar/i18next-type-generator");
|
|
39
|
+
if (!cli) throw new CliError(`"@wolfstar/i18next-type-generator" is not installed in ${config.root}`, {
|
|
40
|
+
code: "DEPENDENCY_MISSING",
|
|
41
|
+
hint: "Install it with `pnpm add -D @wolfstar/i18next-type-generator`, or set `codegen.i18n` to false."
|
|
42
|
+
});
|
|
43
|
+
if (!check) {
|
|
44
|
+
await generate(config.root, cli, locales, output);
|
|
45
|
+
return {
|
|
46
|
+
generator: "i18n",
|
|
47
|
+
output,
|
|
48
|
+
status: "written"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const directory = await mkdtemp(join(tmpdir(), "stars-codegen-"));
|
|
52
|
+
try {
|
|
53
|
+
const candidate = join(directory, "i18next.d.ts");
|
|
54
|
+
await generate(config.root, cli, locales, candidate);
|
|
55
|
+
const [expected, actual] = await Promise.all([readFile(candidate, "utf-8"), readFile(output, "utf-8").catch(() => null)]);
|
|
56
|
+
return {
|
|
57
|
+
generator: "i18n",
|
|
58
|
+
output,
|
|
59
|
+
status: expected === actual ? "up-to-date" : "outdated"
|
|
60
|
+
};
|
|
61
|
+
} finally {
|
|
62
|
+
await rm(directory, {
|
|
63
|
+
recursive: true,
|
|
64
|
+
force: true
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function generate(root, cli, locales, output) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const child = spawn(process.execPath, [
|
|
71
|
+
cli,
|
|
72
|
+
locales,
|
|
73
|
+
output
|
|
74
|
+
], {
|
|
75
|
+
cwd: root,
|
|
76
|
+
stdio: [
|
|
77
|
+
"ignore",
|
|
78
|
+
"ignore",
|
|
79
|
+
"pipe"
|
|
80
|
+
],
|
|
81
|
+
windowsHide: true
|
|
82
|
+
});
|
|
83
|
+
let stderr = "";
|
|
84
|
+
child.stderr?.on("data", (chunk) => stderr += chunk.toString());
|
|
85
|
+
child.once("error", reject);
|
|
86
|
+
child.once("exit", (code) => {
|
|
87
|
+
if (code === 0) resolve();
|
|
88
|
+
else reject(new CliError(`i18next-type-generator exited with code ${code}${stderr ? `: ${stderr.trim()}` : ""}`, { code: "CODEGEN_FAILED" }));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
export { runCodegen };
|
|
95
|
+
//# sourceMappingURL=codegen-DjPOn7cp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen-DjPOn7cp.js","names":[],"sources":["../src/lib/tasks/codegen.ts"],"sourcesContent":["import { createColors } from 'colorette';\nimport { spawn } from 'node:child_process';\nimport { mkdtemp, readFile, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ProjectArgs } from '../args.js';\nimport { resolveCwd } from '../args.js';\nimport { loadStarsConfig } from '@wolfstar/http-framework/config';\nimport { displayPath, type ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport { CliError, ExitCode } from '../errors.js';\nimport { shouldUseColor } from '../output-mode.js';\nimport { resolveFromProject } from '../project.js';\n\nexport interface CodegenTaskOptions extends ProjectArgs {\n\tcheck?: boolean;\n\tjson?: boolean;\n\tstdout?: NodeJS.WritableStream;\n}\n\nexport interface CodegenResult {\n\tgenerator: 'i18n';\n\toutput: string;\n\tstatus: 'written' | 'up-to-date' | 'outdated';\n}\n\nexport async function runCodegen(options: CodegenTaskOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() && !options.json });\n\tconst config = await loadStarsConfig({ cwd: resolveCwd(options), configFile: options.config });\n\tconst results: CodegenResult[] = [];\n\n\tif (config.codegen.i18n) results.push(await runI18n(config, Boolean(options.check)));\n\n\tif (options.json) {\n\t\tstdout.write(`${JSON.stringify({ check: Boolean(options.check), results }, null, 2)}\\n`);\n\t} else if (results.length === 0) {\n\t\tstdout.write(`${colors.dim('stars')} nothing to generate (no code generators are configured)\\n`);\n\t} else {\n\t\tfor (const result of results) {\n\t\t\tconst paint = result.status === 'outdated' ? colors.red : colors.green;\n\t\t\tstdout.write(`${colors.dim('stars')} ${result.generator}: ${paint(result.status)} ${displayPath(config.root, result.output)}\\n`);\n\t\t}\n\t}\n\n\tif (results.some((result) => result.status === 'outdated')) {\n\t\tthrow new CliError('Generated files are out of date, run `stars codegen` to update them.', {\n\t\t\tcode: 'CODEGEN_OUTDATED',\n\t\t\texitCode: ExitCode.Error\n\t\t});\n\t}\n}\n\nasync function runI18n(config: ResolvedStarsConfig, check: boolean): Promise<CodegenResult> {\n\tconst { locales, output } = config.codegen.i18n!;\n\tconst cli = resolveFromProject(config.root, '@wolfstar/i18next-type-generator');\n\tif (!cli) {\n\t\tthrow new CliError(`\"@wolfstar/i18next-type-generator\" is not installed in ${config.root}`, {\n\t\t\tcode: 'DEPENDENCY_MISSING',\n\t\t\thint: 'Install it with `pnpm add -D @wolfstar/i18next-type-generator`, or set `codegen.i18n` to false.'\n\t\t});\n\t}\n\n\tif (!check) {\n\t\tawait generate(config.root, cli, locales, output);\n\t\treturn { generator: 'i18n', output, status: 'written' };\n\t}\n\n\tconst directory = await mkdtemp(join(tmpdir(), 'stars-codegen-'));\n\ttry {\n\t\tconst candidate = join(directory, 'i18next.d.ts');\n\t\tawait generate(config.root, cli, locales, candidate);\n\t\tconst [expected, actual] = await Promise.all([readFile(candidate, 'utf-8'), readFile(output, 'utf-8').catch(() => null)]);\n\t\treturn { generator: 'i18n', output, status: expected === actual ? 'up-to-date' : 'outdated' };\n\t} finally {\n\t\tawait rm(directory, { recursive: true, force: true });\n\t}\n}\n\nfunction generate(root: string, cli: string, locales: string, output: string): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst child = spawn(process.execPath, [cli, locales, output], { cwd: root, stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });\n\t\tlet stderr = '';\n\t\tchild.stderr?.on('data', (chunk: Buffer) => (stderr += chunk.toString()));\n\t\tchild.once('error', reject);\n\t\tchild.once('exit', (code) => {\n\t\t\tif (code === 0) resolve();\n\t\t\telse\n\t\t\t\treject(\n\t\t\t\t\tnew CliError(`i18next-type-generator exited with code ${code}${stderr ? `: ${stderr.trim()}` : ''}`, { code: 'CODEGEN_FAILED' })\n\t\t\t\t);\n\t\t});\n\t});\n}\n"],"mappings":";;;;;;;;;;;;AAyBA,eAAsB,WAAW,SAA4C;CAC5E,MAAM,SAAS,QAAQ,UAAU,QAAQ;CACzC,MAAM,SAAS,aAAa,EAAE,UAAU,eAAe,KAAK,CAAC,QAAQ,KAAK,CAAC;CAC3E,MAAM,SAAS,MAAM,gBAAgB;EAAE,KAAK,WAAW,OAAO;EAAG,YAAY,QAAQ;CAAO,CAAC;CAC7F,MAAM,UAA2B,CAAC;CAElC,IAAI,OAAO,QAAQ,MAAM,QAAQ,KAAK,MAAM,QAAQ,QAAQ,QAAQ,QAAQ,KAAK,CAAC,CAAC;CAEnF,IAAI,QAAQ,MACX,OAAO,MAAM,GAAG,KAAK,UAAU;EAAE,OAAO,QAAQ,QAAQ,KAAK;EAAG;CAAQ,GAAG,MAAM,CAAC,EAAE,GAAG;MACjF,IAAI,QAAQ,WAAW,GAC7B,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,2DAA2D;MAE/F,KAAK,MAAM,UAAU,SAAS;EAC7B,MAAM,QAAQ,OAAO,WAAW,aAAa,OAAO,MAAM,OAAO;EACjE,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,UAAU,IAAI,MAAM,OAAO,MAAM,EAAE,GAAG,YAAY,OAAO,MAAM,OAAO,MAAM,EAAE,GAAG;CAChI;CAGD,IAAI,QAAQ,MAAM,WAAW,OAAO,WAAW,UAAU,GACxD,MAAM,IAAI,SAAS,wEAAwE;EAC1F,MAAM;EACN,UAAU,SAAS;CACpB,CAAC;AAEH;AAEA,eAAe,QAAQ,QAA6B,OAAwC;CAC3F,MAAM,EAAE,SAAS,WAAW,OAAO,QAAQ;CAC3C,MAAM,MAAM,mBAAmB,OAAO,MAAM,kCAAkC;CAC9E,IAAI,CAAC,KACJ,MAAM,IAAI,SAAS,0DAA0D,OAAO,QAAQ;EAC3F,MAAM;EACN,MAAM;CACP,CAAC;CAGF,IAAI,CAAC,OAAO;EACX,MAAM,SAAS,OAAO,MAAM,KAAK,SAAS,MAAM;EAChD,OAAO;GAAE,WAAW;GAAQ;GAAQ,QAAQ;EAAU;CACvD;CAEA,MAAM,YAAY,MAAM,QAAQ,KAAK,OAAO,GAAG,gBAAgB,CAAC;CAChE,IAAI;EACH,MAAM,YAAY,KAAK,WAAW,cAAc;EAChD,MAAM,SAAS,OAAO,MAAM,KAAK,SAAS,SAAS;EACnD,MAAM,CAAC,UAAU,UAAU,MAAM,QAAQ,IAAI,CAAC,SAAS,WAAW,OAAO,GAAG,SAAS,QAAQ,OAAO,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC;EACxH,OAAO;GAAE,WAAW;GAAQ;GAAQ,QAAQ,aAAa,SAAS,eAAe;EAAW;CAC7F,UAAU;EACT,MAAM,GAAG,WAAW;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;CACrD;AACD;AAEA,SAAS,SAAS,MAAc,KAAa,SAAiB,QAA+B;CAC5F,OAAO,IAAI,SAAS,SAAS,WAAW;EACvC,MAAM,QAAQ,MAAM,QAAQ,UAAU;GAAC;GAAK;GAAS;EAAM,GAAG;GAAE,KAAK;GAAM,OAAO;IAAC;IAAU;IAAU;GAAM;GAAG,aAAa;EAAK,CAAC;EACnI,IAAI,SAAS;EACb,MAAM,QAAQ,GAAG,SAAS,UAAmB,UAAU,MAAM,SAAS,CAAE;EACxE,MAAM,KAAK,SAAS,MAAM;EAC1B,MAAM,KAAK,SAAS,SAAS;GAC5B,IAAI,SAAS,GAAG,QAAQ;QAEvB,OACC,IAAI,SAAS,2CAA2C,OAAO,SAAS,KAAK,OAAO,KAAK,MAAM,MAAM,EAAE,MAAM,iBAAiB,CAAC,CAChI;EACF,CAAC;CACF,CAAC;AACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { t as projectArgs } from "./args-Ch1KXUMn.js";
|
|
2
|
+
import { defineCommand } from "citty";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/codegen.ts
|
|
5
|
+
var codegen_default = defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "codegen",
|
|
8
|
+
description: "Run the configured code generators (i18next types)"
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
...projectArgs,
|
|
12
|
+
check: {
|
|
13
|
+
type: "boolean",
|
|
14
|
+
description: "Fail when the generated files are out of date instead of writing them",
|
|
15
|
+
default: false
|
|
16
|
+
},
|
|
17
|
+
json: {
|
|
18
|
+
type: "boolean",
|
|
19
|
+
description: "Print machine-readable JSON",
|
|
20
|
+
default: false
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
async run({ args }) {
|
|
24
|
+
const { runCodegen } = await import("./codegen-DjPOn7cp.js");
|
|
25
|
+
await runCodegen({
|
|
26
|
+
config: args.config,
|
|
27
|
+
cwd: args.cwd,
|
|
28
|
+
check: args.check,
|
|
29
|
+
json: args.json
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { codegen_default as default };
|
|
36
|
+
//# sourceMappingURL=codegen-mYkL-GBf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen-mYkL-GBf.js","names":[],"sources":["../src/commands/codegen.ts"],"sourcesContent":["import { defineCommand } from 'citty';\nimport { projectArgs } from '../lib/args.js';\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'codegen',\n\t\tdescription: 'Run the configured code generators (i18next types)'\n\t},\n\targs: {\n\t\t...projectArgs,\n\t\tcheck: {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Fail when the generated files are out of date instead of writing them',\n\t\t\tdefault: false\n\t\t},\n\t\tjson: {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Print machine-readable JSON',\n\t\t\tdefault: false\n\t\t}\n\t},\n\tasync run({ args }) {\n\t\tconst { runCodegen } = await import('../lib/tasks/codegen.js');\n\t\tawait runCodegen({ config: args.config, cwd: args.cwd, check: args.check, json: args.json });\n\t}\n});\n"],"mappings":";;;;AAGA,sBAAe,cAAc;CAC5B,MAAM;EACL,MAAM;EACN,aAAa;CACd;CACA,MAAM;EACL,GAAG;EACH,OAAO;GACN,MAAM;GACN,aAAa;GACb,SAAS;EACV;EACA,MAAM;GACL,MAAM;GACN,aAAa;GACb,SAAS;EACV;CACD;CACA,MAAM,IAAI,EAAE,QAAQ;EACnB,MAAM,EAAE,eAAe,MAAM,OAAO;EACpC,MAAM,WAAW;GAAE,QAAQ,KAAK;GAAQ,KAAK,KAAK;GAAK,OAAO,KAAK;GAAO,MAAM,KAAK;EAAK,CAAC;CAC5F;AACD,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { t as projectArgs } from "./args-Ch1KXUMn.js";
|
|
2
|
+
import { defineCommand } from "citty";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/commands.ts
|
|
5
|
+
const scopeArgs = {
|
|
6
|
+
...projectArgs,
|
|
7
|
+
guild: {
|
|
8
|
+
type: "string",
|
|
9
|
+
description: "Work on a guild’s commands instead of the global ones"
|
|
10
|
+
},
|
|
11
|
+
json: {
|
|
12
|
+
type: "boolean",
|
|
13
|
+
description: "Print machine-readable JSON",
|
|
14
|
+
default: false
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const list = defineCommand({
|
|
18
|
+
meta: {
|
|
19
|
+
name: "list",
|
|
20
|
+
description: "List the application commands Discord has deployed"
|
|
21
|
+
},
|
|
22
|
+
args: scopeArgs,
|
|
23
|
+
async run({ args }) {
|
|
24
|
+
const { runCommandsList } = await import("./commands-Dw2LFWV-.js");
|
|
25
|
+
await runCommandsList({
|
|
26
|
+
config: args.config,
|
|
27
|
+
cwd: args.cwd,
|
|
28
|
+
guild: args.guild,
|
|
29
|
+
json: args.json
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const clean = defineCommand({
|
|
34
|
+
meta: {
|
|
35
|
+
name: "clean",
|
|
36
|
+
description: "Delete deployed application commands (all of them, or the named ones)"
|
|
37
|
+
},
|
|
38
|
+
args: {
|
|
39
|
+
...scopeArgs,
|
|
40
|
+
name: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "Only delete the command with this name (repeatable)"
|
|
43
|
+
},
|
|
44
|
+
yes: {
|
|
45
|
+
type: "boolean",
|
|
46
|
+
alias: "y",
|
|
47
|
+
description: "Delete without asking for a confirmation",
|
|
48
|
+
default: false
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
async run({ args }) {
|
|
52
|
+
const { runCommandsClean } = await import("./commands-Dw2LFWV-.js");
|
|
53
|
+
const names = Array.isArray(args.name) ? args.name : args.name ? [args.name] : [];
|
|
54
|
+
await runCommandsClean({
|
|
55
|
+
config: args.config,
|
|
56
|
+
cwd: args.cwd,
|
|
57
|
+
guild: args.guild,
|
|
58
|
+
json: args.json,
|
|
59
|
+
names,
|
|
60
|
+
yes: args.yes
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
var commands_default = defineCommand({
|
|
65
|
+
meta: {
|
|
66
|
+
name: "commands",
|
|
67
|
+
description: "Inspect and clean the application commands deployed to Discord"
|
|
68
|
+
},
|
|
69
|
+
subCommands: {
|
|
70
|
+
list,
|
|
71
|
+
clean
|
|
72
|
+
},
|
|
73
|
+
args: scopeArgs,
|
|
74
|
+
async run({ args, rawArgs }) {
|
|
75
|
+
if (rawArgs.some((argument) => argument === "list" || argument === "clean")) return;
|
|
76
|
+
const { runCommandsList } = await import("./commands-Dw2LFWV-.js");
|
|
77
|
+
await runCommandsList({
|
|
78
|
+
config: args.config,
|
|
79
|
+
cwd: args.cwd,
|
|
80
|
+
guild: args.guild,
|
|
81
|
+
json: args.json
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
export { commands_default as default };
|
|
88
|
+
//# sourceMappingURL=commands-BkGKs_Bg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands-BkGKs_Bg.js","names":[],"sources":["../src/commands/commands.ts"],"sourcesContent":["import { defineCommand } from 'citty';\nimport { projectArgs } from '../lib/args.js';\n\nconst scopeArgs = {\n\t...projectArgs,\n\tguild: {\n\t\ttype: 'string',\n\t\tdescription: 'Work on a guild’s commands instead of the global ones'\n\t},\n\tjson: {\n\t\ttype: 'boolean',\n\t\tdescription: 'Print machine-readable JSON',\n\t\tdefault: false\n\t}\n} as const;\n\nconst list = defineCommand({\n\tmeta: { name: 'list', description: 'List the application commands Discord has deployed' },\n\targs: scopeArgs,\n\tasync run({ args }) {\n\t\tconst { runCommandsList } = await import('../lib/tasks/commands.js');\n\t\tawait runCommandsList({ config: args.config, cwd: args.cwd, guild: args.guild, json: args.json });\n\t}\n});\n\nconst clean = defineCommand({\n\tmeta: { name: 'clean', description: 'Delete deployed application commands (all of them, or the named ones)' },\n\targs: {\n\t\t...scopeArgs,\n\t\tname: {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'Only delete the command with this name (repeatable)'\n\t\t},\n\t\tyes: {\n\t\t\ttype: 'boolean',\n\t\t\talias: 'y',\n\t\t\tdescription: 'Delete without asking for a confirmation',\n\t\t\tdefault: false\n\t\t}\n\t},\n\tasync run({ args }) {\n\t\tconst { runCommandsClean } = await import('../lib/tasks/commands.js');\n\t\tconst names = Array.isArray(args.name) ? args.name : args.name ? [args.name] : [];\n\t\tawait runCommandsClean({ config: args.config, cwd: args.cwd, guild: args.guild, json: args.json, names, yes: args.yes });\n\t}\n});\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'commands',\n\t\tdescription: 'Inspect and clean the application commands deployed to Discord'\n\t},\n\tsubCommands: { list, clean },\n\targs: scopeArgs,\n\tasync run({ args, rawArgs }) {\n\t\t// `stars commands` with no subcommand lists, the way `git branch` does.\n\t\tif (rawArgs.some((argument) => argument === 'list' || argument === 'clean')) return;\n\n\t\tconst { runCommandsList } = await import('../lib/tasks/commands.js');\n\t\tawait runCommandsList({ config: args.config, cwd: args.cwd, guild: args.guild, json: args.json });\n\t}\n});\n"],"mappings":";;;;AAGA,MAAM,YAAY;CACjB,GAAG;CACH,OAAO;EACN,MAAM;EACN,aAAa;CACd;CACA,MAAM;EACL,MAAM;EACN,aAAa;EACb,SAAS;CACV;AACD;AAEA,MAAM,OAAO,cAAc;CAC1B,MAAM;EAAE,MAAM;EAAQ,aAAa;CAAqD;CACxF,MAAM;CACN,MAAM,IAAI,EAAE,QAAQ;EACnB,MAAM,EAAE,oBAAoB,MAAM,OAAO;EACzC,MAAM,gBAAgB;GAAE,QAAQ,KAAK;GAAQ,KAAK,KAAK;GAAK,OAAO,KAAK;GAAO,MAAM,KAAK;EAAK,CAAC;CACjG;AACD,CAAC;AAED,MAAM,QAAQ,cAAc;CAC3B,MAAM;EAAE,MAAM;EAAS,aAAa;CAAwE;CAC5G,MAAM;EACL,GAAG;EACH,MAAM;GACL,MAAM;GACN,aAAa;EACd;EACA,KAAK;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;EACV;CACD;CACA,MAAM,IAAI,EAAE,QAAQ;EACnB,MAAM,EAAE,qBAAqB,MAAM,OAAO;EAC1C,MAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,OAAO,KAAK,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC;EAChF,MAAM,iBAAiB;GAAE,QAAQ,KAAK;GAAQ,KAAK,KAAK;GAAK,OAAO,KAAK;GAAO,MAAM,KAAK;GAAM;GAAO,KAAK,KAAK;EAAI,CAAC;CACxH;AACD,CAAC;AAED,uBAAe,cAAc;CAC5B,MAAM;EACL,MAAM;EACN,aAAa;CACd;CACA,aAAa;EAAE;EAAM;CAAM;CAC3B,MAAM;CACN,MAAM,IAAI,EAAE,MAAM,WAAW;EAE5B,IAAI,QAAQ,MAAM,aAAa,aAAa,UAAU,aAAa,OAAO,GAAG;EAE7E,MAAM,EAAE,oBAAoB,MAAM,OAAO;EACzC,MAAM,gBAAgB;GAAE,QAAQ,KAAK;GAAQ,KAAK,KAAK;GAAK,OAAO,KAAK;GAAO,MAAM,KAAK;EAAK,CAAC;CACjG;AACD,CAAC"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { t as CliError } from "./errors-Bket6XFn.js";
|
|
2
|
+
import { i as shouldUseColor } from "./output-mode-ChrOf0LO.js";
|
|
3
|
+
import { n as resolveCwd } from "./args-Ch1KXUMn.js";
|
|
4
|
+
import { n as readDiscordCredentials } from "./tunnel-B8Id4-lR.js";
|
|
5
|
+
import { loadStarsConfig } from "@wolfstar/http-framework/config";
|
|
6
|
+
import { createColors } from "colorette";
|
|
7
|
+
|
|
8
|
+
//#region src/lib/discord.ts
|
|
9
|
+
const API = "https://discord.com/api/v10";
|
|
10
|
+
const COMMAND_TYPE_NAMES = {
|
|
11
|
+
1: "chat input",
|
|
12
|
+
2: "user",
|
|
13
|
+
3: "message"
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A minimal Discord REST client for what `stars commands` manages. The credentials come from the environment or the
|
|
17
|
+
* project's `.env`, the same place the bot reads them from once it starts.
|
|
18
|
+
*/
|
|
19
|
+
function createDiscordClient(config, env = process.env) {
|
|
20
|
+
const credentials = readDiscordCredentials(config, env);
|
|
21
|
+
if (!credentials) throw new CliError("DISCORD_TOKEN is not set", {
|
|
22
|
+
code: "DISCORD_TOKEN_MISSING",
|
|
23
|
+
hint: "Set DISCORD_TOKEN in the environment or in the project .env file."
|
|
24
|
+
});
|
|
25
|
+
if (!credentials.applicationId) throw new CliError("The Discord application id is not set", {
|
|
26
|
+
code: "DISCORD_APPLICATION_ID_MISSING",
|
|
27
|
+
hint: "Set DISCORD_APPLICATION_ID (or APPLICATION_ID) in the environment or in the project .env file."
|
|
28
|
+
});
|
|
29
|
+
const applicationId = credentials.applicationId;
|
|
30
|
+
const scope = (guildId) => guildId ? `/applications/${applicationId}/guilds/${guildId}/commands` : `/applications/${applicationId}/commands`;
|
|
31
|
+
const request = async (method, path) => {
|
|
32
|
+
const response = await fetch(`${API}${path}`, {
|
|
33
|
+
method,
|
|
34
|
+
headers: { authorization: `Bot ${credentials.token}` },
|
|
35
|
+
signal: AbortSignal.timeout(15e3)
|
|
36
|
+
});
|
|
37
|
+
if (response.status === 204) return null;
|
|
38
|
+
const body = await response.json().catch(() => null);
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
const detail = typeof body === "object" && body !== null && "message" in body ? String(body.message) : "";
|
|
41
|
+
throw new CliError(`Discord answered ${response.status}${detail ? `: ${detail}` : ""}`, {
|
|
42
|
+
code: "DISCORD_REQUEST_FAILED",
|
|
43
|
+
hint: response.status === 401 ? "Check DISCORD_TOKEN." : "Check the application id and the bot permissions."
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return body;
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
applicationId,
|
|
50
|
+
async listCommands(guildId) {
|
|
51
|
+
const body = await request("GET", scope(guildId));
|
|
52
|
+
return Array.isArray(body) ? body : [];
|
|
53
|
+
},
|
|
54
|
+
async deleteCommand(guildId, commandId) {
|
|
55
|
+
await request("DELETE", `${scope(guildId)}/${commandId}`);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/lib/prompts.ts
|
|
62
|
+
/**
|
|
63
|
+
* The interactive wizard behind `stars commands clean`: a checklist of what Discord has deployed, then a
|
|
64
|
+
* confirmation, so a destructive cleanup is never one keystroke away. `@clack/prompts` is loaded lazily — the
|
|
65
|
+
* non-interactive paths (`--yes`, `--json`, CI) never pay for it.
|
|
66
|
+
*/
|
|
67
|
+
function createClackPrompt() {
|
|
68
|
+
return {
|
|
69
|
+
async pick(commands, scope) {
|
|
70
|
+
const { isCancel, multiselect } = await import("@clack/prompts");
|
|
71
|
+
const answer = await multiselect({
|
|
72
|
+
message: `Which commands should be deleted from ${scope}?`,
|
|
73
|
+
options: commands.map((command) => ({
|
|
74
|
+
value: command.id,
|
|
75
|
+
label: command.name,
|
|
76
|
+
hint: [describeType(command), command.id].filter(Boolean).join(" · ")
|
|
77
|
+
})),
|
|
78
|
+
required: false
|
|
79
|
+
});
|
|
80
|
+
if (isCancel(answer)) throw aborted();
|
|
81
|
+
return answer;
|
|
82
|
+
},
|
|
83
|
+
async confirm(message) {
|
|
84
|
+
const { confirm, isCancel } = await import("@clack/prompts");
|
|
85
|
+
const answer = await confirm({
|
|
86
|
+
message,
|
|
87
|
+
initialValue: false
|
|
88
|
+
});
|
|
89
|
+
if (isCancel(answer)) throw aborted();
|
|
90
|
+
return answer;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function describeType(command) {
|
|
95
|
+
switch (command.type) {
|
|
96
|
+
case 2: return "user";
|
|
97
|
+
case 3: return "message";
|
|
98
|
+
default: return "chat input";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function aborted() {
|
|
102
|
+
return new CliError("Aborted", { code: "ABORTED" });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/lib/tasks/commands.ts
|
|
107
|
+
/**
|
|
108
|
+
* Lists the application commands Discord currently has deployed, which is not necessarily what the project would
|
|
109
|
+
* register today: renamed and removed commands stay until something deletes them.
|
|
110
|
+
*/
|
|
111
|
+
async function runCommandsList(options) {
|
|
112
|
+
const stdout = options.stdout ?? process.stdout;
|
|
113
|
+
const colors = createColors({ useColor: shouldUseColor() && !options.json });
|
|
114
|
+
const { client, guild } = await connect(options);
|
|
115
|
+
const commands = await client.listCommands(guild);
|
|
116
|
+
if (options.json) {
|
|
117
|
+
stdout.write(`${JSON.stringify({
|
|
118
|
+
applicationId: client.applicationId,
|
|
119
|
+
guildId: guild,
|
|
120
|
+
commands
|
|
121
|
+
}, null, 2)}\n`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (commands.length === 0) {
|
|
125
|
+
stdout.write(`${colors.dim("stars")} no ${guild ? `commands in guild ${guild}` : "global commands"} are deployed\n`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
stdout.write(`${colors.dim("stars")} ${commands.length} ${guild ? `command(s) in guild ${guild}` : "global command(s)"}\n`);
|
|
129
|
+
for (const command of commands) stdout.write(` ${colors.bold(describeName(command))} ${colors.dim(command.id)}\n`);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Deletes deployed application commands. Discord keeps whatever was registered last, so this is the way to clear
|
|
133
|
+
* commands a project no longer defines (or a whole guild's test deployment).
|
|
134
|
+
*/
|
|
135
|
+
async function runCommandsClean(options) {
|
|
136
|
+
const stdout = options.stdout ?? process.stdout;
|
|
137
|
+
const colors = createColors({ useColor: shouldUseColor() && !options.json });
|
|
138
|
+
const { client, guild } = await connect(options);
|
|
139
|
+
const deployed = await client.listCommands(guild);
|
|
140
|
+
const wanted = options.names?.filter((name) => name.length > 0) ?? [];
|
|
141
|
+
const missing = wanted.filter((name) => !deployed.some((command) => command.name === name));
|
|
142
|
+
if (missing.length > 0) throw new CliError(`No deployed command is named ${missing.join(", ")}`, {
|
|
143
|
+
code: "COMMAND_NOT_FOUND",
|
|
144
|
+
hint: "Run `stars commands list` to see what is deployed."
|
|
145
|
+
});
|
|
146
|
+
if (deployed.length === 0) {
|
|
147
|
+
if (options.json) stdout.write(`${JSON.stringify({ deleted: [] }, null, 2)}\n`);
|
|
148
|
+
else stdout.write(`${colors.dim("stars")} nothing to delete\n`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const targets = await selectTargets(options, deployed, wanted, guild ? `guild ${guild}` : "the global scope", stdout, colors);
|
|
152
|
+
if (targets.length === 0) {
|
|
153
|
+
if (options.json) stdout.write(`${JSON.stringify({ deleted: [] }, null, 2)}\n`);
|
|
154
|
+
else stdout.write(`${colors.dim("stars")} nothing to delete\n`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
for (const command of targets) {
|
|
158
|
+
await client.deleteCommand(guild, command.id);
|
|
159
|
+
if (!options.json) stdout.write(`${colors.dim("stars")} ${colors.red("deleted")} ${describeName(command)}\n`);
|
|
160
|
+
}
|
|
161
|
+
if (options.json) stdout.write(`${JSON.stringify({ deleted: targets.map((command) => ({
|
|
162
|
+
id: command.id,
|
|
163
|
+
name: command.name
|
|
164
|
+
})) }, null, 2)}\n`);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Works out what to delete: `--name` (and `--yes`) keep the command scriptable, while a bare `stars commands clean`
|
|
168
|
+
* on a terminal runs the wizard — a checklist of what Discord has deployed, then a confirmation.
|
|
169
|
+
*/
|
|
170
|
+
async function selectTargets(options, deployed, wanted, scope, stdout, colors) {
|
|
171
|
+
if (wanted.length > 0) {
|
|
172
|
+
const named = deployed.filter((command) => wanted.includes(command.name));
|
|
173
|
+
if (options.yes) return named;
|
|
174
|
+
await confirmOrThrow(options, stdout, colors, `delete ${colors.bold(String(named.length))} command(s) from ${scope}?`);
|
|
175
|
+
return named;
|
|
176
|
+
}
|
|
177
|
+
if (options.yes) return deployed;
|
|
178
|
+
const stdin = options.stdin ?? process.stdin;
|
|
179
|
+
if (!options.prompt && !stdin.isTTY) throw confirmationRequired();
|
|
180
|
+
const prompt = options.prompt ?? createClackPrompt();
|
|
181
|
+
const chosen = new Set(await prompt.pick(deployed, scope));
|
|
182
|
+
const targets = deployed.filter((command) => chosen.has(command.id));
|
|
183
|
+
if (targets.length === 0) return [];
|
|
184
|
+
const names = targets.map((command) => command.name).join(", ");
|
|
185
|
+
if (!await prompt.confirm(`Delete ${targets.length} command(s) from ${scope}: ${names}?`)) throw new CliError("Aborted", { code: "ABORTED" });
|
|
186
|
+
return targets;
|
|
187
|
+
}
|
|
188
|
+
async function connect(options) {
|
|
189
|
+
const config = await loadStarsConfig({
|
|
190
|
+
cwd: resolveCwd(options),
|
|
191
|
+
configFile: options.config
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
client: options.client ?? createDiscordClient(config),
|
|
195
|
+
guild: options.guild ?? null
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function describeName(command) {
|
|
199
|
+
const type = command.type && command.type !== 1 ? ` (${COMMAND_TYPE_NAMES[command.type] ?? `type ${command.type}`})` : "";
|
|
200
|
+
return `${command.name}${type}`;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Deleting deployed commands is not reversible, so a terminal run asks first; scripts pass `--yes`.
|
|
204
|
+
*/
|
|
205
|
+
async function confirmOrThrow(options, stdout, colors, question) {
|
|
206
|
+
if (options.prompt) {
|
|
207
|
+
if (await options.prompt.confirm(question)) return;
|
|
208
|
+
throw new CliError("Aborted", { code: "ABORTED" });
|
|
209
|
+
}
|
|
210
|
+
const stdin = options.stdin ?? process.stdin;
|
|
211
|
+
if (!stdin.isTTY) throw confirmationRequired();
|
|
212
|
+
stdout.write(`${colors.dim("stars")} ${question} [y/N] `);
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
stdin.setEncoding?.("utf-8");
|
|
215
|
+
stdin.once("data", (chunk) => {
|
|
216
|
+
stdin.pause?.();
|
|
217
|
+
const answer = chunk.trim().toLowerCase();
|
|
218
|
+
if (answer === "y" || answer === "yes") resolve();
|
|
219
|
+
else reject(new CliError("Aborted", { code: "ABORTED" }));
|
|
220
|
+
});
|
|
221
|
+
stdin.resume?.();
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function confirmationRequired() {
|
|
225
|
+
return new CliError("Refusing to delete commands without a confirmation", {
|
|
226
|
+
code: "CONFIRMATION_REQUIRED",
|
|
227
|
+
hint: "Pass --yes to delete them, or --name to pick one, from a script."
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
//#endregion
|
|
232
|
+
export { runCommandsClean, runCommandsList };
|
|
233
|
+
//# sourceMappingURL=commands-Dw2LFWV-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands-Dw2LFWV-.js","names":[],"sources":["../src/lib/discord.ts","../src/lib/prompts.ts","../src/lib/tasks/commands.ts"],"sourcesContent":["import type { ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport { CliError } from './errors.js';\nimport { readDiscordCredentials } from './tunnel.js';\n\nconst API = 'https://discord.com/api/v10';\n\nexport interface ApplicationCommand {\n\tid: string;\n\tapplication_id: string;\n\tname: string;\n\tdescription?: string;\n\ttype?: number;\n\tguild_id?: string;\n\tversion?: string;\n}\n\nexport interface DiscordClient {\n\tapplicationId: string;\n\tlistCommands(guildId: string | null): Promise<ApplicationCommand[]>;\n\tdeleteCommand(guildId: string | null, commandId: string): Promise<void>;\n}\n\nexport const COMMAND_TYPE_NAMES: Record<number, string> = { 1: 'chat input', 2: 'user', 3: 'message' };\n\n/**\n * A minimal Discord REST client for what `stars commands` manages. The credentials come from the environment or the\n * project's `.env`, the same place the bot reads them from once it starts.\n */\nexport function createDiscordClient(config: ResolvedStarsConfig, env: NodeJS.ProcessEnv = process.env): DiscordClient {\n\tconst credentials = readDiscordCredentials(config, env);\n\tif (!credentials) {\n\t\tthrow new CliError('DISCORD_TOKEN is not set', {\n\t\t\tcode: 'DISCORD_TOKEN_MISSING',\n\t\t\thint: 'Set DISCORD_TOKEN in the environment or in the project .env file.'\n\t\t});\n\t}\n\n\tif (!credentials.applicationId) {\n\t\tthrow new CliError('The Discord application id is not set', {\n\t\t\tcode: 'DISCORD_APPLICATION_ID_MISSING',\n\t\t\thint: 'Set DISCORD_APPLICATION_ID (or APPLICATION_ID) in the environment or in the project .env file.'\n\t\t});\n\t}\n\n\tconst applicationId = credentials.applicationId;\n\tconst scope = (guildId: string | null) =>\n\t\tguildId ? `/applications/${applicationId}/guilds/${guildId}/commands` : `/applications/${applicationId}/commands`;\n\n\tconst request = async (method: string, path: string): Promise<unknown> => {\n\t\tconst response = await fetch(`${API}${path}`, {\n\t\t\tmethod,\n\t\t\theaders: { authorization: `Bot ${credentials.token}` },\n\t\t\tsignal: AbortSignal.timeout(15_000)\n\t\t});\n\n\t\tif (response.status === 204) return null;\n\n\t\tconst body: unknown = await response.json().catch(() => null);\n\t\tif (!response.ok) {\n\t\t\tconst detail = typeof body === 'object' && body !== null && 'message' in body ? String((body as { message: unknown }).message) : '';\n\t\t\tthrow new CliError(`Discord answered ${response.status}${detail ? `: ${detail}` : ''}`, {\n\t\t\t\tcode: 'DISCORD_REQUEST_FAILED',\n\t\t\t\thint: response.status === 401 ? 'Check DISCORD_TOKEN.' : 'Check the application id and the bot permissions.'\n\t\t\t});\n\t\t}\n\n\t\treturn body;\n\t};\n\n\treturn {\n\t\tapplicationId,\n\t\tasync listCommands(guildId) {\n\t\t\tconst body = await request('GET', scope(guildId));\n\t\t\treturn Array.isArray(body) ? (body as ApplicationCommand[]) : [];\n\t\t},\n\t\tasync deleteCommand(guildId, commandId) {\n\t\t\tawait request('DELETE', `${scope(guildId)}/${commandId}`);\n\t\t}\n\t};\n}\n","import type { ApplicationCommand } from './discord.js';\nimport { CliError } from './errors.js';\n\nexport interface CommandsPrompt {\n\t/** Asks which of the deployed commands to delete. Returns the chosen ids. */\n\tpick(commands: readonly ApplicationCommand[], scope: string): Promise<string[]>;\n\t/** Final go/no-go before anything is deleted. */\n\tconfirm(message: string): Promise<boolean>;\n}\n\n/**\n * The interactive wizard behind `stars commands clean`: a checklist of what Discord has deployed, then a\n * confirmation, so a destructive cleanup is never one keystroke away. `@clack/prompts` is loaded lazily — the\n * non-interactive paths (`--yes`, `--json`, CI) never pay for it.\n */\nexport function createClackPrompt(): CommandsPrompt {\n\treturn {\n\t\tasync pick(commands, scope) {\n\t\t\tconst { isCancel, multiselect } = await import('@clack/prompts');\n\t\t\tconst answer = await multiselect({\n\t\t\t\tmessage: `Which commands should be deleted from ${scope}?`,\n\t\t\t\toptions: commands.map((command) => ({\n\t\t\t\t\tvalue: command.id,\n\t\t\t\t\tlabel: command.name,\n\t\t\t\t\thint: [describeType(command), command.id].filter(Boolean).join(' · ')\n\t\t\t\t})),\n\t\t\t\trequired: false\n\t\t\t});\n\n\t\t\tif (isCancel(answer)) throw aborted();\n\t\t\treturn answer as string[];\n\t\t},\n\t\tasync confirm(message) {\n\t\t\tconst { confirm, isCancel } = await import('@clack/prompts');\n\t\t\tconst answer = await confirm({ message, initialValue: false });\n\n\t\t\tif (isCancel(answer)) throw aborted();\n\t\t\treturn answer;\n\t\t}\n\t};\n}\n\nfunction describeType(command: ApplicationCommand): string {\n\tswitch (command.type) {\n\t\tcase 2:\n\t\t\treturn 'user';\n\t\tcase 3:\n\t\t\treturn 'message';\n\t\tdefault:\n\t\t\treturn 'chat input';\n\t}\n}\n\nfunction aborted(): CliError {\n\treturn new CliError('Aborted', { code: 'ABORTED' });\n}\n","import { loadStarsConfig } from '@wolfstar/http-framework/config';\nimport { createColors } from 'colorette';\nimport type { ProjectArgs } from '../args.js';\nimport { resolveCwd } from '../args.js';\nimport { COMMAND_TYPE_NAMES, createDiscordClient, type ApplicationCommand, type DiscordClient } from '../discord.js';\nimport { CliError } from '../errors.js';\nimport { shouldUseColor } from '../output-mode.js';\nimport { createClackPrompt, type CommandsPrompt } from '../prompts.js';\n\nexport interface CommandsTaskOptions extends ProjectArgs {\n\t/** A guild id to work on the guild-scoped commands instead of the global ones. */\n\tguild?: string;\n\tjson?: boolean;\n\tstdout?: NodeJS.WritableStream;\n\t/** Overrides the client, for tests. */\n\tclient?: DiscordClient;\n}\n\nexport interface CommandsCleanOptions extends CommandsTaskOptions {\n\t/** Names to delete; without any, the wizard asks which of the deployed commands to remove. */\n\tnames?: string[];\n\t/** Deletes without asking. Required in a non-interactive terminal. */\n\tyes?: boolean;\n\tstdin?: NodeJS.ReadableStream & { isTTY?: boolean };\n\t/** Overrides the interactive wizard, for tests. */\n\tprompt?: CommandsPrompt;\n}\n\n/**\n * Lists the application commands Discord currently has deployed, which is not necessarily what the project would\n * register today: renamed and removed commands stay until something deletes them.\n */\nexport async function runCommandsList(options: CommandsTaskOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() && !options.json });\n\tconst { client, guild } = await connect(options);\n\tconst commands = await client.listCommands(guild);\n\n\tif (options.json) {\n\t\tstdout.write(`${JSON.stringify({ applicationId: client.applicationId, guildId: guild, commands }, null, 2)}\\n`);\n\t\treturn;\n\t}\n\n\tif (commands.length === 0) {\n\t\tstdout.write(`${colors.dim('stars')} no ${guild ? `commands in guild ${guild}` : 'global commands'} are deployed\\n`);\n\t\treturn;\n\t}\n\n\tstdout.write(`${colors.dim('stars')} ${commands.length} ${guild ? `command(s) in guild ${guild}` : 'global command(s)'}\\n`);\n\tfor (const command of commands) stdout.write(` ${colors.bold(describeName(command))} ${colors.dim(command.id)}\\n`);\n}\n\n/**\n * Deletes deployed application commands. Discord keeps whatever was registered last, so this is the way to clear\n * commands a project no longer defines (or a whole guild's test deployment).\n */\nexport async function runCommandsClean(options: CommandsCleanOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() && !options.json });\n\tconst { client, guild } = await connect(options);\n\tconst deployed = await client.listCommands(guild);\n\tconst wanted = options.names?.filter((name) => name.length > 0) ?? [];\n\n\tconst missing = wanted.filter((name) => !deployed.some((command) => command.name === name));\n\tif (missing.length > 0) {\n\t\tthrow new CliError(`No deployed command is named ${missing.join(', ')}`, {\n\t\t\tcode: 'COMMAND_NOT_FOUND',\n\t\t\thint: 'Run `stars commands list` to see what is deployed.'\n\t\t});\n\t}\n\n\tif (deployed.length === 0) {\n\t\tif (options.json) stdout.write(`${JSON.stringify({ deleted: [] }, null, 2)}\\n`);\n\t\telse stdout.write(`${colors.dim('stars')} nothing to delete\\n`);\n\t\treturn;\n\t}\n\n\tconst scope = guild ? `guild ${guild}` : 'the global scope';\n\tconst targets = await selectTargets(options, deployed, wanted, scope, stdout, colors);\n\tif (targets.length === 0) {\n\t\tif (options.json) stdout.write(`${JSON.stringify({ deleted: [] }, null, 2)}\\n`);\n\t\telse stdout.write(`${colors.dim('stars')} nothing to delete\\n`);\n\t\treturn;\n\t}\n\n\tfor (const command of targets) {\n\t\tawait client.deleteCommand(guild, command.id);\n\t\tif (!options.json) stdout.write(`${colors.dim('stars')} ${colors.red('deleted')} ${describeName(command)}\\n`);\n\t}\n\n\tif (options.json) stdout.write(`${JSON.stringify({ deleted: targets.map((command) => ({ id: command.id, name: command.name })) }, null, 2)}\\n`);\n}\n\n/**\n * Works out what to delete: `--name` (and `--yes`) keep the command scriptable, while a bare `stars commands clean`\n * on a terminal runs the wizard — a checklist of what Discord has deployed, then a confirmation.\n */\nasync function selectTargets(\n\toptions: CommandsCleanOptions,\n\tdeployed: ApplicationCommand[],\n\twanted: string[],\n\tscope: string,\n\tstdout: NodeJS.WritableStream,\n\tcolors: ReturnType<typeof createColors>\n): Promise<ApplicationCommand[]> {\n\tif (wanted.length > 0) {\n\t\tconst named = deployed.filter((command) => wanted.includes(command.name));\n\t\tif (options.yes) return named;\n\n\t\tawait confirmOrThrow(options, stdout, colors, `delete ${colors.bold(String(named.length))} command(s) from ${scope}?`);\n\t\treturn named;\n\t}\n\n\tif (options.yes) return deployed;\n\n\tconst stdin = options.stdin ?? process.stdin;\n\tif (!options.prompt && !stdin.isTTY) throw confirmationRequired();\n\n\tconst prompt = options.prompt ?? createClackPrompt();\n\tconst chosen = new Set(await prompt.pick(deployed, scope));\n\tconst targets = deployed.filter((command) => chosen.has(command.id));\n\tif (targets.length === 0) return [];\n\n\tconst names = targets.map((command) => command.name).join(', ');\n\tif (!(await prompt.confirm(`Delete ${targets.length} command(s) from ${scope}: ${names}?`))) {\n\t\tthrow new CliError('Aborted', { code: 'ABORTED' });\n\t}\n\n\treturn targets;\n}\n\nasync function connect(options: CommandsTaskOptions): Promise<{ client: DiscordClient; guild: string | null }> {\n\tconst config = await loadStarsConfig({ cwd: resolveCwd(options), configFile: options.config });\n\treturn { client: options.client ?? createDiscordClient(config), guild: options.guild ?? null };\n}\n\nfunction describeName(command: ApplicationCommand): string {\n\tconst type = command.type && command.type !== 1 ? ` (${COMMAND_TYPE_NAMES[command.type] ?? `type ${command.type}`})` : '';\n\treturn `${command.name}${type}`;\n}\n\n/**\n * Deleting deployed commands is not reversible, so a terminal run asks first; scripts pass `--yes`.\n */\nasync function confirmOrThrow(\n\toptions: CommandsCleanOptions,\n\tstdout: NodeJS.WritableStream,\n\tcolors: ReturnType<typeof createColors>,\n\tquestion: string\n): Promise<void> {\n\tif (options.prompt) {\n\t\tif (await options.prompt.confirm(question)) return;\n\t\tthrow new CliError('Aborted', { code: 'ABORTED' });\n\t}\n\n\tconst stdin = options.stdin ?? process.stdin;\n\tif (!stdin.isTTY) throw confirmationRequired();\n\n\tstdout.write(`${colors.dim('stars')} ${question} [y/N] `);\n\treturn new Promise((resolve, reject) => {\n\t\tstdin.setEncoding?.('utf-8');\n\t\tstdin.once('data', (chunk: string) => {\n\t\t\tstdin.pause?.();\n\t\t\tconst answer = chunk.trim().toLowerCase();\n\t\t\tif (answer === 'y' || answer === 'yes') resolve();\n\t\t\telse reject(new CliError('Aborted', { code: 'ABORTED' }));\n\t\t});\n\t\tstdin.resume?.();\n\t});\n}\n\nfunction confirmationRequired(): CliError {\n\treturn new CliError('Refusing to delete commands without a confirmation', {\n\t\tcode: 'CONFIRMATION_REQUIRED',\n\t\thint: 'Pass --yes to delete them, or --name to pick one, from a script.'\n\t});\n}\n"],"mappings":"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"}
|