@wolfstar/cli 2.3.1 → 2.6.0-next-20261007120201

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.
Files changed (74) hide show
  1. package/README.md +83 -13
  2. package/dist/{_shared-BIQK0Bhm.js → _shared-CyzTojMA.js} +95 -20
  3. package/dist/_shared-CyzTojMA.js.map +1 -0
  4. package/dist/{bridge-BegCt_n2.js → bridge-Cbm0jX9h.js} +3 -236
  5. package/dist/bridge-Cbm0jX9h.js.map +1 -0
  6. package/dist/{build-DJcc95Yq.js → build-BbIEbOu3.js} +5 -5
  7. package/dist/{build-DJcc95Yq.js.map → build-BbIEbOu3.js.map} +1 -1
  8. package/dist/cli.js +1 -1
  9. package/dist/codegen-C2PY7RXA.js +262 -0
  10. package/dist/codegen-C2PY7RXA.js.map +1 -0
  11. package/dist/commands-3Tgwy6cj.js +16 -0
  12. package/dist/commands-3Tgwy6cj.js.map +1 -0
  13. package/dist/commands-3qXbxhXx.js +3 -0
  14. package/dist/{commands-Bh0K9iWX.js → commands-C-i3qYOz.js} +5 -93
  15. package/dist/commands-C-i3qYOz.js.map +1 -0
  16. package/dist/{completions-DMPlZ0S1.js → completions-Cs6YbuXO.js} +3 -3
  17. package/dist/{completions-DMPlZ0S1.js.map → completions-Cs6YbuXO.js.map} +1 -1
  18. package/dist/{dev-Owu3e3AO.js → dev-9YvzhtkU.js} +22 -215
  19. package/dist/dev-9YvzhtkU.js.map +1 -0
  20. package/dist/{diagnostics-BysbNnPD.js → diagnostics-Dn3CXa8J.js} +11 -2
  21. package/dist/diagnostics-Dn3CXa8J.js.map +1 -0
  22. package/dist/{doctor-DEtAAknK.js → doctor-CMGfbXS5.js} +15 -7
  23. package/dist/doctor-CMGfbXS5.js.map +1 -0
  24. package/dist/{hooks-Cy6F2bhH.js → hooks-D_RVS0ba.js} +13 -5
  25. package/dist/hooks-D_RVS0ba.js.map +1 -0
  26. package/dist/index.d.ts +21 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -2
  29. package/dist/{info-CKJVMWIT.js → info-eXsx1AfE.js} +5 -4
  30. package/dist/info-eXsx1AfE.js.map +1 -0
  31. package/dist/local-commands-CGBjc8KJ.js +95 -0
  32. package/dist/local-commands-CGBjc8KJ.js.map +1 -0
  33. package/dist/{locales-BJAP4HYf.js → locales-BzyEMQnX.js} +5 -5
  34. package/dist/{locales-BJAP4HYf.js.map → locales-BzyEMQnX.js.map} +1 -1
  35. package/dist/{nitro-B8u1iVgf.js → nitro-DufOgx8s.js} +2 -2
  36. package/dist/{nitro-B8u1iVgf.js.map → nitro-DufOgx8s.js.map} +1 -1
  37. package/dist/{prepare-BM93i3Rm.js → prepare-DKH0UIYA.js} +12 -8
  38. package/dist/prepare-DKH0UIYA.js.map +1 -0
  39. package/dist/{project-zlQStwsf.js → project-DBqQMFCk.js} +14 -3
  40. package/dist/project-DBqQMFCk.js.map +1 -0
  41. package/dist/{run-CHqwvZXf.js → run-DlNY1QM1.js} +2 -2
  42. package/dist/{run-CHqwvZXf.js.map → run-DlNY1QM1.js.map} +1 -1
  43. package/dist/tsc-9yHyx0L6.js +3 -0
  44. package/dist/{tsc-puVAcwmo.js → tsc-BArammxe.js} +3 -3
  45. package/dist/{tsc-puVAcwmo.js.map → tsc-BArammxe.js.map} +1 -1
  46. package/dist/{tsdown-Dwt3JhPw.js → tsdown-2i5h88vT.js} +2 -2
  47. package/dist/{tsdown-Dwt3JhPw.js.map → tsdown-2i5h88vT.js.map} +1 -1
  48. package/dist/{tui-nldGSD4c.js → tui-CB_qk2Z1.js} +2 -2
  49. package/dist/{tui-nldGSD4c.js.map → tui-CB_qk2Z1.js.map} +1 -1
  50. package/dist/tunnel-BNV5cuJz.js +236 -0
  51. package/dist/tunnel-BNV5cuJz.js.map +1 -0
  52. package/dist/typecheck-p_2rj9lL.js +66 -0
  53. package/dist/typecheck-p_2rj9lL.js.map +1 -0
  54. package/dist/typechecker-C3UOI2LF.js +213 -0
  55. package/dist/typechecker-C3UOI2LF.js.map +1 -0
  56. package/dist/{vite-YZUtv2Hk.js → vite-C5CN3xUu.js} +2 -2
  57. package/dist/{vite-YZUtv2Hk.js.map → vite-C5CN3xUu.js.map} +1 -1
  58. package/package.json +9 -9
  59. package/dist/_shared-BIQK0Bhm.js.map +0 -1
  60. package/dist/bridge-BegCt_n2.js.map +0 -1
  61. package/dist/codegen-aJSyPz-J.js +0 -124
  62. package/dist/codegen-aJSyPz-J.js.map +0 -1
  63. package/dist/commands-Bh0K9iWX.js.map +0 -1
  64. package/dist/commands-BlDTDvFO.js +0 -3
  65. package/dist/commands-CSB-pjgs.js +0 -15
  66. package/dist/commands-CSB-pjgs.js.map +0 -1
  67. package/dist/dev-Owu3e3AO.js.map +0 -1
  68. package/dist/diagnostics-BysbNnPD.js.map +0 -1
  69. package/dist/doctor-DEtAAknK.js.map +0 -1
  70. package/dist/hooks-Cy6F2bhH.js.map +0 -1
  71. package/dist/info-CKJVMWIT.js.map +0 -1
  72. package/dist/prepare-BM93i3Rm.js.map +0 -1
  73. package/dist/project-zlQStwsf.js.map +0 -1
  74. package/dist/tsc-C5l5TQGC.js +0 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-Cbm0jX9h.js","names":[],"sources":["../src/dev/bridge.ts"],"sourcesContent":["import type { ResolvedStarsConfig } from '@wolfstar/schema';\nimport { resolveSync } from 'mlly';\nimport { pathToFileURL } from 'node:url';\nimport { findInstalledVersion } from '../utils/project.js';\n\n/** Marks a message of the bridge, so a bot using the IPC channel for something else is left alone. */\nexport const BRIDGE_SOURCE = 'stars:bridge';\n/** Marks a message `stars dev` sends to the bot. */\nexport const CLI_SOURCE = 'stars:cli';\n/** Set on a bot started only to report the commands it registers (see `readLocalCommands`). */\nexport const DUMP_ENV = 'STARS_COMMANDS_DUMP';\n/** The first `@wolfstar/http-framework` with object plugins (`Client.use({ name, postListen })`), which the bridge is. */\nexport const BRIDGE_FRAMEWORK_VERSION = '6.1.0';\n\n/** An application command as the bot would register it: the JSON body Discord receives. */\nexport interface CommandData {\n\tname: string;\n\ttype?: number;\n\t[key: string]: unknown;\n}\n\nexport interface CommandSnapshot {\n\tglobal: CommandData[];\n\t/** Guild-restricted commands, by guild id. */\n\tguilds: Record<string, CommandData[]>;\n}\n\nexport interface BridgeError {\n\tmessage: string;\n\t/** The first stack frames, without the message line. */\n\tstack: string[];\n}\n\n/**\n * What the bot tells `stars dev` over the IPC channel. The bot's stdout only carries text a level is guessed for; this\n * is everything the dev UI shows that happens inside the bot: hot reloads, requests, dispatches, its commands.\n */\nexport type BridgeMessage =\n\t| { type: 'ready'; clientId: string; port: number | null }\n\t| { type: 'pieces'; store: string; pieces: { name: string; path: string }[] }\n\t| { type: 'hmr'; event: 'start'; paths: string[] }\n\t| { type: 'hmr'; event: 'stop' }\n\t| { type: 'hmr'; event: 'reloaded' | 'loaded' | 'unloaded'; store: string; names: string[]; path: string }\n\t| ({ type: 'hmr'; event: 'error'; path: string } & BridgeError)\n\t| ({ type: 'commands' } & CommandSnapshot)\n\t| { type: 'dispatch'; phase: 'run'; route: string; piece: string }\n\t| { type: 'dispatch'; phase: 'success'; route: string; piece: string; ms: number | null }\n\t| { type: 'dispatch'; phase: 'error'; route: string; piece: string; ms: number | null; error: BridgeError }\n\t| { type: 'request'; method: string; path: string; status: number; ms: number }\n\t| { type: 'refreshed'; ok: true; global: number; guilds: number; cleared: number }\n\t| ({ type: 'refreshed'; ok: false } & BridgeError)\n\t/** A line on a channel of the sender's choice: how a plugin gets its own channel in the dev UI. */\n\t| { type: 'log'; channel: string; level: string; text: string; detail?: string[] };\n\n/** What the CLI asks of the bot. `clearGuilds` are guilds whose last command was removed: nothing registers them any more. */\nexport interface RefreshRequest {\n\tsource: typeof CLI_SOURCE;\n\ttype: 'commands:refresh';\n\tclearGuilds: string[];\n}\n\ntype Loose = Record<string, any>;\n\nconst isString = (value: unknown): value is string => typeof value === 'string';\nconst isNumber = (value: unknown): value is number => typeof value === 'number' && Number.isFinite(value);\nconst isStrings = (value: unknown): value is string[] => Array.isArray(value) && value.every(isString);\nconst isRecord = (value: unknown): value is Loose => value !== null && typeof value === 'object' && !Array.isArray(value);\nconst isCommands = (value: unknown): boolean => Array.isArray(value) && value.every((command) => isRecord(command) && isString(command.name));\nconst isError = (value: Loose): boolean => isString(value.message) && isStrings(value.stack);\n\n/** The shape each message must have. A plugin can send `log`, so nothing here is taken on trust. */\nconst SHAPES: Record<BridgeMessage['type'], (message: Loose) => boolean> = {\n\tready: (message) => isString(message.clientId) && (message.port === null || isNumber(message.port)),\n\tpieces: (message) =>\n\t\tisString(message.store) &&\n\t\tArray.isArray(message.pieces) &&\n\t\tmessage.pieces.every((piece: unknown) => isRecord(piece) && isString(piece.name) && isString(piece.path)),\n\thmr: (message) => {\n\t\tswitch (message.event) {\n\t\t\tcase 'start':\n\t\t\t\treturn isStrings(message.paths);\n\t\t\tcase 'stop':\n\t\t\t\treturn true;\n\t\t\tcase 'error':\n\t\t\t\treturn isString(message.path) && isError(message);\n\t\t\tcase 'reloaded':\n\t\t\tcase 'loaded':\n\t\t\tcase 'unloaded':\n\t\t\t\treturn isString(message.store) && isStrings(message.names) && isString(message.path);\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t},\n\tcommands: (message) => isCommands(message.global) && isRecord(message.guilds) && Object.values(message.guilds).every(isCommands),\n\tdispatch: (message) => {\n\t\tif (!isString(message.route) || !isString(message.piece)) return false;\n\t\tif (message.phase === 'run') return true;\n\t\tif (message.ms !== null && !isNumber(message.ms)) return false;\n\t\treturn message.phase === 'success' || (message.phase === 'error' && isRecord(message.error) && isError(message.error));\n\t},\n\trequest: (message) => isString(message.method) && isString(message.path) && isNumber(message.status) && isNumber(message.ms),\n\trefreshed: (message) =>\n\t\tmessage.ok === true\n\t\t\t? isNumber(message.global) && isNumber(message.guilds) && isNumber(message.cleared)\n\t\t\t: message.ok === false && isError(message),\n\tlog: (message) =>\n\t\tisString(message.channel) &&\n\t\tmessage.channel.length > 0 &&\n\t\tisString(message.level) &&\n\t\tisString(message.text) &&\n\t\t(message.detail === undefined || isStrings(message.detail))\n};\n\n/**\n * Narrows an IPC message to one of the bridge's, `null` for anything else the bot sends and for a bridge message that\n * does not have the shape of its type.\n */\nexport function parseBridgeMessage(value: unknown): BridgeMessage | null {\n\tif (!isRecord(value) || value.source !== BRIDGE_SOURCE || !isString(value.type) || !Object.hasOwn(SHAPES, value.type)) return null;\n\treturn SHAPES[value.type as BridgeMessage['type']](value) ? (value as BridgeMessage) : null;\n}\n\n/**\n * The source of the bridge: a module preloaded into the bot (`node --import`) that registers a plugin on the bot's own\n * `Client` and reports what happens there with `process.send`. It never writes to stdout, and does nothing when the\n * process has no IPC channel, so it is inert outside `stars dev`.\n *\n * It only uses what `@wolfstar/http-framework` already exposes — the client's events, its registry and its stores —\n * which is what keeps `@wolfstar/cli` free of a dependency on the framework: `framework` is the project's own copy.\n */\nexport function bridgeModuleSource(framework: string): string {\n\treturn `import { Client, container } from ${JSON.stringify(framework)};\n\nif (typeof process.send === 'function') {\n\tconst send = (type, data) => {\n\t\ttry {\n\t\t\tprocess.send({ source: ${JSON.stringify(BRIDGE_SOURCE)}, type, ...data });\n\t\t} catch {}\n\t};\n\tconst describe = (error) => ({\n\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\tstack:\n\t\t\terror instanceof Error && typeof error.stack === 'string'\n\t\t\t\t? error.stack.split('\\\\n').slice(1, 6).map((line) => line.trim())\n\t\t\t\t: []\n\t});\n\tconst commandRoute = (context) => {\n\t\tconst data = context.interaction.data ?? {};\n\t\tconst kind = context.interaction.type === 4 ? 'autocomplete' : data.type === 2 ? 'user' : data.type === 3 ? 'message' : 'slash';\n\t\tlet name = String(data.name ?? 'unknown');\n\t\tlet options = data.options;\n\t\twhile (Array.isArray(options) && options.length === 1 && (options[0].type === 1 || options[0].type === 2)) {\n\t\t\tname += ' ' + options[0].name;\n\t\t\toptions = options[0].options;\n\t\t}\n\t\treturn kind + ':' + name;\n\t};\n\tconst handlerRoute = (context) => (context.interaction.type === 5 ? 'modal' : 'component') + ':' + context.handler.name;\n\tconst commandsOf = (client) => {\n\t\tconst guilds = {};\n\t\tfor (const [id, commands] of client.registry.getLoadedGuildCommands()) guilds[id] = commands;\n\t\treturn { global: client.registry.getLoadedGlobalCommands(), guilds };\n\t};\n\tconst snapshot = (client) => {\n\t\ttry {\n\t\t\tsend('commands', commandsOf(client));\n\t\t} catch {}\n\t};\n\n\ttry {\n\t\tClient.use({\n\t\t\tname: 'stars:dev-bridge',\n\t\t\tenforce: 'post',\n\t\t\tpostInitialization(client) {\n\t\t\t\tif (process.env[${JSON.stringify(DUMP_ENV)}] === '1') {\n\t\t\t\t\t// \\`stars commands diff\\` only wants to know what the bot would register: once its pieces are loaded\n\t\t\t\t\t// it has the answer, and exits before it listens or talks to Discord.\n\t\t\t\t\tconst load = client.load.bind(client);\n\t\t\t\t\tclient.load = async (...args) => {\n\t\t\t\t\t\tawait load(...args);\n\t\t\t\t\t\tprocess.send({ source: ${JSON.stringify(BRIDGE_SOURCE)}, type: 'commands', ...commandsOf(client) }, () => process.exit(0));\n\t\t\t\t\t\tawait new Promise(() => {});\n\t\t\t\t\t};\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst started = new WeakMap();\n\t\t\t\tconst track = (prefix, route, pieceOf) => {\n\t\t\t\t\tclient.on(prefix + 'Run', (context) => {\n\t\t\t\t\t\tstarted.set(context.interaction, performance.now());\n\t\t\t\t\t\tsend('dispatch', { phase: 'run', route: route(context), piece: pieceOf(context).name });\n\t\t\t\t\t});\n\t\t\t\t\tconst done = (phase) => (first, second) => {\n\t\t\t\t\t\tconst context = phase === 'error' ? second : first;\n\t\t\t\t\t\tconst at = started.get(context.interaction);\n\t\t\t\t\t\tsend('dispatch', {\n\t\t\t\t\t\t\tphase,\n\t\t\t\t\t\t\troute: route(context),\n\t\t\t\t\t\t\tpiece: pieceOf(context).name,\n\t\t\t\t\t\t\tms: at === undefined ? null : Math.round(performance.now() - at),\n\t\t\t\t\t\t\t...(phase === 'error' ? { error: describe(first) } : {})\n\t\t\t\t\t\t});\n\t\t\t\t\t};\n\t\t\t\t\tclient.on(prefix + 'Success', done('success'));\n\t\t\t\t\tclient.on(prefix + 'Error', done('error'));\n\t\t\t\t};\n\t\t\t\ttrack('command', commandRoute, (context) => context.command);\n\t\t\t\ttrack('autocomplete', commandRoute, (context) => context.command);\n\t\t\t\ttrack('interactionHandler', handlerRoute, (context) => context.handler);\n\n\t\t\t\tconst changed = (event) => (pieces, path) => {\n\t\t\t\t\tconst list = Array.isArray(pieces) ? pieces : [pieces];\n\t\t\t\t\tconst store = list[0]?.store?.name ?? 'pieces';\n\t\t\t\t\tsend('hmr', { event, path, store, names: list.map((piece) => piece.name) });\n\t\t\t\t\tif (store === 'commands') snapshot(client);\n\t\t\t\t};\n\t\t\t\tclient.on('hmrStart', (paths) => send('hmr', { event: 'start', paths }));\n\t\t\t\tclient.on('hmrStop', () => send('hmr', { event: 'stop' }));\n\t\t\t\tclient.on('hmrPieceReloaded', changed('reloaded'));\n\t\t\t\tclient.on('hmrPiecesLoaded', changed('loaded'));\n\t\t\t\tclient.on('hmrPieceUnloaded', changed('unloaded'));\n\t\t\t\tclient.on('hmrError', (error, path) => send('hmr', { event: 'error', path, ...describe(error) }));\n\t\t\t},\n\t\t\tpostListen(client) {\n\t\t\t\tif (process.env[${JSON.stringify(DUMP_ENV)}] === '1') return;\n\t\t\t\tconst server = client.server;\n\t\t\t\tserver?.on?.('request', (request, response) => {\n\t\t\t\t\tconst at = performance.now();\n\t\t\t\t\tresponse.once('finish', () =>\n\t\t\t\t\t\tsend('request', {\n\t\t\t\t\t\t\tmethod: String(request.method),\n\t\t\t\t\t\t\tpath: String(request.url),\n\t\t\t\t\t\t\tstatus: response.statusCode,\n\t\t\t\t\t\t\tms: Math.round(performance.now() - at)\n\t\t\t\t\t\t})\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\tfor (const store of container.stores.values()) {\n\t\t\t\t\tsend('pieces', {\n\t\t\t\t\t\tstore: store.name,\n\t\t\t\t\t\tpieces: [...store.values()].map((piece) => ({ name: piece.name, path: piece.location.full }))\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tsnapshot(client);\n\t\t\t\tconst address = server?.address?.();\n\t\t\t\tsend('ready', { clientId: client.id, port: address !== null && typeof address === 'object' ? address.port : null });\n\t\t\t}\n\t\t});\n\n\t\tprocess.on('message', (message) => {\n\t\t\tif (message === null || typeof message !== 'object' || message.source !== ${JSON.stringify(CLI_SOURCE)}) return;\n\t\t\tif (message.type !== 'commands:refresh') return;\n\t\t\tPromise.resolve()\n\t\t\t\t.then(async () => {\n\t\t\t\t\tconst client = container.client;\n\t\t\t\t\tconst global = await client.registry.pushGlobalCommands();\n\t\t\t\t\tconst guilds = await client.registry.pushGuildRestrictedCommands();\n\t\t\t\t\tconst failed = guilds.find((result) => result.status === 'rejected');\n\t\t\t\t\tif (failed) throw failed.reason;\n\t\t\t\t\t// A guild that lost its last command is no longer in the registry, so nothing above overwrote it.\n\t\t\t\t\tconst clear = Array.isArray(message.clearGuilds) ? message.clearGuilds.filter((id) => typeof id === 'string') : [];\n\t\t\t\t\tfor (const id of clear) {\n\t\t\t\t\t\tawait container.rest.put('/applications/' + client.id + '/guilds/' + id + '/commands', { body: [] });\n\t\t\t\t\t}\n\t\t\t\t\tsend('refreshed', { ok: true, global: global.length, guilds: guilds.length, cleared: clear.length });\n\t\t\t\t})\n\t\t\t\t.catch((error) => send('refreshed', { ok: false, ...describe(error) }));\n\t\t});\n\t\t// Listening for messages keeps the channel, and with it the process, alive: a bot that is done must still exit.\n\t\tprocess.channel?.unref?.();\n\t} catch {}\n}\n`;\n}\n\n/**\n * `node` arguments that preload the bridge into the bot. Nothing is preloaded when the project's framework cannot be\n * resolved or predates object plugins: `stars dev` then shows what the bot prints, as it did before the bridge.\n *\n * The framework is resolved from the project with the `import` condition, the way `moduleImportArgs` does, so the\n * plugin lands on the `Client` the bot constructs. A build that bundles the framework into its output (Vite, Nitro)\n * has its own copy, which the preload cannot reach: the bridge stays silent there too.\n */\nexport function bridgeImportArgs(config: ResolvedStarsConfig): string[] {\n\tconst version = findInstalledVersion(config.root, '@wolfstar/http-framework');\n\tif (version === null || !isAtLeast(version, BRIDGE_FRAMEWORK_VERSION)) return [];\n\n\tlet framework: string;\n\ttry {\n\t\tframework = resolveSync('@wolfstar/http-framework', { url: pathToFileURL(`${config.root}/package.json`).href });\n\t} catch {\n\t\treturn [];\n\t}\n\n\treturn ['--import', `data:text/javascript,${encodeURIComponent(bridgeModuleSource(framework))}`];\n}\n\n/** Compares the `major.minor.patch` of two versions; a prerelease counts as its release. */\nexport function isAtLeast(version: string, minimum: string): boolean {\n\tconst parse = (value: string) =>\n\t\tvalue\n\t\t\t.split('-', 1)[0]!\n\t\t\t.split('.')\n\t\t\t.map((part) => Number.parseInt(part, 10) || 0);\n\tconst [a, b] = [parse(version), parse(minimum)];\n\tfor (let index = 0; index < 3; index++) {\n\t\tconst difference = (a[index] ?? 0) - (b[index] ?? 0);\n\t\tif (difference !== 0) return difference > 0;\n\t}\n\n\treturn true;\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { i as shouldUseColor } from "./output-mode-Bp7Da8vJ.js";
2
- import { t as cliDiagnostics } from "./diagnostics-BysbNnPD.js";
3
- import { n as createBuilder, t as Locales } from "./locales-BJAP4HYf.js";
4
- import { d as resolveCwd, n as loadProject, t as applyEnvOptions, u as projectArgs } from "./hooks-Cy6F2bhH.js";
5
- import { n as reportWarnings, t as prepareProject } from "./_shared-BIQK0Bhm.js";
2
+ import { t as cliDiagnostics } from "./diagnostics-Dn3CXa8J.js";
3
+ import { n as createBuilder, t as Locales } from "./locales-BzyEMQnX.js";
4
+ import { d as resolveCwd, n as loadProject, t as applyEnvOptions, u as projectArgs } from "./hooks-D_RVS0ba.js";
5
+ import { n as reportWarnings, t as prepareProject } from "./_shared-CyzTojMA.js";
6
6
  import { defineCommand } from "citty";
7
7
  import { displayPath } from "@wolfstar/schema";
8
8
  import { createColors } from "colorette";
@@ -53,4 +53,4 @@ var build_default = defineCommand({
53
53
 
54
54
  //#endregion
55
55
  export { build_default as default };
56
- //# sourceMappingURL=build-DJcc95Yq.js.map
56
+ //# sourceMappingURL=build-BbIEbOu3.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-DJcc95Yq.js","names":[],"sources":["../src/commands/build.ts"],"sourcesContent":["import { displayPath } from '@wolfstar/schema';\nimport { defineCommand } from 'citty';\nimport { createColors } from 'colorette';\nimport { createBuilder } from '../builders/index.js';\nimport { projectArgs, resolveCwd, type ProjectArgs } from '../utils/args.js';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport { applyEnvOptions, loadProject } from '../utils/hooks.js';\nimport { Locales } from '../utils/locales.js';\nimport { shouldUseColor } from '../utils/output-mode.js';\nimport { prepareProject, reportWarnings } from './_shared.js';\n\nexport interface BuildTaskOptions extends ProjectArgs {\n\tstdout?: NodeJS.WritableStream;\n}\n\nexport async function runBuild(options: BuildTaskOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() });\n\tconst { config: resolved, hooks } = await loadProject({ cwd: resolveCwd(options), configFile: options.config });\n\tawait reportWarnings(resolved, (text) => process.stderr.write(`${text}\\n`));\n\n\tif (resolved.build.tool === 'none') {\n\t\tawait prepareProject(resolved, hooks);\n\t\tstdout.write(`${colors.dim('stars')} nothing to build, ${displayPath(resolved.root, resolved.entry)} runs as-is (build.tool is 'none')\\n`);\n\t\treturn;\n\t}\n\n\t// Same hook order as `stars dev`: `env:options` and `builder:created` before `prepare:*`.\n\tconst config = await applyEnvOptions(resolved, hooks);\n\tconst builder = await createBuilder(config, hooks);\n\tawait hooks.callHook('builder:created', builder, config);\n\tawait prepareProject(config, hooks);\n\tbuilder.on('log', (level, text) => {\n\t\tconst paint = level === 'error' ? colors.red : level === 'warn' ? colors.yellow : (value: string) => value;\n\t\tstdout.write(`${paint(text)}\\n`);\n\t});\n\n\tstdout.write(`${colors.dim('stars')} building with ${colors.bold(config.build.tool)}…\\n`);\n\tawait hooks.callHook('build:before', config);\n\tconst outcome = await builder.build();\n\tawait hooks.callHook('build:done', outcome, config);\n\tif (!outcome.ok) {\n\t\tthrow cliDiagnostics.BUILD_FAILED({ message: outcome.message ?? '' });\n\t}\n\tnew Locales(config).copy();\n\n\tstdout.write(`${colors.dim('stars')} ${colors.green(`built in ${outcome.durationMs}ms`)} → ${displayPath(config.root, config.build.output)}\\n`);\n}\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'build',\n\t\tdescription: 'Build the project once with the configured build tool'\n\t},\n\targs: { ...projectArgs },\n\tasync run({ args }) {\n\t\tawait runBuild({ config: args.config, cwd: args.cwd });\n\t}\n});\n"],"mappings":";;;;;;;;;;AAeA,eAAsB,SAAS,SAA0C;CACxE,MAAM,SAAS,QAAQ,UAAU,QAAQ;CACzC,MAAM,SAAS,aAAa,EAAE,UAAU,eAAe,EAAE,CAAC;CAC1D,MAAM,EAAE,QAAQ,UAAU,UAAU,MAAM,YAAY;EAAE,KAAK,WAAW,OAAO;EAAG,YAAY,QAAQ;CAAO,CAAC;CAC9G,MAAM,eAAe,WAAW,SAAS,QAAQ,OAAO,MAAM,GAAG,KAAK,GAAG,CAAC;CAE1E,IAAI,SAAS,MAAM,SAAS,QAAQ;EACnC,MAAM,eAAe,UAAU,KAAK;EACpC,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,qBAAqB,YAAY,SAAS,MAAM,SAAS,KAAK,EAAE,qCAAqC;EACzI;CACD;CAGA,MAAM,SAAS,MAAM,gBAAgB,UAAU,KAAK;CACpD,MAAM,UAAU,MAAM,cAAc,QAAQ,KAAK;CACjD,MAAM,MAAM,SAAS,mBAAmB,SAAS,MAAM;CACvD,MAAM,eAAe,QAAQ,KAAK;CAClC,QAAQ,GAAG,QAAQ,OAAO,SAAS;EAClC,MAAM,QAAQ,UAAU,UAAU,OAAO,MAAM,UAAU,SAAS,OAAO,UAAU,UAAkB;EACrG,OAAO,MAAM,GAAG,MAAM,IAAI,EAAE,GAAG;CAChC,CAAC;CAED,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,iBAAiB,OAAO,KAAK,OAAO,MAAM,IAAI,EAAE,IAAI;CACxF,MAAM,MAAM,SAAS,gBAAgB,MAAM;CAC3C,MAAM,UAAU,MAAM,QAAQ,MAAM;CACpC,MAAM,MAAM,SAAS,cAAc,SAAS,MAAM;CAClD,IAAI,CAAC,QAAQ,IACZ,MAAM,eAAe,aAAa,EAAE,SAAS,QAAQ,WAAW,GAAG,CAAC;CAErE,IAAI,QAAQ,MAAM,CAAC,CAAC,KAAK;CAEzB,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,MAAM,YAAY,QAAQ,WAAW,GAAG,EAAE,KAAK,YAAY,OAAO,MAAM,OAAO,MAAM,MAAM,EAAE,GAAG;AAC/I;AAEA,oBAAe,cAAc;CAC5B,MAAM;EACL,MAAM;EACN,aAAa;CACd;CACA,MAAM,EAAE,GAAG,YAAY;CACvB,MAAM,IAAI,EAAE,QAAQ;EACnB,MAAM,SAAS;GAAE,QAAQ,KAAK;GAAQ,KAAK,KAAK;EAAI,CAAC;CACtD;AACD,CAAC"}
1
+ {"version":3,"file":"build-BbIEbOu3.js","names":[],"sources":["../src/commands/build.ts"],"sourcesContent":["import { displayPath } from '@wolfstar/schema';\nimport { defineCommand } from 'citty';\nimport { createColors } from 'colorette';\nimport { createBuilder } from '../builders/index.js';\nimport { projectArgs, resolveCwd, type ProjectArgs } from '../utils/args.js';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport { applyEnvOptions, loadProject } from '../utils/hooks.js';\nimport { Locales } from '../utils/locales.js';\nimport { shouldUseColor } from '../utils/output-mode.js';\nimport { prepareProject, reportWarnings } from './_shared.js';\n\nexport interface BuildTaskOptions extends ProjectArgs {\n\tstdout?: NodeJS.WritableStream;\n}\n\nexport async function runBuild(options: BuildTaskOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() });\n\tconst { config: resolved, hooks } = await loadProject({ cwd: resolveCwd(options), configFile: options.config });\n\tawait reportWarnings(resolved, (text) => process.stderr.write(`${text}\\n`));\n\n\tif (resolved.build.tool === 'none') {\n\t\tawait prepareProject(resolved, hooks);\n\t\tstdout.write(`${colors.dim('stars')} nothing to build, ${displayPath(resolved.root, resolved.entry)} runs as-is (build.tool is 'none')\\n`);\n\t\treturn;\n\t}\n\n\t// Same hook order as `stars dev`: `env:options` and `builder:created` before `prepare:*`.\n\tconst config = await applyEnvOptions(resolved, hooks);\n\tconst builder = await createBuilder(config, hooks);\n\tawait hooks.callHook('builder:created', builder, config);\n\tawait prepareProject(config, hooks);\n\tbuilder.on('log', (level, text) => {\n\t\tconst paint = level === 'error' ? colors.red : level === 'warn' ? colors.yellow : (value: string) => value;\n\t\tstdout.write(`${paint(text)}\\n`);\n\t});\n\n\tstdout.write(`${colors.dim('stars')} building with ${colors.bold(config.build.tool)}…\\n`);\n\tawait hooks.callHook('build:before', config);\n\tconst outcome = await builder.build();\n\tawait hooks.callHook('build:done', outcome, config);\n\tif (!outcome.ok) {\n\t\tthrow cliDiagnostics.BUILD_FAILED({ message: outcome.message ?? '' });\n\t}\n\tnew Locales(config).copy();\n\n\tstdout.write(`${colors.dim('stars')} ${colors.green(`built in ${outcome.durationMs}ms`)} → ${displayPath(config.root, config.build.output)}\\n`);\n}\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'build',\n\t\tdescription: 'Build the project once with the configured build tool'\n\t},\n\targs: { ...projectArgs },\n\tasync run({ args }) {\n\t\tawait runBuild({ config: args.config, cwd: args.cwd });\n\t}\n});\n"],"mappings":";;;;;;;;;;AAeA,eAAsB,SAAS,SAA0C;CACxE,MAAM,SAAS,QAAQ,UAAU,QAAQ;CACzC,MAAM,SAAS,aAAa,EAAE,UAAU,eAAe,EAAE,CAAC;CAC1D,MAAM,EAAE,QAAQ,UAAU,UAAU,MAAM,YAAY;EAAE,KAAK,WAAW,OAAO;EAAG,YAAY,QAAQ;CAAO,CAAC;CAC9G,MAAM,eAAe,WAAW,SAAS,QAAQ,OAAO,MAAM,GAAG,KAAK,GAAG,CAAC;CAE1E,IAAI,SAAS,MAAM,SAAS,QAAQ;EACnC,MAAM,eAAe,UAAU,KAAK;EACpC,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,qBAAqB,YAAY,SAAS,MAAM,SAAS,KAAK,EAAE,qCAAqC;EACzI;CACD;CAGA,MAAM,SAAS,MAAM,gBAAgB,UAAU,KAAK;CACpD,MAAM,UAAU,MAAM,cAAc,QAAQ,KAAK;CACjD,MAAM,MAAM,SAAS,mBAAmB,SAAS,MAAM;CACvD,MAAM,eAAe,QAAQ,KAAK;CAClC,QAAQ,GAAG,QAAQ,OAAO,SAAS;EAClC,MAAM,QAAQ,UAAU,UAAU,OAAO,MAAM,UAAU,SAAS,OAAO,UAAU,UAAkB;EACrG,OAAO,MAAM,GAAG,MAAM,IAAI,EAAE,GAAG;CAChC,CAAC;CAED,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,iBAAiB,OAAO,KAAK,OAAO,MAAM,IAAI,EAAE,IAAI;CACxF,MAAM,MAAM,SAAS,gBAAgB,MAAM;CAC3C,MAAM,UAAU,MAAM,QAAQ,MAAM;CACpC,MAAM,MAAM,SAAS,cAAc,SAAS,MAAM;CAClD,IAAI,CAAC,QAAQ,IACZ,MAAM,eAAe,aAAa,EAAE,SAAS,QAAQ,WAAW,GAAG,CAAC;CAErE,IAAI,QAAQ,MAAM,CAAC,CAAC,KAAK;CAEzB,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,MAAM,YAAY,QAAQ,WAAW,GAAG,EAAE,KAAK,YAAY,OAAO,MAAM,OAAO,MAAM,MAAM,EAAE,GAAG;AAC/I;AAEA,oBAAe,cAAc;CAC5B,MAAM;EACL,MAAM;EACN,aAAa;CACd;CACA,MAAM,EAAE,GAAG,YAAY;CACvB,MAAM,IAAI,EAAE,QAAQ;EACnB,MAAM,SAAS;GAAE,QAAQ,KAAK;GAAQ,KAAK,KAAK;EAAI,CAAC;CACtD;AACD,CAAC"}
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as runMain } from "./run-CHqwvZXf.js";
2
+ import { t as runMain } from "./run-DlNY1QM1.js";
3
3
 
4
4
  //#region src/cli.ts
5
5
  await runMain();
@@ -0,0 +1,262 @@
1
+ import { i as shouldUseColor } from "./output-mode-Bp7Da8vJ.js";
2
+ import { t as cliDiagnostics } from "./diagnostics-Dn3CXa8J.js";
3
+ import { i as resolveFromProject } from "./project-DBqQMFCk.js";
4
+ import { d as resolveCwd, n as loadProject, r as withProjectEnv, t as applyEnvOptions, u as projectArgs } from "./hooks-D_RVS0ba.js";
5
+ import { t as readLocalCommands } from "./local-commands-CGBjc8KJ.js";
6
+ import { defineCommand } from "citty";
7
+ import { dirname, join } from "node:path";
8
+ import { displayPath } from "@wolfstar/schema";
9
+ import { createColors } from "colorette";
10
+ import { tmpdir } from "node:os";
11
+ import { spawn } from "node:child_process";
12
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
13
+
14
+ //#region src/utils/command-types.ts
15
+ /** `ApplicationCommandOptionType`, which the CLI cannot import: it has no dependency on `discord-api-types`. */
16
+ const OptionType = {
17
+ Subcommand: 1,
18
+ SubcommandGroup: 2,
19
+ String: 3,
20
+ Integer: 4,
21
+ Boolean: 5,
22
+ User: 6,
23
+ Channel: 7,
24
+ Role: 8,
25
+ Mentionable: 9,
26
+ Number: 10,
27
+ Attachment: 11
28
+ };
29
+ /** `ApplicationCommandType.ChatInput`: the only type with options, and the one a command without `type` is. */
30
+ const CHAT_INPUT = 1;
31
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
32
+ /**
33
+ * The declaration file `stars codegen` writes for the commands a project registers: one `CommandOptionsRegistry` entry
34
+ * (`Command.OptionsOf<'path'>`) per leaf, that is a command without subcommands, `parent sub` or `parent group sub`.
35
+ *
36
+ * It reads the JSON Discord receives rather than the builders, so what the handler is typed with cannot drift from what
37
+ * is registered, and loops and factories need no special care. The option types point at the framework's
38
+ * `TransformedArguments`, which is what the router resolves them to, instead of repeating those shapes here.
39
+ *
40
+ * Context menu commands have no options and are skipped. A path registered more than once (a command restricted to
41
+ * several guilds is the same command in each) keeps its first definition, global commands first. The output depends
42
+ * on nothing but the commands, in sorted order, so `stars codegen --check` is a string comparison.
43
+ */
44
+ function generateCommandTypes(snapshot) {
45
+ const entries = /* @__PURE__ */ new Map();
46
+ const scopes = [snapshot.global, ...Object.keys(snapshot.guilds).sort().map((guild) => snapshot.guilds[guild])];
47
+ for (const commands of scopes) for (const command of commands) collectLeaves(command, entries);
48
+ const rendering = { usesResolvedTypes: false };
49
+ const lines = [...entries.keys()].sort().map((path) => ` ${renderEntry(path, entries.get(path), rendering)}`);
50
+ return [
51
+ "// This file is automatically generated by `stars codegen`, do not edit it.",
52
+ "/* eslint-disable */",
53
+ "/* oxlint-disable */",
54
+ "// oxfmt-ignore",
55
+ "// prettier-ignore",
56
+ rendering.usesResolvedTypes ? "import type { TransformedArguments } from '@wolfstar/http-framework';" : "export {};",
57
+ "",
58
+ "// oxfmt-ignore",
59
+ "// prettier-ignore",
60
+ "declare module '@wolfstar/http-framework' {",
61
+ " interface CommandOptionsRegistry {",
62
+ ...lines,
63
+ " }",
64
+ "}",
65
+ ""
66
+ ].join("\n");
67
+ }
68
+ function collectLeaves(command, entries) {
69
+ if ((command.type ?? CHAT_INPUT) !== CHAT_INPUT) return;
70
+ const options = command.options ?? [];
71
+ const add = (path, leaf) => {
72
+ if (!entries.has(path)) entries.set(path, leaf ?? []);
73
+ };
74
+ if (!options.some((option) => option.type === OptionType.Subcommand || option.type === OptionType.SubcommandGroup)) {
75
+ add(command.name, options);
76
+ return;
77
+ }
78
+ for (const option of options) if (option.type === OptionType.Subcommand) add(`${command.name} ${option.name}`, option.options);
79
+ else if (option.type === OptionType.SubcommandGroup) {
80
+ for (const sub of option.options ?? []) if (sub.type === OptionType.Subcommand) add(`${command.name} ${option.name} ${sub.name}`, sub.options);
81
+ }
82
+ }
83
+ /** Only the first line of an entry is indented by the caller: the options go on lines of their own. */
84
+ function renderEntry(path, options, rendering) {
85
+ const properties = [...options].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).map((option) => ` ${key(option.name)}${option.required ? "" : "?"}: ${renderOption(option, rendering)};`);
86
+ return properties.length === 0 ? `${quote(path)}: {};` : `${quote(path)}: {\n${properties.join("\n")}\n };`;
87
+ }
88
+ function renderOption(option, rendering) {
89
+ const resolved = (name) => {
90
+ rendering.usesResolvedTypes = true;
91
+ return `TransformedArguments.${name}`;
92
+ };
93
+ switch (option.type) {
94
+ case OptionType.String:
95
+ case OptionType.Integer:
96
+ case OptionType.Number: {
97
+ const fallback = option.type === OptionType.String ? "string" : "number";
98
+ if (!option.choices?.length) return fallback;
99
+ const values = option.choices.map((choice) => typeof choice.value === "string" ? quote(choice.value) : String(choice.value));
100
+ return [...new Set(values)].join(" | ");
101
+ }
102
+ case OptionType.Boolean: return "boolean";
103
+ case OptionType.User: return resolved("User");
104
+ case OptionType.Role: return resolved("Role");
105
+ case OptionType.Mentionable: return resolved("Mentionable");
106
+ case OptionType.Attachment: return resolved("Attachment");
107
+ case OptionType.Channel: {
108
+ const channel = resolved("Channel");
109
+ if (!option.channel_types?.length) return channel;
110
+ return `${channel} & { type: Extract<${channel}['type'], ${[...new Set(option.channel_types)].sort((a, b) => a - b).join(" | ")}> }`;
111
+ }
112
+ default: return "unknown";
113
+ }
114
+ }
115
+ function key(name) {
116
+ return IDENTIFIER.test(name) ? name : quote(name);
117
+ }
118
+ function quote(value) {
119
+ return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029")}'`;
120
+ }
121
+
122
+ //#endregion
123
+ //#region src/commands/codegen.ts
124
+ async function runCodegen(options) {
125
+ const stdout = options.stdout ?? process.stdout;
126
+ const colors = createColors({ useColor: shouldUseColor() && !options.json });
127
+ const project = await loadProject({
128
+ cwd: resolveCwd(options),
129
+ configFile: options.config
130
+ });
131
+ const { config } = project;
132
+ const results = [];
133
+ if (config.codegen.i18n) results.push(await runI18n(config, Boolean(options.check)));
134
+ if (config.codegen.commands) {
135
+ const botConfig = withProjectEnv(await applyEnvOptions(config, project.hooks));
136
+ results.push(await runCommands(botConfig, Boolean(options.check), options.commands));
137
+ }
138
+ if (options.json) stdout.write(`${JSON.stringify({
139
+ check: Boolean(options.check),
140
+ results
141
+ }, null, 2)}\n`);
142
+ else if (results.length === 0) stdout.write(`${colors.dim("stars")} nothing to generate (no code generators are configured)\n`);
143
+ else for (const result of results) {
144
+ const paint = result.status === "outdated" ? colors.red : colors.green;
145
+ stdout.write(`${colors.dim("stars")} ${result.generator}: ${paint(result.status)} ${displayPath(config.root, result.output)}\n`);
146
+ }
147
+ if (results.some((result) => result.status === "outdated")) throw cliDiagnostics.CODEGEN_OUTDATED({});
148
+ }
149
+ /**
150
+ * Types the options of the commands the project registers. The commands are read from the built bot, the way
151
+ * `stars commands diff` reads them: nothing connects to Discord, but the bot still runs its own env setup, so `--check`
152
+ * in CI needs placeholders for the values it reads while starting (`DISCORD_CLIENT_ID`, `DISCORD_PUBLIC_KEY`, ...).
153
+ */
154
+ async function runCommands(config, check, local) {
155
+ const { output } = config.codegen.commands;
156
+ const generated = generateCommandTypes(local ?? await readLocalCommands(config));
157
+ if (!check) {
158
+ await mkdir(dirname(output), { recursive: true });
159
+ await writeFile(output, generated);
160
+ return {
161
+ generator: "commands",
162
+ output,
163
+ status: "written"
164
+ };
165
+ }
166
+ return {
167
+ generator: "commands",
168
+ output,
169
+ status: generated === await readFile(output, "utf-8").catch(() => null) ? "up-to-date" : "outdated"
170
+ };
171
+ }
172
+ async function runI18n(config, check) {
173
+ const { locales, output } = config.codegen.i18n;
174
+ const cli = resolveFromProject(config.root, "@wolfstar/i18next-type-generator");
175
+ if (!cli) throw cliDiagnostics.DEPENDENCY_MISSING({
176
+ name: "@wolfstar/i18next-type-generator",
177
+ root: config.root,
178
+ hint: "Install it with `pnpm add -D @wolfstar/i18next-type-generator`, or set `codegen.i18n` to false."
179
+ });
180
+ if (!check) {
181
+ await generate(config.root, cli, locales, output);
182
+ return {
183
+ generator: "i18n",
184
+ output,
185
+ status: "written"
186
+ };
187
+ }
188
+ const directory = await mkdtemp(join(tmpdir(), "stars-codegen-"));
189
+ try {
190
+ const candidate = join(directory, "i18next.d.ts");
191
+ await generate(config.root, cli, locales, candidate);
192
+ const [expected, actual] = await Promise.all([readFile(candidate, "utf-8"), readFile(output, "utf-8").catch(() => null)]);
193
+ return {
194
+ generator: "i18n",
195
+ output,
196
+ status: expected === actual ? "up-to-date" : "outdated"
197
+ };
198
+ } finally {
199
+ await rm(directory, {
200
+ recursive: true,
201
+ force: true
202
+ });
203
+ }
204
+ }
205
+ function generate(root, cli, locales, output) {
206
+ return new Promise((resolve, reject) => {
207
+ const child = spawn(process.execPath, [
208
+ cli,
209
+ locales,
210
+ output
211
+ ], {
212
+ cwd: root,
213
+ stdio: [
214
+ "ignore",
215
+ "ignore",
216
+ "pipe"
217
+ ],
218
+ windowsHide: true
219
+ });
220
+ let stderr = "";
221
+ child.stderr?.on("data", (chunk) => stderr += chunk.toString());
222
+ child.once("error", reject);
223
+ child.once("exit", (code) => {
224
+ if (code === 0) resolve();
225
+ else reject(cliDiagnostics.CODEGEN_FAILED({
226
+ code,
227
+ stderr: stderr.trim()
228
+ }));
229
+ });
230
+ });
231
+ }
232
+ var codegen_default = defineCommand({
233
+ meta: {
234
+ name: "codegen",
235
+ description: "Run the configured code generators (i18next types, command options)"
236
+ },
237
+ args: {
238
+ ...projectArgs,
239
+ check: {
240
+ type: "boolean",
241
+ description: "Fail when the generated files are out of date instead of writing them",
242
+ default: false
243
+ },
244
+ json: {
245
+ type: "boolean",
246
+ description: "Print machine-readable JSON",
247
+ default: false
248
+ }
249
+ },
250
+ async run({ args }) {
251
+ await runCodegen({
252
+ config: args.config,
253
+ cwd: args.cwd,
254
+ check: args.check,
255
+ json: args.json
256
+ });
257
+ }
258
+ });
259
+
260
+ //#endregion
261
+ export { codegen_default as default };
262
+ //# sourceMappingURL=codegen-C2PY7RXA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen-C2PY7RXA.js","names":[],"sources":["../src/utils/command-types.ts","../src/commands/codegen.ts"],"sourcesContent":["import type { CommandData, CommandSnapshot } from '../dev/bridge.js';\n\n/** `ApplicationCommandOptionType`, which the CLI cannot import: it has no dependency on `discord-api-types`. */\nconst OptionType = {\n\tSubcommand: 1,\n\tSubcommandGroup: 2,\n\tString: 3,\n\tInteger: 4,\n\tBoolean: 5,\n\tUser: 6,\n\tChannel: 7,\n\tRole: 8,\n\tMentionable: 9,\n\tNumber: 10,\n\tAttachment: 11\n} as const;\n\n/** `ApplicationCommandType.ChatInput`: the only type with options, and the one a command without `type` is. */\nconst CHAT_INPUT = 1;\n\nconst IDENTIFIER = /^[A-Za-z_$][\\w$]*$/;\n\ninterface OptionData {\n\tname: string;\n\ttype: number;\n\trequired?: boolean;\n\tchoices?: { value: string | number }[];\n\tchannel_types?: number[];\n\toptions?: OptionData[];\n}\n\ninterface Rendering {\n\t/** Set once something refers to `TransformedArguments`, which the file then has to import. */\n\tusesResolvedTypes: boolean;\n}\n\n/**\n * The declaration file `stars codegen` writes for the commands a project registers: one `CommandOptionsRegistry` entry\n * (`Command.OptionsOf<'path'>`) per leaf, that is a command without subcommands, `parent sub` or `parent group sub`.\n *\n * It reads the JSON Discord receives rather than the builders, so what the handler is typed with cannot drift from what\n * is registered, and loops and factories need no special care. The option types point at the framework's\n * `TransformedArguments`, which is what the router resolves them to, instead of repeating those shapes here.\n *\n * Context menu commands have no options and are skipped. A path registered more than once (a command restricted to\n * several guilds is the same command in each) keeps its first definition, global commands first. The output depends\n * on nothing but the commands, in sorted order, so `stars codegen --check` is a string comparison.\n */\nexport function generateCommandTypes(snapshot: CommandSnapshot): string {\n\tconst entries = new Map<string, OptionData[]>();\n\tconst scopes = [\n\t\tsnapshot.global,\n\t\t...Object.keys(snapshot.guilds)\n\t\t\t.sort()\n\t\t\t.map((guild) => snapshot.guilds[guild])\n\t];\n\tfor (const commands of scopes) {\n\t\tfor (const command of commands) collectLeaves(command, entries);\n\t}\n\n\tconst rendering: Rendering = { usesResolvedTypes: false };\n\tconst lines = [...entries.keys()].sort().map((path) => ` ${renderEntry(path, entries.get(path)!, rendering)}`);\n\n\treturn [\n\t\t'// This file is automatically generated by `stars codegen`, do not edit it.',\n\t\t'/* eslint-disable */',\n\t\t'/* oxlint-disable */',\n\t\t'// oxfmt-ignore',\n\t\t'// prettier-ignore',\n\t\trendering.usesResolvedTypes ? \"import type { TransformedArguments } from '@wolfstar/http-framework';\" : 'export {};',\n\t\t'',\n\t\t'// oxfmt-ignore',\n\t\t'// prettier-ignore',\n\t\t\"declare module '@wolfstar/http-framework' {\",\n\t\t' interface CommandOptionsRegistry {',\n\t\t...lines,\n\t\t' }',\n\t\t'}',\n\t\t''\n\t].join('\\n');\n}\n\nfunction collectLeaves(command: CommandData, entries: Map<string, OptionData[]>): void {\n\tif ((command.type ?? CHAT_INPUT) !== CHAT_INPUT) return;\n\n\tconst options = (command.options ?? []) as OptionData[];\n\tconst add = (path: string, leaf: OptionData[] | undefined) => {\n\t\tif (!entries.has(path)) entries.set(path, leaf ?? []);\n\t};\n\n\tif (!options.some((option) => option.type === OptionType.Subcommand || option.type === OptionType.SubcommandGroup)) {\n\t\tadd(command.name, options);\n\t\treturn;\n\t}\n\n\tfor (const option of options) {\n\t\tif (option.type === OptionType.Subcommand) {\n\t\t\tadd(`${command.name} ${option.name}`, option.options);\n\t\t} else if (option.type === OptionType.SubcommandGroup) {\n\t\t\tfor (const sub of option.options ?? []) {\n\t\t\t\tif (sub.type === OptionType.Subcommand) add(`${command.name} ${option.name} ${sub.name}`, sub.options);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Only the first line of an entry is indented by the caller: the options go on lines of their own. */\nfunction renderEntry(path: string, options: readonly OptionData[], rendering: Rendering): string {\n\tconst sorted = [...options].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));\n\tconst properties = sorted.map((option) => ` ${key(option.name)}${option.required ? '' : '?'}: ${renderOption(option, rendering)};`);\n\treturn properties.length === 0 ? `${quote(path)}: {};` : `${quote(path)}: {\\n${properties.join('\\n')}\\n };`;\n}\n\nfunction renderOption(option: OptionData, rendering: Rendering): string {\n\tconst resolved = (name: string) => {\n\t\trendering.usesResolvedTypes = true;\n\t\treturn `TransformedArguments.${name}`;\n\t};\n\n\tswitch (option.type) {\n\t\tcase OptionType.String:\n\t\tcase OptionType.Integer:\n\t\tcase OptionType.Number: {\n\t\t\tconst fallback = option.type === OptionType.String ? 'string' : 'number';\n\t\t\tif (!option.choices?.length) return fallback;\n\t\t\tconst values = option.choices.map((choice) => (typeof choice.value === 'string' ? quote(choice.value) : String(choice.value)));\n\t\t\treturn [...new Set(values)].join(' | ');\n\t\t}\n\t\tcase OptionType.Boolean:\n\t\t\treturn 'boolean';\n\t\tcase OptionType.User:\n\t\t\treturn resolved('User');\n\t\tcase OptionType.Role:\n\t\t\treturn resolved('Role');\n\t\tcase OptionType.Mentionable:\n\t\t\treturn resolved('Mentionable');\n\t\tcase OptionType.Attachment:\n\t\t\treturn resolved('Attachment');\n\t\tcase OptionType.Channel: {\n\t\t\tconst channel = resolved('Channel');\n\t\t\tif (!option.channel_types?.length) return channel;\n\t\t\t// `ChannelType` is not imported on purpose: its members are the numbers they are in the payload.\n\t\t\tconst types = [...new Set(option.channel_types)].sort((a, b) => a - b).join(' | ');\n\t\t\treturn `${channel} & { type: Extract<${channel}['type'], ${types}> }`;\n\t\t}\n\t\tdefault:\n\t\t\t// An option type Discord added after this was written: the handler still gets something.\n\t\t\treturn 'unknown';\n\t}\n}\n\nfunction key(name: string): string {\n\treturn IDENTIFIER.test(name) ? name : quote(name);\n}\n\nfunction quote(value: string): string {\n\tconst escaped = value\n\t\t.replaceAll('\\\\', '\\\\\\\\')\n\t\t.replaceAll(\"'\", \"\\\\'\")\n\t\t.replaceAll('\\n', '\\\\n')\n\t\t.replaceAll('\\r', '\\\\r')\n\t\t.replaceAll('
', '\\\\u2028')\n\t\t.replaceAll('
', '\\\\u2029');\n\treturn `'${escaped}'`;\n}\n","import { displayPath, type ResolvedStarsConfig } from '@wolfstar/schema';\nimport { applyEnvOptions, loadProject, withProjectEnv } from '../utils/hooks.js';\nimport { defineCommand } from 'citty';\nimport { createColors } from 'colorette';\nimport { spawn } from 'node:child_process';\nimport { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { projectArgs, resolveCwd, type ProjectArgs } from '../utils/args.js';\nimport type { CommandSnapshot } from '../dev/bridge.js';\nimport { generateCommandTypes } from '../utils/command-types.js';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport { shouldUseColor } from '../utils/output-mode.js';\nimport { readLocalCommands } from '../utils/local-commands.js';\nimport { resolveFromProject } from '../utils/project.js';\n\nexport interface CodegenTaskOptions extends ProjectArgs {\n\tcheck?: boolean;\n\tjson?: boolean;\n\tstdout?: NodeJS.WritableStream;\n\t/** Overrides the commands read from the built bot, for tests. */\n\tcommands?: CommandSnapshot;\n}\n\nexport interface CodegenResult {\n\tgenerator: 'i18n' | 'commands';\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 project = await loadProject({ cwd: resolveCwd(options), configFile: options.config });\n\tconst { config } = project;\n\tconst results: CodegenResult[] = [];\n\n\tif (config.codegen.i18n) results.push(await runI18n(config, Boolean(options.check)));\n\tif (config.codegen.commands) {\n\t\t// What the bot is started with: `env:options` (and varlock) may pick other env files than the defaults.\n\t\tconst botConfig = withProjectEnv(await applyEnvOptions(config, project.hooks));\n\t\tresults.push(await runCommands(botConfig, Boolean(options.check), options.commands));\n\t}\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 cliDiagnostics.CODEGEN_OUTDATED({});\n\t}\n}\n\n/**\n * Types the options of the commands the project registers. The commands are read from the built bot, the way\n * `stars commands diff` reads them: nothing connects to Discord, but the bot still runs its own env setup, so `--check`\n * in CI needs placeholders for the values it reads while starting (`DISCORD_CLIENT_ID`, `DISCORD_PUBLIC_KEY`, ...).\n */\nasync function runCommands(config: ResolvedStarsConfig, check: boolean, local?: CommandSnapshot): Promise<CodegenResult> {\n\tconst { output } = config.codegen.commands!;\n\tconst generated = generateCommandTypes(local ?? (await readLocalCommands(config)));\n\n\tif (!check) {\n\t\tawait mkdir(dirname(output), { recursive: true });\n\t\tawait writeFile(output, generated);\n\t\treturn { generator: 'commands', output, status: 'written' };\n\t}\n\n\tconst actual = await readFile(output, 'utf-8').catch(() => null);\n\treturn { generator: 'commands', output, status: generated === actual ? 'up-to-date' : 'outdated' };\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 cliDiagnostics.DEPENDENCY_MISSING({\n\t\t\tname: '@wolfstar/i18next-type-generator',\n\t\t\troot: config.root,\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 reject(cliDiagnostics.CODEGEN_FAILED({ code, stderr: stderr.trim() }));\n\t\t});\n\t});\n}\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'codegen',\n\t\tdescription: 'Run the configured code generators (i18next types, command options)'\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\tawait runCodegen({ config: args.config, cwd: args.cwd, check: args.check, json: args.json });\n\t}\n});\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ //#region src/commands/index.ts
2
+ const commands = {
3
+ dev: () => import("./dev-9YvzhtkU.js").then((module) => module.default),
4
+ build: () => import("./build-BbIEbOu3.js").then((module) => module.default),
5
+ info: () => import("./info-eXsx1AfE.js").then((module) => module.default),
6
+ codegen: () => import("./codegen-C2PY7RXA.js").then((module) => module.default),
7
+ prepare: () => import("./prepare-DKH0UIYA.js").then((module) => module.default),
8
+ typecheck: () => import("./typecheck-p_2rj9lL.js").then((module) => module.default),
9
+ commands: () => import("./commands-C-i3qYOz.js").then((module) => module.default),
10
+ doctor: () => import("./doctor-CMGfbXS5.js").then((module) => module.default),
11
+ completions: () => import("./completions-Cs6YbuXO.js").then((module) => module.default)
12
+ };
13
+
14
+ //#endregion
15
+ export { commands as t };
16
+ //# sourceMappingURL=commands-3Tgwy6cj.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands-3Tgwy6cj.js","names":[],"sources":["../src/commands/index.ts"],"sourcesContent":["import type { CommandDef } from 'citty';\n\n// citty types each command by its own arguments; the registry needs the erased form.\n// oxlint-disable-next-line typescript/no-explicit-any\ntype AnyCommand = CommandDef<any>;\n\nexport const commands: Record<string, () => Promise<AnyCommand>> = {\n\tdev: () => import('./dev.js').then((module) => module.default),\n\tbuild: () => import('./build.js').then((module) => module.default),\n\tinfo: () => import('./info.js').then((module) => module.default),\n\tcodegen: () => import('./codegen.js').then((module) => module.default),\n\tprepare: () => import('./prepare.js').then((module) => module.default),\n\ttypecheck: () => import('./typecheck.js').then((module) => module.default),\n\tcommands: () => import('./commands.js').then((module) => module.default),\n\tdoctor: () => import('./doctor.js').then((module) => module.default),\n\tcompletions: () => import('./completions.js').then((module) => module.default)\n};\n"],"mappings":";AAMA,MAAa,WAAsD;CAClE,WAAW,OAAO,oBAAW,CAAC,MAAM,WAAW,OAAO,OAAO;CAC7D,aAAa,OAAO,sBAAa,CAAC,MAAM,WAAW,OAAO,OAAO;CACjE,YAAY,OAAO,qBAAY,CAAC,MAAM,WAAW,OAAO,OAAO;CAC/D,eAAe,OAAO,wBAAe,CAAC,MAAM,WAAW,OAAO,OAAO;CACrE,eAAe,OAAO,wBAAe,CAAC,MAAM,WAAW,OAAO,OAAO;CACrE,iBAAiB,OAAO,0BAAiB,CAAC,MAAM,WAAW,OAAO,OAAO;CACzE,gBAAgB,OAAO,yBAAgB,CAAC,MAAM,WAAW,OAAO,OAAO;CACvE,cAAc,OAAO,uBAAc,CAAC,MAAM,WAAW,OAAO,OAAO;CACnE,mBAAmB,OAAO,4BAAmB,CAAC,MAAM,WAAW,OAAO,OAAO;AAC9E"}
@@ -0,0 +1,3 @@
1
+ import { t as commands } from "./commands-3Tgwy6cj.js";
2
+
3
+ export { commands };
@@ -1,13 +1,11 @@
1
1
  import { i as shouldUseColor } from "./output-mode-Bp7Da8vJ.js";
2
- import { t as cliDiagnostics } from "./diagnostics-BysbNnPD.js";
3
- import { c as moduleImportArgs, d as resolveCwd, n as loadProject, r as withProjectEnv, s as envImportArgs, t as applyEnvOptions, u as projectArgs } from "./hooks-Cy6F2bhH.js";
2
+ import { t as cliDiagnostics } from "./diagnostics-Dn3CXa8J.js";
3
+ import { d as resolveCwd, n as loadProject, r as withProjectEnv, t as applyEnvOptions, u as projectArgs } from "./hooks-D_RVS0ba.js";
4
+ import { t as readLocalCommands } from "./local-commands-CGBjc8KJ.js";
4
5
  import { i as matchesDeployed, n as diffCommands, t as describeCommand } from "./command-diff-BK912Hjc.js";
5
- import { i as bridgeImportArgs, l as readDiscordCredentials, o as parseBridgeMessage, r as DUMP_ENV, t as BRIDGE_FRAMEWORK_VERSION } from "./bridge-BegCt_n2.js";
6
+ import { r as readDiscordCredentials } from "./tunnel-BNV5cuJz.js";
6
7
  import { defineCommand } from "citty";
7
- import { existsSync } from "node:fs";
8
- import { displayPath } from "@wolfstar/schema";
9
8
  import { createColors } from "colorette";
10
- import { spawn } from "node:child_process";
11
9
 
12
10
  //#region src/utils/discord.ts
13
11
  const API = "https://discord.com/api/v10";
@@ -63,92 +61,6 @@ function createDiscordClient(config, env = process.env) {
63
61
  };
64
62
  }
65
63
 
66
- //#endregion
67
- //#region src/utils/local-commands.ts
68
- /**
69
- * The application commands the project defines, as the bot itself would register them.
70
- *
71
- * Commands are declared with builders and decorators that only exist once the bot's modules ran, so nothing short of
72
- * running them knows the answer. This starts the built bot with the dev bridge in its "dump" mode: it loads its
73
- * pieces, reports its registry over IPC and exits before it listens or talks to Discord.
74
- *
75
- * It is a plain start of the bot, not a `stars dev` session: `STARS_DEV` is not set, so commands a project only
76
- * registers while developing are not part of what `stars commands deploy` deploys.
77
- */
78
- async function readLocalCommands(config, options = {}) {
79
- if (!existsSync(config.build.output)) throw cliDiagnostics.LOCAL_COMMANDS_UNAVAILABLE({
80
- reason: `${displayPath(config.root, config.build.output)} does not exist`,
81
- fix: "Run `stars build` first: the commands are read from the built bot."
82
- });
83
- const bridge = bridgeImportArgs(config);
84
- if (bridge.length === 0) throw cliDiagnostics.LOCAL_COMMANDS_UNAVAILABLE({
85
- reason: `@wolfstar/http-framework ${BRIDGE_FRAMEWORK_VERSION} or later is not installed in ${config.root}`,
86
- fix: `Install @wolfstar/http-framework@^${BRIDGE_FRAMEWORK_VERSION} in the project.`
87
- });
88
- const child = spawn(process.execPath, [
89
- ...envImportArgs(config),
90
- ...moduleImportArgs(config),
91
- ...bridge,
92
- ...config.dev.nodeArgs,
93
- config.build.output,
94
- ...config.dev.args
95
- ], {
96
- cwd: config.root,
97
- env: {
98
- ...process.env,
99
- ...config.dev.env,
100
- NODE_ENV: process.env.NODE_ENV ?? "development",
101
- [DUMP_ENV]: "1"
102
- },
103
- stdio: [
104
- "ignore",
105
- "ignore",
106
- "pipe",
107
- "ipc"
108
- ],
109
- windowsHide: true
110
- });
111
- return new Promise((resolve, reject) => {
112
- let stderr = "";
113
- let settled = false;
114
- const finish = (action) => {
115
- if (settled) return;
116
- settled = true;
117
- clearTimeout(timer);
118
- action();
119
- };
120
- const unavailable = (reason) => cliDiagnostics.LOCAL_COMMANDS_UNAVAILABLE({
121
- reason,
122
- fix: "Check that the bot starts with `stars dev`: it has to construct its `Client` and call `client.load()`."
123
- });
124
- const timer = setTimeout(() => {
125
- child.kill("SIGKILL");
126
- finish(() => reject(unavailable("the bot did not load its commands in time")));
127
- }, options.timeout ?? 3e4);
128
- child.stderr?.on("data", (chunk) => stderr = `${stderr}${chunk.toString()}`.slice(-2e3));
129
- let snapshot = null;
130
- child.on("message", (message) => {
131
- const parsed = parseBridgeMessage(message);
132
- if (parsed?.type !== "commands" || snapshot !== null) return;
133
- snapshot = {
134
- global: parsed.global,
135
- guilds: parsed.guilds
136
- };
137
- setTimeout(() => child.kill("SIGKILL"), 2e3).unref();
138
- });
139
- child.once("error", (error) => finish(() => reject(unavailable(error.message))));
140
- child.once("exit", (code) => {
141
- if (snapshot !== null) {
142
- const commands = snapshot;
143
- finish(() => resolve(commands));
144
- return;
145
- }
146
- const detail = stderr.trim().split("\n").at(-1);
147
- finish(() => reject(unavailable(`the bot exited with code ${code} before loading its commands${detail ? ` (${detail})` : ""}`)));
148
- });
149
- });
150
- }
151
-
152
64
  //#endregion
153
65
  //#region src/utils/prompts.ts
154
66
  /**
@@ -545,4 +457,4 @@ var commands_default = defineCommand({
545
457
 
546
458
  //#endregion
547
459
  export { commands_default as default };
548
- //# sourceMappingURL=commands-Bh0K9iWX.js.map
460
+ //# sourceMappingURL=commands-C-i3qYOz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands-C-i3qYOz.js","names":[],"sources":["../src/utils/discord.ts","../src/utils/prompts.ts","../src/commands/commands.ts"],"sourcesContent":["import type { ResolvedStarsConfig } from '@wolfstar/schema';\nimport { cliDiagnostics } from './diagnostics.js';\nimport { readDiscordCredentials } from '../dev/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\t/** Replaces every command of the scope with `commands` (Discord's bulk overwrite): what is not listed is deleted. */\n\tputCommands(guildId: string | null, commands: readonly object[]): Promise<ApplicationCommand[]>;\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 cliDiagnostics.DISCORD_TOKEN_MISSING({});\n\t}\n\n\tif (!credentials.applicationId) {\n\t\tthrow cliDiagnostics.DISCORD_APPLICATION_ID_MISSING({});\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, payload?: unknown): Promise<unknown> => {\n\t\tconst response = await fetch(`${API}${path}`, {\n\t\t\tmethod,\n\t\t\theaders: { authorization: `Bot ${credentials.token}`, ...(payload === undefined ? {} : { 'content-type': 'application/json' }) },\n\t\t\t...(payload === undefined ? {} : { body: JSON.stringify(payload) }),\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 cliDiagnostics.DISCORD_REQUEST_FAILED({ status: response.status, detail });\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\tasync putCommands(guildId, commands) {\n\t\t\tconst body = await request('PUT', scope(guildId), commands);\n\t\t\treturn Array.isArray(body) ? (body as ApplicationCommand[]) : [];\n\t\t}\n\t};\n}\n","import type { Diagnostic } from 'nostics';\nimport type { ApplicationCommand } from './discord.js';\nimport { cliDiagnostics } from './diagnostics.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 Boolean(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(): Diagnostic {\n\treturn cliDiagnostics.ABORTED({});\n}\n","import { applyEnvOptions, loadProject, withProjectEnv } from '../utils/hooks.js';\nimport { defineCommand } from 'citty';\nimport { createColors } from 'colorette';\nimport type { Diagnostic } from 'nostics';\nimport type { ResolvedStarsConfig } from '@wolfstar/schema';\nimport type { CommandData, CommandSnapshot } from '../dev/bridge.js';\nimport { describeCommand, diffCommands, matchesDeployed, type CommandChange } from '../utils/command-diff.js';\nimport { COMMAND_TYPE_NAMES, createDiscordClient, type ApplicationCommand, type DiscordClient } from '../utils/discord.js';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport { readLocalCommands } from '../utils/local-commands.js';\nimport { shouldUseColor } from '../utils/output-mode.js';\nimport { createClackPrompt, type CommandsPrompt } from '../utils/prompts.js';\nimport { projectArgs, resolveCwd, type ProjectArgs } from '../utils/args.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\nexport interface CommandsDiffOptions extends CommandsTaskOptions {\n\t/** Fails when the deployed commands differ, for CI. */\n\tcheck?: boolean;\n\t/** Overrides the commands read from the built bot, for tests. */\n\tlocal?: CommandSnapshot;\n}\n\nexport interface CommandsDeployOptions extends CommandsDiffOptions {\n\t/** Deploys without asking. Required in a non-interactive terminal. */\n\tyes?: boolean;\n\tstdin?: NodeJS.ReadableStream & { isTTY?: boolean };\n\t/** Overrides the confirmation, for tests. */\n\tprompt?: Pick<CommandsPrompt, 'confirm'>;\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 cliDiagnostics.COMMAND_NOT_FOUND({ names: missing.join(', ') });\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\nconst SYMBOLS = { added: '+', removed: '-', changed: '~' } as const;\n\n/**\n * What a deploy of `guild` (or the global scope) would change: the commands the project defines for it against the\n * ones Discord has. A command is `changed` when the project's definition no longer matches (see `matchesDeployed`).\n */\nasync function compare(options: CommandsDiffOptions): Promise<{\n\tclient: DiscordClient;\n\tguild: string | null;\n\tlocal: CommandData[];\n\tchanges: CommandChange[];\n}> {\n\tconst project = await loadProject({ cwd: resolveCwd(options), configFile: options.config });\n\t// What the bot is started with: `env:options` (and varlock) may pick other env files than the defaults.\n\tconst config = withProjectEnv(await applyEnvOptions(project.config, project.hooks));\n\tconst { client, guild } = connectTo(config, options);\n\tconst snapshot = options.local ?? (await readLocalCommands(config));\n\tconst local = guild === null ? snapshot.global : (snapshot.guilds[guild] ?? []);\n\tconst deployed = (await client.listCommands(guild)) as unknown as CommandData[];\n\t// Seen from Discord: what a deploy would add to, change in and remove from the deployed commands.\n\treturn { client, guild, local, changes: diffCommands(deployed, local, guild, (old, next) => matchesDeployed(next, old)) };\n}\n\nfunction writeChanges(stdout: NodeJS.WritableStream, colors: ReturnType<typeof createColors>, changes: readonly CommandChange[]): void {\n\tconst paint = { added: colors.green, removed: colors.red, changed: colors.yellow };\n\tfor (const change of changes) {\n\t\tstdout.write(\n\t\t\t` ${paint[change.kind](`${SYMBOLS[change.kind]} ${describeCommand({ ...change, guild: null })}`)} ${colors.dim(change.kind)}\\n`\n\t\t);\n\t}\n}\n\n/**\n * Compares the commands the project defines with the ones Discord has deployed, without changing anything: what\n * `stars commands deploy` would do. `--check` makes a difference an error, for CI.\n */\nexport async function runCommandsDiff(options: CommandsDiffOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() && !options.json });\n\tconst { client, guild, changes } = await compare(options);\n\n\tif (options.json) {\n\t\tstdout.write(`${JSON.stringify({ applicationId: client.applicationId, guildId: guild, changes }, null, 2)}\\n`);\n\t} else if (changes.length === 0) {\n\t\tstdout.write(`${colors.dim('stars')} ${guild ? `guild ${guild}` : 'the global scope'} is up to date\\n`);\n\t} else {\n\t\tstdout.write(`${colors.dim('stars')} ${changes.length} difference(s) in ${guild ? `guild ${guild}` : 'the global scope'}\\n`);\n\t\twriteChanges(stdout, colors, changes);\n\t}\n\n\tif (options.check && changes.length > 0) throw cliDiagnostics.COMMANDS_DIFFER({ count: changes.length });\n}\n\n/**\n * Deploys the commands the project defines to Discord with a bulk overwrite: a deployed command the project no\n * longer defines is deleted. It shows what will change and asks first; scripts pass `--yes`.\n */\nexport async function runCommandsDeploy(options: CommandsDeployOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() && !options.json });\n\tconst { client, guild, local, changes } = await compare(options);\n\tconst scope = guild ? `guild ${guild}` : 'the global scope';\n\n\tif (changes.length === 0) {\n\t\tif (options.json) stdout.write(`${JSON.stringify({ applicationId: client.applicationId, guildId: guild, deployed: 0, changes }, null, 2)}\\n`);\n\t\telse stdout.write(`${colors.dim('stars')} ${scope} is up to date, nothing to deploy\\n`);\n\t\treturn;\n\t}\n\n\tif (!options.json) {\n\t\tstdout.write(`${colors.dim('stars')} deploying to ${scope}\\n`);\n\t\twriteChanges(stdout, colors, changes);\n\t}\n\n\tif (!options.yes) {\n\t\tconst stdin = options.stdin ?? process.stdin;\n\t\t// `--json` owns stdout: a question printed there would end up in front of the document.\n\t\tif (!options.prompt && (!stdin.isTTY || options.json)) throw cliDiagnostics.DEPLOY_CONFIRMATION_REQUIRED({});\n\t\tconst prompt = options.prompt ?? createClackPrompt();\n\t\tif (!(await prompt.confirm(`Overwrite the commands of ${scope} with the ${local.length} the project defines?`))) {\n\t\t\tthrow cliDiagnostics.ABORTED({});\n\t\t}\n\t}\n\n\tconst deployed = await client.putCommands(guild, local);\n\tif (options.json) {\n\t\tstdout.write(`${JSON.stringify({ applicationId: client.applicationId, guildId: guild, deployed: deployed.length, changes }, null, 2)}\\n`);\n\t} else {\n\t\tstdout.write(`${colors.dim('stars')} ${colors.green('deployed')} ${deployed.length} command(s) to ${scope}\\n`);\n\t}\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 cliDiagnostics.ABORTED({});\n\t}\n\n\treturn targets;\n}\n\nasync function connect(options: CommandsTaskOptions): Promise<{ client: DiscordClient; guild: string | null }> {\n\tconst { config } = await loadProject({ cwd: resolveCwd(options), configFile: options.config });\n\treturn connectTo(config, options);\n}\n\nfunction connectTo(config: ResolvedStarsConfig, options: CommandsTaskOptions): { client: DiscordClient; guild: string | null } {\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 cliDiagnostics.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(cliDiagnostics.ABORTED({}));\n\t\t});\n\t\tstdin.resume?.();\n\t});\n}\n\nfunction confirmationRequired(): Diagnostic {\n\treturn cliDiagnostics.CONFIRMATION_REQUIRED({});\n}\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\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 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\nconst diff = defineCommand({\n\tmeta: { name: 'diff', description: 'Compare the commands the built bot defines with the ones Discord has deployed' },\n\targs: {\n\t\t...scopeArgs,\n\t\tcheck: {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Fail when the deployed commands differ, for CI',\n\t\t\tdefault: false\n\t\t}\n\t},\n\tasync run({ args }) {\n\t\tawait runCommandsDiff({ config: args.config, cwd: args.cwd, guild: args.guild, json: args.json, check: args.check });\n\t}\n});\n\nconst deploy = defineCommand({\n\tmeta: { name: 'deploy', description: 'Deploy the commands the built bot defines, replacing the deployed ones' },\n\targs: {\n\t\t...scopeArgs,\n\t\tyes: {\n\t\t\ttype: 'boolean',\n\t\t\talias: 'y',\n\t\t\tdescription: 'Deploy without asking for a confirmation',\n\t\t\tdefault: false\n\t\t}\n\t},\n\tasync run({ args }) {\n\t\tawait runCommandsDeploy({ config: args.config, cwd: args.cwd, guild: args.guild, json: args.json, yes: args.yes });\n\t}\n});\n\nconst SUBCOMMANDS = ['list', 'clean', 'diff', 'deploy'];\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'commands',\n\t\tdescription: 'Inspect, compare, deploy and clean the application commands deployed to Discord'\n\t},\n\tsubCommands: { list, clean, diff, deploy },\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) => SUBCOMMANDS.includes(argument))) return;\n\n\t\tawait runCommandsList({ config: args.config, cwd: args.cwd, guild: args.guild, json: args.json });\n\t}\n});\n"],"mappings":"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"}