@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.
Files changed (65) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +151 -0
  3. package/dist/args-Ch1KXUMn.js +24 -0
  4. package/dist/args-Ch1KXUMn.js.map +1 -0
  5. package/dist/build-Ct6zabKg.js +22 -0
  6. package/dist/build-Ct6zabKg.js.map +1 -0
  7. package/dist/build-DU76s-Ec.js +39 -0
  8. package/dist/build-DU76s-Ec.js.map +1 -0
  9. package/dist/builders-BzLMIm9Y.js +44 -0
  10. package/dist/builders-BzLMIm9Y.js.map +1 -0
  11. package/dist/cli.d.ts +1 -0
  12. package/dist/cli.js +58 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/codegen-DjPOn7cp.js +95 -0
  15. package/dist/codegen-DjPOn7cp.js.map +1 -0
  16. package/dist/codegen-mYkL-GBf.js +36 -0
  17. package/dist/codegen-mYkL-GBf.js.map +1 -0
  18. package/dist/commands-BkGKs_Bg.js +88 -0
  19. package/dist/commands-BkGKs_Bg.js.map +1 -0
  20. package/dist/commands-Dw2LFWV-.js +233 -0
  21. package/dist/commands-Dw2LFWV-.js.map +1 -0
  22. package/dist/dev-DWg8l23a.js +604 -0
  23. package/dist/dev-DWg8l23a.js.map +1 -0
  24. package/dist/dev-QUxuLcXj.js +30 -0
  25. package/dist/dev-QUxuLcXj.js.map +1 -0
  26. package/dist/errors-Bket6XFn.js +50 -0
  27. package/dist/errors-Bket6XFn.js.map +1 -0
  28. package/dist/external-Bk4EESdT.js +59 -0
  29. package/dist/external-Bk4EESdT.js.map +1 -0
  30. package/dist/index.d.ts +66 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +5 -0
  33. package/dist/info-B-8rAIn6.js +106 -0
  34. package/dist/info-B-8rAIn6.js.map +1 -0
  35. package/dist/info-DRTYYvVB.js +30 -0
  36. package/dist/info-DRTYYvVB.js.map +1 -0
  37. package/dist/none-DEo7zEr9.js +50 -0
  38. package/dist/none-DEo7zEr9.js.map +1 -0
  39. package/dist/output-mode-ChrOf0LO.js +46 -0
  40. package/dist/output-mode-ChrOf0LO.js.map +1 -0
  41. package/dist/plain-CEe4X_Cz.js +43 -0
  42. package/dist/plain-CEe4X_Cz.js.map +1 -0
  43. package/dist/prepare-BXcm2Ukp.js +3 -0
  44. package/dist/prepare-CVf88_Or.js +36 -0
  45. package/dist/prepare-CVf88_Or.js.map +1 -0
  46. package/dist/prepare-D5NZ0w4t.js +68 -0
  47. package/dist/prepare-D5NZ0w4t.js.map +1 -0
  48. package/dist/process-supervisor-CJOdmp0W.js +128 -0
  49. package/dist/process-supervisor-CJOdmp0W.js.map +1 -0
  50. package/dist/project-DHF3qdm-.js +66 -0
  51. package/dist/project-DHF3qdm-.js.map +1 -0
  52. package/dist/tsc-BqZfDTD0.js +133 -0
  53. package/dist/tsc-BqZfDTD0.js.map +1 -0
  54. package/dist/tsc-CovVAtfo.js +3 -0
  55. package/dist/tsdown-DlAs0rSI.js +104 -0
  56. package/dist/tsdown-DlAs0rSI.js.map +1 -0
  57. package/dist/tui-BTRFsLQR.js +770 -0
  58. package/dist/tui-BTRFsLQR.js.map +1 -0
  59. package/dist/tunnel-B8Id4-lR.js +205 -0
  60. package/dist/tunnel-B8Id4-lR.js.map +1 -0
  61. package/dist/version-DN6Uho-Y.js +30 -0
  62. package/dist/version-DN6Uho-Y.js.map +1 -0
  63. package/dist/vite-D3vYFsqa.js +93 -0
  64. package/dist/vite-D3vYFsqa.js.map +1 -0
  65. package/package.json +95 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-BTRFsLQR.js","names":[],"sources":["../src/renderers/ink/theme.tsx","../src/renderers/ink/components/HelpOverlay.tsx","../src/renderers/ink/hooks/useLogVersion.ts","../src/renderers/ink/components/LogLine.tsx","../src/renderers/ink/components/LogBrowser.tsx","../src/renderers/panel-logic.ts","../src/renderers/ink/components/Panel.tsx","../src/renderers/ink/hooks/useDevStatus.ts","../src/renderers/ink/hooks/useLogCounters.ts","../src/renderers/ink/hooks/useSpinner.ts","../src/renderers/ink/hooks/useStaticLog.ts","../src/renderers/ink/hooks/useUptime.ts","../src/renderers/ink/DevApp.tsx","../src/renderers/tui.tsx"],"sourcesContent":["import { createContext, useContext, type ReactNode } from 'react';\n\n/**\n * Whether the UI may use colour. `stars dev` decides this once (`NO_COLOR`, `FORCE_COLOR`, TTY detection) and every\n * component reads it from here instead of re-deriving it.\n */\nconst ColorContext = createContext(true);\n\nexport function ColorProvider({ color, children }: { color: boolean; children: ReactNode }) {\n\treturn <ColorContext value={color}>{children}</ColorContext>;\n}\n\n/**\n * Returns a colour name only when colour is enabled, so `<Text color={useColor()('green')}>` degrades to plain text.\n */\nexport function useColor(): (name: string) => string | undefined {\n\tconst enabled = useContext(ColorContext);\n\treturn (name) => (enabled ? name : undefined);\n}\n","import { Box, Text, useInput } from 'ink';\nimport { useColor } from '../theme.js';\n\nconst KEYS: [string, string][] = [\n\t['r', 'restart the bot now'],\n\t['l', 'open the logs (scroll, filter by source or level)'],\n\t['q / Ctrl+C', 'stop the bot and quit'],\n\t['?', 'toggle this help']\n];\n\nexport interface HelpOverlayProps {\n\theight: number;\n\tonClose: () => void;\n}\n\n/** The static keys reference, opened with `?` from the pinned panel. */\nexport function HelpOverlay({ height, onClose }: HelpOverlayProps) {\n\tconst paint = useColor();\n\tuseInput(() => onClose());\n\n\treturn (\n\t\t<Box flexDirection=\"column\" height={height}>\n\t\t\t<Text bold>Keys</Text>\n\t\t\t<Text dimColor>{'─'.repeat(60)}</Text>\n\t\t\t{KEYS.map(([key, description]) => (\n\t\t\t\t<Box key={key} gap={1}>\n\t\t\t\t\t<Text bold color={paint('white')}>\n\t\t\t\t\t\t{key.padEnd(14)}\n\t\t\t\t\t</Text>\n\t\t\t\t\t<Text dimColor>{description}</Text>\n\t\t\t\t</Box>\n\t\t\t))}\n\t\t\t<Box flexGrow={1} />\n\t\t\t<Text dimColor>press any key to close</Text>\n\t\t</Box>\n\t);\n}\n","import { useEffect, useState } from 'react';\nimport type { LogBuffer } from '../../../lib/log-buffer.js';\n\n/** Bumps whenever the buffer changes, so a consumer can re-derive a filtered view without re-filtering on every render. */\nexport function useLogVersion(logs: LogBuffer): number {\n\tconst [version, setVersion] = useState(0);\n\n\tuseEffect(() => {\n\t\tconst bump = () => setVersion((current) => current + 1);\n\t\tlogs.on('entry', bump);\n\t\tlogs.on('clear', bump);\n\t\treturn () => {\n\t\t\tlogs.off('entry', bump);\n\t\t\tlogs.off('clear', bump);\n\t\t};\n\t}, [logs]);\n\n\treturn version;\n}\n","import { Text } from 'ink';\nimport type { LogEntry, LogSource } from '../../../lib/log-buffer.js';\nimport { useColor } from '../theme.js';\n\nconst SOURCE_COLORS: Partial<Record<LogSource, string>> = { stars: 'cyan', build: 'magenta', tsc: 'cyan', tunnel: 'yellow' };\n\n/** One committed log line, formatted the same way plain mode prints it, so scrollback reads identically either way. */\nexport function LogLine({ entry }: { entry: LogEntry }) {\n\tconst paint = useColor();\n\tconst source = SOURCE_COLORS[entry.source];\n\tconst level = entry.level === 'error' ? 'red' : entry.level === 'warn' ? 'yellow' : entry.level === 'success' ? 'green' : undefined;\n\n\tif (!source) return <Text color={level ? paint(level) : undefined}>{entry.text}</Text>;\n\n\treturn (\n\t\t<Text>\n\t\t\t<Text color={paint(source)}>{entry.source}</Text> <Text color={level ? paint(level) : undefined}>{entry.text}</Text>\n\t\t</Text>\n\t);\n}\n","import { Box, Text, useInput } from 'ink';\nimport { useMemo, useState } from 'react';\nimport type { DevService } from '../../../lib/dev-service.js';\nimport type { LogEntry, LogLevel, LogSource } from '../../../lib/log-buffer.js';\nimport { useLogVersion } from '../hooks/useLogVersion.js';\nimport { useColor } from '../theme.js';\nimport { LogLine } from './LogLine.js';\n\nconst SOURCE_FILTERS: (LogSource | null)[] = [null, 'app', 'build', 'tsc', 'tunnel', 'stars'];\nconst LEVEL_FILTERS: (LogLevel | null)[] = [null, 'warn', 'error'];\n\nexport interface LogBrowserProps {\n\tservice: DevService;\n\theight: number;\n\tonClose: () => void;\n}\n\n/**\n * The full, filterable log history, opened with `l` from the panel. The pinned panel only ever shows a live count\n * of warnings/errors (Nuxt CLI v4's own dev panel does the same); browsing what actually happened lives here.\n */\nexport function LogBrowser({ service, height, onClose }: LogBrowserProps) {\n\tconst paint = useColor();\n\tconst [sourceIndex, setSourceIndex] = useState(0);\n\tconst [levelIndex, setLevelIndex] = useState(0);\n\tconst [scroll, setScroll] = useState(0);\n\n\tconst source = SOURCE_FILTERS[sourceIndex] ?? null;\n\tconst level = LEVEL_FILTERS[levelIndex] ?? null;\n\tconst version = useLogVersion(service.logs);\n\tconst entries = useMemo(() => service.logs.filter({ source, level }), [service, source, level, version]);\n\n\tconst bodyHeight = Math.max(1, height - 3);\n\tconst maxScroll = Math.max(0, entries.length - bodyHeight);\n\tconst clamped = Math.min(scroll, maxScroll);\n\tconst end = entries.length - clamped;\n\tconst visible = entries.slice(Math.max(0, end - bodyHeight), end);\n\n\tuseInput((input, key) => {\n\t\tif (key.escape || input === 'l' || input === 'q') return onClose();\n\t\tif (key.ctrl && input === 'c') return onClose();\n\n\t\tswitch (input) {\n\t\t\tcase 'f':\n\t\t\t\tsetSourceIndex((current) => (current + 1) % SOURCE_FILTERS.length);\n\t\t\t\treturn setScroll(0);\n\t\t\tcase 'e':\n\t\t\t\tsetLevelIndex((current) => (current + 1) % LEVEL_FILTERS.length);\n\t\t\t\treturn setScroll(0);\n\t\t\tcase 'c':\n\t\t\t\treturn service.logs.clear();\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (key.downArrow || input === 'j') return setScroll((current) => Math.max(0, current - 1));\n\t\tif (key.upArrow || input === 'k') return setScroll((current) => Math.min(maxScroll, current + 1));\n\t\tif (key.pageDown) return setScroll((current) => Math.max(0, current - bodyHeight));\n\t\tif (key.pageUp) return setScroll((current) => Math.min(maxScroll, current + bodyHeight));\n\t\tif (key.end) return setScroll(0);\n\t});\n\n\treturn (\n\t\t<Box flexDirection=\"column\" height={height}>\n\t\t\t<Box gap={1}>\n\t\t\t\t<Text bold>stars dev logs</Text>\n\t\t\t\t<Text dimColor>{`filter ${source ?? 'all'}${level ? `, level ≥ ${level}` : ''}`}</Text>\n\t\t\t</Box>\n\t\t\t<Text dimColor>{'─'.repeat(60)}</Text>\n\t\t\t<Box flexDirection=\"column\" height={bodyHeight}>\n\t\t\t\t{visible.length === 0 ? (\n\t\t\t\t\t<Text dimColor>no logs yet</Text>\n\t\t\t\t) : (\n\t\t\t\t\tvisible.map((entry: LogEntry) => <LogLine key={entry.id} entry={entry} />)\n\t\t\t\t)}\n\t\t\t</Box>\n\t\t\t<Box gap={2}>\n\t\t\t\t<Text>\n\t\t\t\t\t<Text bold color={paint('white')}>\n\t\t\t\t\t\tf\n\t\t\t\t\t</Text>{' '}\n\t\t\t\t\t<Text dimColor>source</Text>\n\t\t\t\t</Text>\n\t\t\t\t<Text>\n\t\t\t\t\t<Text bold color={paint('white')}>\n\t\t\t\t\t\te\n\t\t\t\t\t</Text>{' '}\n\t\t\t\t\t<Text dimColor>errors</Text>\n\t\t\t\t</Text>\n\t\t\t\t<Text>\n\t\t\t\t\t<Text bold color={paint('white')}>\n\t\t\t\t\t\tc\n\t\t\t\t\t</Text>{' '}\n\t\t\t\t\t<Text dimColor>clear</Text>\n\t\t\t\t</Text>\n\t\t\t\t<Text>\n\t\t\t\t\t<Text bold color={paint('white')}>\n\t\t\t\t\t\t↑↓\n\t\t\t\t\t</Text>{' '}\n\t\t\t\t\t<Text dimColor>scroll</Text>\n\t\t\t\t</Text>\n\t\t\t\t<Text>\n\t\t\t\t\t<Text bold color={paint('white')}>\n\t\t\t\t\t\tq / esc / l\n\t\t\t\t\t</Text>{' '}\n\t\t\t\t\t<Text dimColor>close</Text>\n\t\t\t\t</Text>\n\t\t\t</Box>\n\t\t</Box>\n\t);\n}\n","import type { DevStatus } from '../lib/dev-service.js';\n\nexport type Badge = 'starting' | 'building' | 'restarting' | 'ready' | 'error';\n\n/**\n * The badge and standing description for the panel's status row, mirroring the states Nuxt CLI v4's own dev panel\n * cycles through (`STARTING`/`BUILDING`/`RESTART`/`READY`/`ERROR`).\n */\nexport function describeBadge(status: DevStatus): { badge: Badge; note: string } {\n\tif (status.build === 'building') return { badge: 'building', note: 'compiling changes' };\n\tif (status.process === 'starting') return { badge: 'starting', note: 'starting the bot' };\n\tif (status.process === 'stopping') return { badge: 'restarting', note: 'restarting the bot' };\n\tif (status.build === 'failed') return { badge: 'error', note: 'build failed, waiting for changes' };\n\tif (status.process === 'crashed') return { badge: 'error', note: 'the bot crashed, press r to restart' };\n\tif (status.process === 'running') return { badge: 'ready', note: 'watching for changes' };\n\treturn { badge: 'starting', note: 'waiting for the first build' };\n}\n\n/** Formats a duration the way a dev session reads it: `mm:ss` under an hour, `h:mm:ss` above. */\nexport function formatDuration(milliseconds: number): string {\n\tconst total = Math.max(0, Math.floor(milliseconds / 1000));\n\tconst seconds = total % 60;\n\tconst minutes = Math.floor(total / 60) % 60;\n\tconst hours = Math.floor(total / 3600);\n\tconst pad = (value: number) => value.toString().padStart(2, '0');\n\n\treturn hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;\n}\n","import { Box, Text } from 'ink';\nimport type { ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport type { DevStatus } from '../../../lib/dev-service.js';\nimport type { LogCounters } from '../hooks/useLogCounters.js';\nimport { describeBadge, formatDuration, type Badge } from '../../panel-logic.js';\nimport { useColor } from '../theme.js';\n\nconst BADGE_COLOR: Record<Badge, string> = { starting: 'yellow', building: 'yellow', restarting: 'yellow', ready: 'green', error: 'red' };\n\nexport interface PanelHint {\n\tkey: string;\n\tlabel: string;\n}\n\nexport const DEFAULT_HINTS: PanelHint[] = [\n\t{ key: 'r', label: 'restart' },\n\t{ key: 'l', label: 'logs' },\n\t{ key: '?', label: 'help' },\n\t{ key: 'q', label: 'quit' }\n];\n\nexport interface PanelProps {\n\tname: string;\n\tstatus: DevStatus;\n\tconfig: ResolvedStarsConfig;\n\tuptimeMs: number | null;\n\tcounters: LogCounters;\n\tspinner: string;\n\tnarrow: boolean;\n}\n\n/**\n * The pinned dev panel: a handful of lines below the log stream, redrawn as state changes. Mirrors the blocks\n * Nuxt CLI v4's own dev panel shows — a wordmark, the URL with its live checks, a warning/error summary, a status\n * badge and the shortcut hints — kept to a fixed, small height instead of growing with the session.\n */\nexport function Panel({ name, status, config, uptimeMs, counters, spinner, narrow }: PanelProps) {\n\tconst { badge, note } = describeBadge(status);\n\n\treturn (\n\t\t<Box flexDirection=\"column\">\n\t\t\t<Wordmark name={name} status={status} uptimeMs={uptimeMs} narrow={narrow} />\n\t\t\t{status.url && <UrlRow status={status} config={config} spinner={spinner} narrow={narrow} />}\n\t\t\t<Summary counters={counters} />\n\t\t\t<StatusRow badge={badge} note={note} />\n\t\t\t<Hints errors={counters.errors} narrow={narrow} />\n\t\t</Box>\n\t);\n}\n\nfunction Wordmark({ name, status, uptimeMs, narrow }: { name: string; status: DevStatus; uptimeMs: number | null; narrow: boolean }) {\n\tconst paint = useColor();\n\treturn (\n\t\t<Box gap={narrow ? 1 : 2}>\n\t\t\t<Text bold color={paint('yellow')}>\n\t\t\t\t★ stars dev\n\t\t\t</Text>\n\t\t\t<Text dimColor>{name}</Text>\n\t\t\t{!narrow && uptimeMs !== null && <Text dimColor>{`up ${formatDuration(uptimeMs)}`}</Text>}\n\t\t\t{!narrow && status.restarts > 0 && <Text dimColor>{`restarts ${status.restarts}`}</Text>}\n\t\t</Box>\n\t);\n}\n\nfunction UrlRow({ status, config, spinner, narrow }: { status: DevStatus; config: ResolvedStarsConfig; spinner: string; narrow: boolean }) {\n\tconst paint = useColor();\n\treturn (\n\t\t<Box gap={narrow ? 1 : 2}>\n\t\t\t<Text color={paint('cyan')}>{status.url}</Text>\n\t\t\t{!narrow && config.dev.health !== null && <Health status={status} />}\n\t\t\t{!narrow && config.dev.typecheck.enabled && <Typecheck status={status} spinner={spinner} />}\n\t\t\t{!narrow && status.tunnel !== 'off' && <Tunnel status={status} spinner={spinner} />}\n\t\t</Box>\n\t);\n}\n\nfunction Health({ status }: { status: DevStatus }) {\n\tconst paint = useColor();\n\tswitch (status.health) {\n\t\tcase 'ok':\n\t\t\treturn <Text color={paint('green')}>health ok</Text>;\n\t\tcase 'down':\n\t\t\treturn <Text color={paint('red')}>health down</Text>;\n\t\tdefault:\n\t\t\treturn <Text dimColor>health unknown</Text>;\n\t}\n}\n\nfunction Typecheck({ status, spinner }: { status: DevStatus; spinner: string }) {\n\tconst paint = useColor();\n\tswitch (status.typecheck) {\n\t\tcase 'checking':\n\t\t\treturn <Text dimColor>{`${spinner} types`}</Text>;\n\t\tcase 'ok':\n\t\t\treturn <Text color={paint('green')}>types ok</Text>;\n\t\tcase 'failed':\n\t\t\treturn <Text color={paint('red')}>{`types ${status.typeErrors}`}</Text>;\n\t\tdefault:\n\t\t\treturn <Text dimColor>types idle</Text>;\n\t}\n}\n\nfunction Tunnel({ status, spinner }: { status: DevStatus; spinner: string }) {\n\tconst paint = useColor();\n\tswitch (status.tunnel) {\n\t\tcase 'starting':\n\t\t\treturn <Text dimColor>{`${spinner} tunnel`}</Text>;\n\t\tcase 'up':\n\t\t\treturn <Text color={paint('green')}>{status.tunnelUrl ?? 'tunnel up'}</Text>;\n\t\tcase 'failed':\n\t\t\treturn <Text color={paint('red')}>tunnel failed</Text>;\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction Summary({ counters }: { counters: LogCounters }) {\n\tconst paint = useColor();\n\tif (counters.warnings === 0 && counters.errors === 0) return null;\n\n\treturn (\n\t\t<Box gap={2}>\n\t\t\t{counters.warnings > 0 && <Text color={paint('yellow')}>{`⚠ ${counters.warnings} ${plural(counters.warnings, 'warning')}`}</Text>}\n\t\t\t{counters.errors > 0 && <Text bold color={paint('red')}>{`✖ ${counters.errors} ${plural(counters.errors, 'error')}`}</Text>}\n\t\t</Box>\n\t);\n}\n\nfunction StatusRow({ badge, note }: { badge: Badge; note: string }) {\n\tconst paint = useColor();\n\treturn (\n\t\t<Box gap={1}>\n\t\t\t<Text backgroundColor={paint(BADGE_COLOR[badge])} color={paint('black')} bold>\n\t\t\t\t{` ${badge.toUpperCase()} `}\n\t\t\t</Text>\n\t\t\t<Text dimColor>{note}</Text>\n\t\t</Box>\n\t);\n}\n\nfunction Hints({ errors, narrow }: { errors: number; narrow: boolean }) {\n\tconst paint = useColor();\n\tconst hints = [...(errors > 0 ? [{ key: 'e', label: 'last error' }] : []), ...DEFAULT_HINTS];\n\tconst shown = narrow ? hints.slice(0, 2) : hints;\n\n\treturn (\n\t\t<Box gap={narrow ? 1 : 2}>\n\t\t\t{shown.map(({ key, label }) => (\n\t\t\t\t<Text key={key}>\n\t\t\t\t\t<Text bold color={paint('white')}>\n\t\t\t\t\t\t{key}\n\t\t\t\t\t</Text>{' '}\n\t\t\t\t\t<Text dimColor>{label}</Text>\n\t\t\t\t</Text>\n\t\t\t))}\n\t\t</Box>\n\t);\n}\n\nfunction plural(count: number, word: string): string {\n\treturn count === 1 ? word : `${word}s`;\n}\n","import { useEffect, useState } from 'react';\nimport type { DevService, DevStatus } from '../../../lib/dev-service.js';\n\n/**\n * Mirrors the service's status into React state. The service stays the single source of truth: the UI only\n * subscribes to it, exactly like the plain renderer does.\n */\nexport function useDevStatus(service: DevService): DevStatus {\n\tconst [status, setStatus] = useState<DevStatus>(() => service.status);\n\n\tuseEffect(() => {\n\t\tconst onStatus = (next: DevStatus) => setStatus(next);\n\t\tservice.on('status', onStatus);\n\t\tsetStatus(service.status);\n\t\treturn () => {\n\t\t\tservice.off('status', onStatus);\n\t\t};\n\t}, [service]);\n\n\treturn status;\n}\n","import { useEffect, useState } from 'react';\nimport type { DevService, DevStatus } from '../../../lib/dev-service.js';\nimport type { LogEntry } from '../../../lib/log-buffer.js';\n\nexport interface LogCounters {\n\twarnings: number;\n\terrors: number;\n}\n\n/**\n * Warnings and errors logged since the last successful build, the way Nuxt CLI v4's own panel counts them: the\n * badge's job is the current state, this line is what happened getting there.\n */\nexport function useLogCounters(service: DevService): LogCounters {\n\tconst [counters, setCounters] = useState<LogCounters>({ warnings: 0, errors: 0 });\n\n\tuseEffect(() => {\n\t\tlet previousBuild: DevStatus['build'] = service.status.build;\n\n\t\tconst onEntry = (entry: LogEntry) => {\n\t\t\tif (entry.level === 'warn') setCounters((current) => ({ ...current, warnings: current.warnings + 1 }));\n\t\t\telse if (entry.level === 'error') setCounters((current) => ({ ...current, errors: current.errors + 1 }));\n\t\t};\n\n\t\tconst onStatus = (status: DevStatus) => {\n\t\t\tif (status.build === 'ok' && previousBuild !== 'ok') setCounters({ warnings: 0, errors: 0 });\n\t\t\tpreviousBuild = status.build;\n\t\t};\n\n\t\tservice.logs.on('entry', onEntry);\n\t\tservice.on('status', onStatus);\n\t\treturn () => {\n\t\t\tservice.logs.off('entry', onEntry);\n\t\t\tservice.off('status', onStatus);\n\t\t};\n\t}, [service]);\n\n\treturn counters;\n}\n","import { useEffect, useState } from 'react';\n\nconst FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] as const;\nconst STILL = '•';\n\n/**\n * A spinner frame for whatever is in flight (a build, a tunnel coming up). `STARS_REDUCED_MOTION=1` and non-TTY\n * output freeze it on a static character instead.\n */\nexport function useSpinner(active: boolean, reducedMotion: boolean): string {\n\tconst [frame, setFrame] = useState(0);\n\n\tuseEffect(() => {\n\t\tif (!active || reducedMotion) return;\n\n\t\tconst timer = setInterval(() => setFrame((current) => (current + 1) % FRAMES.length), 80);\n\t\treturn () => clearInterval(timer);\n\t}, [active, reducedMotion]);\n\n\tif (!active) return ' ';\n\treturn reducedMotion ? STILL : FRAMES[frame % FRAMES.length]!;\n}\n","import { useEffect, useState } from 'react';\nimport type { DevService } from '../../../lib/dev-service.js';\nimport type { LogEntry } from '../../../lib/log-buffer.js';\n\n/**\n * The append-only stream `<Static>` prints once and never revisits, so a session's output becomes real terminal\n * scrollback exactly like plain mode's — the panel below stays a handful of lines, the way Nuxt CLI v4's own dev\n * server never mixes its log stream into the panel it pins.\n */\nexport function useStaticLog(service: DevService): LogEntry[] {\n\tconst [entries, setEntries] = useState<LogEntry[]>(() => service.logs.entries().slice());\n\n\tuseEffect(() => {\n\t\tconst onEntry = (entry: LogEntry) => setEntries((current) => [...current, entry]);\n\t\tservice.logs.on('entry', onEntry);\n\t\treturn () => {\n\t\t\tservice.logs.off('entry', onEntry);\n\t\t};\n\t}, [service]);\n\n\treturn entries;\n}\n","import { useEffect, useState } from 'react';\n\n/**\n * Re-renders once a second while the bot is up, so the uptime in the header keeps counting.\n */\nexport function useUptime(startedAt: number | null): number | null {\n\tconst [now, setNow] = useState(() => Date.now());\n\n\tuseEffect(() => {\n\t\tif (startedAt === null) return;\n\n\t\tsetNow(Date.now());\n\t\tconst timer = setInterval(() => setNow(Date.now()), 1000);\n\t\treturn () => clearInterval(timer);\n\t}, [startedAt]);\n\n\treturn startedAt === null ? null : now - startedAt;\n}\n","import { Box, Static, useApp, useInput, useWindowSize } from 'ink';\nimport { useState } from 'react';\nimport type { DevService } from '../../lib/dev-service.js';\nimport { HelpOverlay } from './components/HelpOverlay.js';\nimport { LogBrowser } from './components/LogBrowser.js';\nimport { LogLine } from './components/LogLine.js';\nimport { Panel } from './components/Panel.js';\nimport { useDevStatus } from './hooks/useDevStatus.js';\nimport { useLogCounters } from './hooks/useLogCounters.js';\nimport { useSpinner } from './hooks/useSpinner.js';\nimport { useStaticLog } from './hooks/useStaticLog.js';\nimport { useUptime } from './hooks/useUptime.js';\nimport { ColorProvider } from './theme.js';\n\nexport interface DevAppProps {\n\tservice: DevService;\n\tcolor: boolean;\n\treducedMotion: boolean;\n\t/** Called when the user asks to quit, so the CLI can stop the bot and exit. */\n\tonQuit: () => void;\n}\n\ntype View = 'panel' | 'logs' | 'help';\n\n/**\n * The interactive `stars dev` UI, in the style of Nuxt CLI v4's own dev server: log output streams into the\n * terminal's real scrollback through `<Static>` (never re-rendered, never reformatted), and a compact panel below\n * it — a handful of lines, redrawn as state changes — carries the status Nuxt's own summary/hints blocks show.\n * `l` opens the full, filterable log history and `?` the key reference, both replacing the panel until closed.\n */\nexport function DevApp({ service, color, reducedMotion, onQuit }: DevAppProps) {\n\tconst { exit } = useApp();\n\tconst { columns, rows } = useWindowSize();\n\tconst [view, setView] = useState<View>('panel');\n\n\tconst status = useDevStatus(service);\n\tconst uptime = useUptime(status.startedAt);\n\tconst counters = useLogCounters(service);\n\tconst entries = useStaticLog(service);\n\tconst spinning = status.build === 'building' || status.process === 'starting' || status.tunnel === 'starting' || status.typecheck === 'checking';\n\tconst spinner = useSpinner(spinning, reducedMotion);\n\n\tconst width = Math.max(20, columns);\n\tconst height = Math.max(6, rows);\n\tconst narrow = width < 60;\n\n\tconst quit = () => {\n\t\tonQuit();\n\t\texit();\n\t};\n\n\tuseInput((input, key) => {\n\t\tif (view !== 'panel') return;\n\t\tif (key.ctrl && input === 'c') return quit();\n\n\t\tswitch (input) {\n\t\t\tcase 'q':\n\t\t\t\treturn quit();\n\t\t\tcase 'r':\n\t\t\t\treturn void service.restart('manual');\n\t\t\tcase 'l':\n\t\t\t\treturn setView('logs');\n\t\t\tcase '?':\n\t\t\t\treturn setView('help');\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t});\n\n\treturn (\n\t\t<ColorProvider color={color}>\n\t\t\t<Static items={entries}>{(entry) => <LogLine key={entry.id} entry={entry} />}</Static>\n\t\t\t<Box width={width} flexDirection=\"column\">\n\t\t\t\t{view === 'logs' && <LogBrowser service={service} height={height} onClose={() => setView('panel')} />}\n\t\t\t\t{view === 'help' && <HelpOverlay height={height} onClose={() => setView('panel')} />}\n\t\t\t\t{view === 'panel' && (\n\t\t\t\t\t<Panel\n\t\t\t\t\t\tname={service.config.packageJson?.name ?? 'stars project'}\n\t\t\t\t\t\tstatus={status}\n\t\t\t\t\t\tconfig={service.config}\n\t\t\t\t\t\tuptimeMs={uptime}\n\t\t\t\t\t\tcounters={counters}\n\t\t\t\t\t\tspinner={spinner}\n\t\t\t\t\t\tnarrow={narrow}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t</Box>\n\t\t</ColorProvider>\n\t);\n}\n","import { render } from 'ink';\nimport type { DevService } from '../lib/dev-service.js';\nimport { DevApp } from './ink/DevApp.js';\nimport type { Renderer } from './plain.js';\n\nexport interface TuiRendererOptions {\n\tstdout?: NodeJS.WriteStream;\n\tstdin?: NodeJS.ReadStream;\n\tcolor?: boolean;\n\t/** Freezes spinners, for `STARS_REDUCED_MOTION=1`. */\n\treducedMotion?: boolean;\n\t/** Caps how often Ink repaints; the default is Ink's own. */\n\tfps?: number;\n}\n\n/**\n * The interactive renderer: an Ink (React) application whose log stream commits straight to the terminal's normal\n * scrollback (see `DevApp`'s use of `<Static>`), with a small pinned panel below it. Unlike a full-screen app, it\n * never touches the alternate screen buffer, so the session's output survives in the user's own terminal history.\n *\n * `start()` resolves when the user quits, which is what `stars dev` waits on before shutting the bot down.\n */\nexport function createTuiRenderer(service: DevService, options: TuiRendererOptions = {}): Renderer {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst stdin = options.stdin ?? process.stdin;\n\n\tlet quit!: () => void;\n\tconst quitting = new Promise<void>((resolve) => {\n\t\tquit = resolve;\n\t});\n\n\tconst instance = render(\n\t\t<DevApp service={service} color={options.color ?? false} reducedMotion={options.reducedMotion ?? false} onQuit={quit} />,\n\t\t{\n\t\t\tstdout,\n\t\t\tstdin,\n\t\t\t// `stars dev` owns the shutdown: it stops the bot, then exits with the right code.\n\t\t\texitOnCtrlC: false,\n\t\t\tpatchConsole: false,\n\t\t\t// `createTuiRenderer` only runs once `resolveOutputMode()` already picked 'tui' (never in CI or on a\n\t\t\t// non-TTY stdout), so Ink is told to trust that instead of re-deriving it from `process.env.CI`: on a\n\t\t\t// CI runner that env var is set even for this package's own test process, which would otherwise force\n\t\t\t// Ink's non-interactive mode (no repaint until unmount) despite the fake stdin/stdout looking like a\n\t\t\t// real TTY.\n\t\t\tinteractive: true,\n\t\t\t...(options.fps === undefined ? {} : { maxFps: options.fps })\n\t\t}\n\t);\n\n\treturn {\n\t\tasync start() {\n\t\t\tawait Promise.race([quitting, instance.waitUntilExit()]);\n\t\t},\n\t\tstop() {\n\t\t\tinstance.unmount();\n\t\t}\n\t};\n}\n\nexport { formatDuration } from './panel-logic.js';\n"],"mappings":"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"}
@@ -0,0 +1,205 @@
1
+ import { n as createLineSplitter } from "./process-supervisor-CJOdmp0W.js";
2
+ import { readProjectEnvFiles } from "@wolfstar/http-framework/config";
3
+ import { EventEmitter } from "node:events";
4
+ import { spawn } from "node:child_process";
5
+
6
+ //#region src/lib/tunnel.ts
7
+ const QUICK_TUNNEL_URL = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
8
+ const INSTALL_HINT = "Install cloudflared (https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/), point `dev.tunnel` at an https URL you already serve, or set it to false.";
9
+ /**
10
+ * Exposes the bot's interactions endpoint publicly while `stars dev` runs.
11
+ *
12
+ * `dev.tunnel: true` spawns a `cloudflared` quick tunnel and reads its hostname off the process output (it changes
13
+ * on every run); a configured https URL is only probed, since the user already serves it. Writing the URL to the
14
+ * Discord application is opt-in through `dev.tunnel.updateEndpoint`, because it edits a live application.
15
+ */
16
+ var Tunnel = class extends EventEmitter {
17
+ config;
18
+ #child = null;
19
+ #state = "off";
20
+ #url = null;
21
+ constructor(config) {
22
+ super();
23
+ this.config = config;
24
+ }
25
+ get state() {
26
+ return this.#state;
27
+ }
28
+ /** The public URL of the interactions endpoint, `null` until the tunnel is up. */
29
+ get url() {
30
+ return this.#url;
31
+ }
32
+ async start() {
33
+ const tunnel = this.config.dev.tunnel;
34
+ if (tunnel.mode === "off") return;
35
+ this.#setState("starting", null);
36
+ const url = tunnel.mode === "url" ? await this.#useConfiguredUrl(tunnel) : await this.#openQuickTunnel();
37
+ if (!url) return;
38
+ this.#setState("up", endpointUrl(url, tunnel.path));
39
+ this.emit("log", "success", `Tunnel ready at ${this.#url}`);
40
+ if (tunnel.updateEndpoint) await this.#updateInteractionsEndpoint(this.#url);
41
+ }
42
+ close() {
43
+ const child = this.#child;
44
+ this.#child = null;
45
+ this.#setState("off", null);
46
+ if (!child || child.exitCode !== null) return Promise.resolve();
47
+ return new Promise((resolve) => {
48
+ child.once("exit", () => resolve());
49
+ child.kill();
50
+ });
51
+ }
52
+ async #useConfiguredUrl(tunnel) {
53
+ if (!await probe(endpointUrl(tunnel.url, tunnel.path))) this.emit("log", "warn", `${tunnel.url} did not answer; make sure it forwards to ${this.config.dev.url ?? "the bot"}`);
54
+ return tunnel.url;
55
+ }
56
+ async #openQuickTunnel() {
57
+ const target = this.config.dev.url;
58
+ if (!target) {
59
+ this.emit("log", "error", "A quick tunnel needs `dev.url` to know what to forward to");
60
+ this.#setState("failed", null);
61
+ return null;
62
+ }
63
+ this.emit("log", "info", "Opening a cloudflared quick tunnel…");
64
+ return new Promise((resolve) => {
65
+ let child;
66
+ try {
67
+ child = spawn("cloudflared", [
68
+ "tunnel",
69
+ "--url",
70
+ target,
71
+ "--no-autoupdate"
72
+ ], {
73
+ cwd: this.config.root,
74
+ stdio: [
75
+ "ignore",
76
+ "pipe",
77
+ "pipe"
78
+ ],
79
+ windowsHide: true
80
+ });
81
+ } catch {
82
+ this.#failWithMissingBinary();
83
+ resolve(null);
84
+ return;
85
+ }
86
+ this.#child = child;
87
+ let settled = false;
88
+ const settle = (url) => {
89
+ if (settled) return;
90
+ settled = true;
91
+ resolve(url);
92
+ };
93
+ const handleLine = (line) => {
94
+ const match = QUICK_TUNNEL_URL.exec(line);
95
+ if (match) settle(match[0]);
96
+ else if (line.trim().length > 0) this.emit("log", "debug", line.trim());
97
+ };
98
+ const stdout = createLineSplitter(handleLine);
99
+ const stderr = createLineSplitter(handleLine);
100
+ child.stdout?.on("data", stdout.push);
101
+ child.stderr?.on("data", stderr.push);
102
+ child.once("error", (error) => {
103
+ if (error.code === "ENOENT") this.#failWithMissingBinary();
104
+ else {
105
+ this.emit("log", "error", `cloudflared failed: ${error.message}`);
106
+ this.#setState("failed", null);
107
+ }
108
+ settle(null);
109
+ });
110
+ child.once("exit", (code) => {
111
+ stdout.flush();
112
+ stderr.flush();
113
+ this.#child = null;
114
+ if (!settled) {
115
+ this.emit("log", "error", `cloudflared exited with code ${code} before the tunnel was up`);
116
+ this.#setState("failed", null);
117
+ settle(null);
118
+ }
119
+ });
120
+ });
121
+ }
122
+ #failWithMissingBinary() {
123
+ this.emit("log", "error", `cloudflared is not installed. ${INSTALL_HINT}`);
124
+ this.#setState("failed", null);
125
+ }
126
+ /**
127
+ * Points the Discord application's interactions endpoint at the tunnel. Discord validates the endpoint with a
128
+ * signed ping before accepting it, so the bot has to be up already — this runs after the first successful build.
129
+ */
130
+ async #updateInteractionsEndpoint(url) {
131
+ const credentials = readDiscordCredentials(this.config);
132
+ if (!credentials) {
133
+ this.emit("log", "warn", "Skipped the interactions endpoint update: DISCORD_TOKEN is not set");
134
+ return;
135
+ }
136
+ try {
137
+ const response = await fetch("https://discord.com/api/v10/applications/@me", {
138
+ method: "PATCH",
139
+ headers: {
140
+ authorization: `Bot ${credentials.token}`,
141
+ "content-type": "application/json"
142
+ },
143
+ body: JSON.stringify({ interactions_endpoint_url: url }),
144
+ signal: AbortSignal.timeout(1e4)
145
+ });
146
+ if (response.ok) this.emit("log", "success", `Interactions endpoint set to ${url}`);
147
+ else this.emit("log", "error", `Discord rejected the interactions endpoint (${response.status}): ${(await response.text()).slice(0, 300)}`);
148
+ } catch (error) {
149
+ this.emit("log", "error", `Failed to update the interactions endpoint: ${error instanceof Error ? error.message : String(error)}`);
150
+ }
151
+ }
152
+ #setState(state, url) {
153
+ this.#state = state;
154
+ this.#url = url;
155
+ this.emit("state", state, url);
156
+ }
157
+ };
158
+ /**
159
+ * Joins the tunnel's origin with the path the interactions endpoint is served on.
160
+ */
161
+ function endpointUrl(origin, path) {
162
+ return path === "/" ? origin : new URL(path, origin.endsWith("/") ? origin : `${origin}/`).href;
163
+ }
164
+ async function probe(url) {
165
+ try {
166
+ return (await fetch(url, {
167
+ method: "HEAD",
168
+ signal: AbortSignal.timeout(5e3)
169
+ })).status < 500;
170
+ } catch {
171
+ return false;
172
+ }
173
+ }
174
+ /**
175
+ * Reads the Discord credentials the CLI needs from the environment, falling back to the project's `.env` files the
176
+ * way the bot itself does once it starts.
177
+ */
178
+ function readDiscordCredentials(config, env = process.env) {
179
+ const fromFiles = readProjectEnvFilesCached(config.root);
180
+ const read = (...keys) => {
181
+ for (const key of keys) {
182
+ const value = config.dev.env[key] ?? env[key] ?? fromFiles[key];
183
+ if (value) return value;
184
+ }
185
+ return null;
186
+ };
187
+ const token = read("DISCORD_TOKEN", "TOKEN");
188
+ if (!token) return null;
189
+ return {
190
+ token,
191
+ applicationId: read("DISCORD_APPLICATION_ID", "APPLICATION_ID", "DISCORD_CLIENT_ID", "CLIENT_ID")
192
+ };
193
+ }
194
+ let envFileCache = null;
195
+ function readProjectEnvFilesCached(root) {
196
+ if (envFileCache?.root !== root) envFileCache = {
197
+ root,
198
+ values: readProjectEnvFiles(root)
199
+ };
200
+ return envFileCache.values;
201
+ }
202
+
203
+ //#endregion
204
+ export { readDiscordCredentials as n, Tunnel as t };
205
+ //# sourceMappingURL=tunnel-B8Id4-lR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-B8Id4-lR.js","names":["#state","#url","#setState","#useConfiguredUrl","#openQuickTunnel","#updateInteractionsEndpoint","#child","#failWithMissingBinary"],"sources":["../src/lib/tunnel.ts"],"sourcesContent":["import { readProjectEnvFiles, type ResolvedStarsConfig, type ResolvedTunnelConfig } from '@wolfstar/http-framework/config';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { EventEmitter } from 'node:events';\nimport type { LogLevel } from './log-buffer.js';\nimport { createLineSplitter } from './process-supervisor.js';\n\nconst QUICK_TUNNEL_URL = /https:\\/\\/[a-z0-9-]+\\.trycloudflare\\.com/i;\nconst INSTALL_HINT =\n\t'Install cloudflared (https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/), point `dev.tunnel` at an https URL you already serve, or set it to false.';\n\nexport type TunnelState = 'off' | 'starting' | 'up' | 'failed';\n\nexport interface TunnelEvents {\n\tlog: [level: LogLevel, text: string];\n\tstate: [state: TunnelState, url: string | null];\n}\n\n/**\n * Exposes the bot's interactions endpoint publicly while `stars dev` runs.\n *\n * `dev.tunnel: true` spawns a `cloudflared` quick tunnel and reads its hostname off the process output (it changes\n * on every run); a configured https URL is only probed, since the user already serves it. Writing the URL to the\n * Discord application is opt-in through `dev.tunnel.updateEndpoint`, because it edits a live application.\n */\nexport class Tunnel extends EventEmitter<TunnelEvents> {\n\t#child: ChildProcess | null = null;\n\t#state: TunnelState = 'off';\n\t#url: string | null = null;\n\n\tpublic constructor(private readonly config: ResolvedStarsConfig) {\n\t\tsuper();\n\t}\n\n\tpublic get state(): TunnelState {\n\t\treturn this.#state;\n\t}\n\n\t/** The public URL of the interactions endpoint, `null` until the tunnel is up. */\n\tpublic get url(): string | null {\n\t\treturn this.#url;\n\t}\n\n\tpublic async start(): Promise<void> {\n\t\tconst tunnel = this.config.dev.tunnel;\n\t\tif (tunnel.mode === 'off') return;\n\n\t\tthis.#setState('starting', null);\n\t\tconst url = tunnel.mode === 'url' ? await this.#useConfiguredUrl(tunnel) : await this.#openQuickTunnel();\n\t\tif (!url) return;\n\n\t\tthis.#setState('up', endpointUrl(url, tunnel.path));\n\t\tthis.emit('log', 'success', `Tunnel ready at ${this.#url}`);\n\t\tif (tunnel.updateEndpoint) await this.#updateInteractionsEndpoint(this.#url!);\n\t}\n\n\tpublic close(): Promise<void> {\n\t\tconst child = this.#child;\n\t\tthis.#child = null;\n\t\tthis.#setState('off', null);\n\t\tif (!child || child.exitCode !== null) return Promise.resolve();\n\n\t\treturn new Promise((resolve) => {\n\t\t\tchild.once('exit', () => resolve());\n\t\t\tchild.kill();\n\t\t});\n\t}\n\n\tasync #useConfiguredUrl(tunnel: Extract<ResolvedTunnelConfig, { mode: 'url' }>): Promise<string | null> {\n\t\tconst reachable = await probe(endpointUrl(tunnel.url, tunnel.path));\n\t\tif (!reachable) {\n\t\t\tthis.emit('log', 'warn', `${tunnel.url} did not answer; make sure it forwards to ${this.config.dev.url ?? 'the bot'}`);\n\t\t}\n\n\t\treturn tunnel.url;\n\t}\n\n\tasync #openQuickTunnel(): Promise<string | null> {\n\t\tconst target = this.config.dev.url;\n\t\tif (!target) {\n\t\t\tthis.emit('log', 'error', 'A quick tunnel needs `dev.url` to know what to forward to');\n\t\t\tthis.#setState('failed', null);\n\t\t\treturn null;\n\t\t}\n\n\t\tthis.emit('log', 'info', 'Opening a cloudflared quick tunnel…');\n\n\t\treturn new Promise<string | null>((resolve) => {\n\t\t\tlet child: ChildProcess;\n\t\t\ttry {\n\t\t\t\tchild = spawn('cloudflared', ['tunnel', '--url', target, '--no-autoupdate'], {\n\t\t\t\t\tcwd: this.config.root,\n\t\t\t\t\tstdio: ['ignore', 'pipe', 'pipe'],\n\t\t\t\t\twindowsHide: true\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\tthis.#failWithMissingBinary();\n\t\t\t\tresolve(null);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.#child = child;\n\t\t\tlet settled = false;\n\t\t\tconst settle = (url: string | null) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tresolve(url);\n\t\t\t};\n\n\t\t\tconst handleLine = (line: string) => {\n\t\t\t\tconst match = QUICK_TUNNEL_URL.exec(line);\n\t\t\t\tif (match) settle(match[0]);\n\t\t\t\telse if (line.trim().length > 0) this.emit('log', 'debug', line.trim());\n\t\t\t};\n\n\t\t\tconst stdout = createLineSplitter(handleLine);\n\t\t\tconst stderr = createLineSplitter(handleLine);\n\t\t\tchild.stdout?.on('data', stdout.push);\n\t\t\tchild.stderr?.on('data', stderr.push);\n\n\t\t\tchild.once('error', (error: NodeJS.ErrnoException) => {\n\t\t\t\tif (error.code === 'ENOENT') this.#failWithMissingBinary();\n\t\t\t\telse {\n\t\t\t\t\tthis.emit('log', 'error', `cloudflared failed: ${error.message}`);\n\t\t\t\t\tthis.#setState('failed', null);\n\t\t\t\t}\n\t\t\t\tsettle(null);\n\t\t\t});\n\n\t\t\tchild.once('exit', (code) => {\n\t\t\t\tstdout.flush();\n\t\t\t\tstderr.flush();\n\t\t\t\tthis.#child = null;\n\t\t\t\tif (!settled) {\n\t\t\t\t\tthis.emit('log', 'error', `cloudflared exited with code ${code} before the tunnel was up`);\n\t\t\t\t\tthis.#setState('failed', null);\n\t\t\t\t\tsettle(null);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t#failWithMissingBinary(): void {\n\t\tthis.emit('log', 'error', `cloudflared is not installed. ${INSTALL_HINT}`);\n\t\tthis.#setState('failed', null);\n\t}\n\n\t/**\n\t * Points the Discord application's interactions endpoint at the tunnel. Discord validates the endpoint with a\n\t * signed ping before accepting it, so the bot has to be up already — this runs after the first successful build.\n\t */\n\tasync #updateInteractionsEndpoint(url: string): Promise<void> {\n\t\tconst credentials = readDiscordCredentials(this.config);\n\t\tif (!credentials) {\n\t\t\tthis.emit('log', 'warn', 'Skipped the interactions endpoint update: DISCORD_TOKEN is not set');\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await fetch('https://discord.com/api/v10/applications/@me', {\n\t\t\t\tmethod: 'PATCH',\n\t\t\t\theaders: { authorization: `Bot ${credentials.token}`, 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ interactions_endpoint_url: url }),\n\t\t\t\tsignal: AbortSignal.timeout(10_000)\n\t\t\t});\n\n\t\t\tif (response.ok) this.emit('log', 'success', `Interactions endpoint set to ${url}`);\n\t\t\telse\n\t\t\t\tthis.emit(\n\t\t\t\t\t'log',\n\t\t\t\t\t'error',\n\t\t\t\t\t`Discord rejected the interactions endpoint (${response.status}): ${(await response.text()).slice(0, 300)}`\n\t\t\t\t);\n\t\t} catch (error) {\n\t\t\tthis.emit('log', 'error', `Failed to update the interactions endpoint: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\n\t#setState(state: TunnelState, url: string | null): void {\n\t\tthis.#state = state;\n\t\tthis.#url = url;\n\t\tthis.emit('state', state, url);\n\t}\n}\n\n/**\n * Joins the tunnel's origin with the path the interactions endpoint is served on.\n */\nexport function endpointUrl(origin: string, path: string): string {\n\treturn path === '/' ? origin : new URL(path, origin.endsWith('/') ? origin : `${origin}/`).href;\n}\n\nasync function probe(url: string): Promise<boolean> {\n\ttry {\n\t\tconst response = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(5000) });\n\t\treturn response.status < 500;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport interface DiscordCredentials {\n\ttoken: string;\n\tapplicationId: string | null;\n}\n\n/**\n * Reads the Discord credentials the CLI needs from the environment, falling back to the project's `.env` files the\n * way the bot itself does once it starts.\n */\nexport function readDiscordCredentials(config: ResolvedStarsConfig, env: NodeJS.ProcessEnv = process.env): DiscordCredentials | null {\n\tconst fromFiles = readProjectEnvFilesCached(config.root);\n\tconst read = (...keys: string[]): string | null => {\n\t\tfor (const key of keys) {\n\t\t\tconst value = config.dev.env[key] ?? env[key] ?? fromFiles[key];\n\t\t\tif (value) return value;\n\t\t}\n\t\treturn null;\n\t};\n\n\tconst token = read('DISCORD_TOKEN', 'TOKEN');\n\tif (!token) return null;\n\n\treturn { token, applicationId: read('DISCORD_APPLICATION_ID', 'APPLICATION_ID', 'DISCORD_CLIENT_ID', 'CLIENT_ID') };\n}\n\nlet envFileCache: { root: string; values: Record<string, string> } | null = null;\n\nfunction readProjectEnvFilesCached(root: string): Record<string, string> {\n\tif (envFileCache?.root !== root) envFileCache = { root, values: readProjectEnvFiles(root) };\n\treturn envFileCache.values;\n}\n"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ //#region src/lib/version.ts
6
+ let cached = null;
7
+ /**
8
+ * Reads this package's own `package.json`, from `src/` and from the bundled `dist/` chunks alike.
9
+ */
10
+ function readOwnPackageJson() {
11
+ if (cached) return cached;
12
+ let directory = dirname(fileURLToPath(import.meta.url));
13
+ for (;;) {
14
+ const file = join(directory, "package.json");
15
+ if (existsSync(file)) {
16
+ const parsed = JSON.parse(readFileSync(file, "utf-8"));
17
+ if (parsed.name === "@wolfstar/cli") {
18
+ cached = parsed;
19
+ return parsed;
20
+ }
21
+ }
22
+ const parent = dirname(directory);
23
+ if (parent === directory) throw new Error("Could not locate the package.json of @wolfstar/cli");
24
+ directory = parent;
25
+ }
26
+ }
27
+
28
+ //#endregion
29
+ export { readOwnPackageJson as t };
30
+ //# sourceMappingURL=version-DN6Uho-Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-DN6Uho-Y.js","names":[],"sources":["../src/lib/version.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport interface OwnPackageJson {\n\tname: string;\n\tversion: string;\n\tdescription: string;\n}\n\nlet cached: OwnPackageJson | null = null;\n\n/**\n * Reads this package's own `package.json`, from `src/` and from the bundled `dist/` chunks alike.\n */\nexport function readOwnPackageJson(): OwnPackageJson {\n\tif (cached) return cached;\n\n\tlet directory = dirname(fileURLToPath(import.meta.url));\n\tfor (;;) {\n\t\tconst file = join(directory, 'package.json');\n\t\tif (existsSync(file)) {\n\t\t\tconst parsed = JSON.parse(readFileSync(file, 'utf-8')) as OwnPackageJson;\n\t\t\tif (parsed.name === '@wolfstar/cli') {\n\t\t\t\tcached = parsed;\n\t\t\t\treturn parsed;\n\t\t\t}\n\t\t}\n\n\t\tconst parent = dirname(directory);\n\t\tif (parent === directory) throw new Error('Could not locate the package.json of @wolfstar/cli');\n\t\tdirectory = parent;\n\t}\n}\n"],"mappings":";;;;;AAUA,IAAI,SAAgC;;;;AAKpC,SAAgB,qBAAqC;CACpD,IAAI,QAAQ,OAAO;CAEnB,IAAI,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;CACtD,SAAS;EACR,MAAM,OAAO,KAAK,WAAW,cAAc;EAC3C,IAAI,WAAW,IAAI,GAAG;GACrB,MAAM,SAAS,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;GACrD,IAAI,OAAO,SAAS,iBAAiB;IACpC,SAAS;IACT,OAAO;GACR;EACD;EAEA,MAAM,SAAS,QAAQ,SAAS;EAChC,IAAI,WAAW,WAAW,MAAM,IAAI,MAAM,oDAAoD;EAC9F,YAAY;CACb;AACD"}
@@ -0,0 +1,93 @@
1
+ import { n as importFromProject } from "./project-DHF3qdm-.js";
2
+ import { EventEmitter } from "node:events";
3
+
4
+ //#region src/lib/builders/vite.ts
5
+ const INSTALL_HINT = "Install it with `pnpm add -D vite`, or set `build.tool` to 'tsdown', 'tsc' or 'none'.";
6
+ /**
7
+ * Builds through the project's own `vite`, honouring its `vite.config.*`.
8
+ *
9
+ * Guarded by `experimental.enableVite`: Vite replaces `tsdown` as the bundler and, with the framework's Fetch
10
+ * adapter, the `node:http` listener too, so a project can serve its interactions endpoint the same way it serves
11
+ * everything else.
12
+ */
13
+ var ViteBuilder = class extends EventEmitter {
14
+ config;
15
+ tool = "vite";
16
+ #watcher = null;
17
+ #startedAt = 0;
18
+ constructor(config) {
19
+ super();
20
+ this.config = config;
21
+ }
22
+ async build() {
23
+ const vite = await this.#load();
24
+ this.#begin();
25
+ try {
26
+ await vite.build({
27
+ root: this.config.root,
28
+ logLevel: "warn",
29
+ customLogger: this.#logger(vite)
30
+ });
31
+ return this.#finish(null);
32
+ } catch (error) {
33
+ return this.#finish(error instanceof Error ? error.message : String(error));
34
+ }
35
+ }
36
+ async watch() {
37
+ const vite = await this.#load();
38
+ this.#begin();
39
+ const result = await vite.build({
40
+ root: this.config.root,
41
+ logLevel: "warn",
42
+ customLogger: this.#logger(vite),
43
+ build: { watch: {} }
44
+ });
45
+ this.#watcher = result;
46
+ result.on("event", ((event) => {
47
+ switch (event.code) {
48
+ case "BUNDLE_START": return this.#begin();
49
+ case "BUNDLE_END":
50
+ this.#finish(null);
51
+ return;
52
+ case "ERROR":
53
+ this.#finish(event.error?.message ?? "The build reported errors");
54
+ return;
55
+ }
56
+ }));
57
+ }
58
+ async close() {
59
+ const watcher = this.#watcher;
60
+ this.#watcher = null;
61
+ await watcher?.close();
62
+ }
63
+ async #load() {
64
+ return importFromProject(this.config.root, "vite", INSTALL_HINT);
65
+ }
66
+ #logger(vite) {
67
+ return {
68
+ ...vite.createLogger("warn", { allowClearScreen: false }),
69
+ info: (message) => this.emit("log", "info", message),
70
+ warn: (message) => this.emit("log", "warn", message),
71
+ warnOnce: (message) => this.emit("log", "warn", message),
72
+ error: (message) => this.emit("log", "error", message)
73
+ };
74
+ }
75
+ #begin() {
76
+ this.#startedAt = performance.now();
77
+ this.emit("start");
78
+ }
79
+ #finish(message) {
80
+ const outcome = {
81
+ ok: message === null,
82
+ durationMs: Math.round(performance.now() - this.#startedAt),
83
+ message
84
+ };
85
+ this.#startedAt = 0;
86
+ this.emit(outcome.ok ? "success" : "failure", outcome);
87
+ return outcome;
88
+ }
89
+ };
90
+
91
+ //#endregion
92
+ export { ViteBuilder };
93
+ //# sourceMappingURL=vite-D3vYFsqa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-D3vYFsqa.js","names":["#load","#begin","#logger","#finish","#watcher","#startedAt"],"sources":["../src/lib/builders/vite.ts"],"sourcesContent":["import type { ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport { EventEmitter } from 'node:events';\nimport { importFromProject } from '../project.js';\nimport type { Builder, BuilderEvents, BuildOutcome } from './types.js';\n\ntype ViteModule = typeof import('vite');\ntype RollupWatcher =\n\tAwaited<ReturnType<ViteModule['build']>> extends infer Result\n\t\t? Result extends { close: () => Promise<void>; on: (...args: never[]) => unknown }\n\t\t\t? Result\n\t\t\t: never\n\t\t: never;\n\nconst INSTALL_HINT = \"Install it with `pnpm add -D vite`, or set `build.tool` to 'tsdown', 'tsc' or 'none'.\";\n\n/**\n * Builds through the project's own `vite`, honouring its `vite.config.*`.\n *\n * Guarded by `experimental.enableVite`: Vite replaces `tsdown` as the bundler and, with the framework's Fetch\n * adapter, the `node:http` listener too, so a project can serve its interactions endpoint the same way it serves\n * everything else.\n */\nexport class ViteBuilder extends EventEmitter<BuilderEvents> implements Builder {\n\tpublic readonly tool = 'vite' as const;\n\t#watcher: RollupWatcher | null = null;\n\t#startedAt = 0;\n\n\tpublic constructor(private readonly config: ResolvedStarsConfig) {\n\t\tsuper();\n\t}\n\n\tpublic async build(): Promise<BuildOutcome> {\n\t\tconst vite = await this.#load();\n\t\tthis.#begin();\n\n\t\ttry {\n\t\t\tawait vite.build({ root: this.config.root, logLevel: 'warn', customLogger: this.#logger(vite) });\n\t\t\treturn this.#finish(null);\n\t\t} catch (error) {\n\t\t\treturn this.#finish(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tpublic async watch(): Promise<void> {\n\t\tconst vite = await this.#load();\n\t\tthis.#begin();\n\n\t\t// `build.watch` makes Vite return a rollup watcher instead of resolving once, which is what keeps the bot\n\t\t// rebuilding on change; every rebuild reports through the same events the other builders use.\n\t\tconst result = (await vite.build({\n\t\t\troot: this.config.root,\n\t\t\tlogLevel: 'warn',\n\t\t\tcustomLogger: this.#logger(vite),\n\t\t\tbuild: { watch: {} }\n\t\t})) as unknown as RollupWatcher;\n\n\t\tthis.#watcher = result;\n\t\tresult.on(\n\t\t\t'event' as never,\n\t\t\t((event: { code: string; error?: Error }) => {\n\t\t\t\tswitch (event.code) {\n\t\t\t\t\tcase 'BUNDLE_START':\n\t\t\t\t\t\treturn this.#begin();\n\t\t\t\t\tcase 'BUNDLE_END':\n\t\t\t\t\t\treturn void this.#finish(null);\n\t\t\t\t\tcase 'ERROR':\n\t\t\t\t\t\treturn void this.#finish(event.error?.message ?? 'The build reported errors');\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}) as never\n\t\t);\n\t}\n\n\tpublic async close(): Promise<void> {\n\t\tconst watcher = this.#watcher;\n\t\tthis.#watcher = null;\n\t\tawait watcher?.close();\n\t}\n\n\tasync #load(): Promise<ViteModule> {\n\t\treturn importFromProject<ViteModule>(this.config.root, 'vite', INSTALL_HINT);\n\t}\n\n\t#logger(vite: ViteModule) {\n\t\tconst logger = vite.createLogger('warn', { allowClearScreen: false });\n\t\treturn {\n\t\t\t...logger,\n\t\t\tinfo: (message: string) => this.emit('log', 'info', message),\n\t\t\twarn: (message: string) => this.emit('log', 'warn', message),\n\t\t\twarnOnce: (message: string) => this.emit('log', 'warn', message),\n\t\t\terror: (message: string) => this.emit('log', 'error', message)\n\t\t};\n\t}\n\n\t#begin(): void {\n\t\tthis.#startedAt = performance.now();\n\t\tthis.emit('start');\n\t}\n\n\t#finish(message: string | null): BuildOutcome {\n\t\tconst outcome: BuildOutcome = { ok: message === null, durationMs: Math.round(performance.now() - this.#startedAt), message };\n\t\tthis.#startedAt = 0;\n\t\tthis.emit(outcome.ok ? 'success' : 'failure', outcome);\n\t\treturn outcome;\n\t}\n}\n"],"mappings":";;;;AAaA,MAAM,eAAe;;;;;;;;AASrB,IAAa,cAAb,cAAiC,aAA+C;CAK3C;CAJpC,AAAgB,OAAO;CACvB,WAAiC;CACjC,aAAa;CAEb,AAAO,YAAY,AAAiB,QAA6B;EAChE,MAAM;EAD6B;CAEpC;CAEA,MAAa,QAA+B;EAC3C,MAAM,OAAO,MAAM,KAAKA,MAAM;EAC9B,KAAKC,OAAO;EAEZ,IAAI;GACH,MAAM,KAAK,MAAM;IAAE,MAAM,KAAK,OAAO;IAAM,UAAU;IAAQ,cAAc,KAAKC,QAAQ,IAAI;GAAE,CAAC;GAC/F,OAAO,KAAKC,QAAQ,IAAI;EACzB,SAAS,OAAO;GACf,OAAO,KAAKA,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;EAC3E;CACD;CAEA,MAAa,QAAuB;EACnC,MAAM,OAAO,MAAM,KAAKH,MAAM;EAC9B,KAAKC,OAAO;EAIZ,MAAM,SAAU,MAAM,KAAK,MAAM;GAChC,MAAM,KAAK,OAAO;GAClB,UAAU;GACV,cAAc,KAAKC,QAAQ,IAAI;GAC/B,OAAO,EAAE,OAAO,CAAC,EAAE;EACpB,CAAC;EAED,KAAKE,WAAW;EAChB,OAAO,GACN,WACE,UAA2C;GAC5C,QAAQ,MAAM,MAAd;IACC,KAAK,gBACJ,OAAO,KAAKH,OAAO;IACpB,KAAK;KACG,AAAK,KAAKE,QAAQ,IAAI;KAA7B;IACD,KAAK;KACG,AAAK,KAAKA,QAAQ,MAAM,OAAO,WAAW,2BAA2B;KAA5E;GAGF;EACD,EACD;CACD;CAEA,MAAa,QAAuB;EACnC,MAAM,UAAU,KAAKC;EACrB,KAAKA,WAAW;EAChB,MAAM,SAAS,MAAM;CACtB;CAEA,MAAMJ,QAA6B;EAClC,OAAO,kBAA8B,KAAK,OAAO,MAAM,QAAQ,YAAY;CAC5E;CAEA,QAAQ,MAAkB;EAEzB,OAAO;GACN,GAFc,KAAK,aAAa,QAAQ,EAAE,kBAAkB,MAAM,CAE1D;GACR,OAAO,YAAoB,KAAK,KAAK,OAAO,QAAQ,OAAO;GAC3D,OAAO,YAAoB,KAAK,KAAK,OAAO,QAAQ,OAAO;GAC3D,WAAW,YAAoB,KAAK,KAAK,OAAO,QAAQ,OAAO;GAC/D,QAAQ,YAAoB,KAAK,KAAK,OAAO,SAAS,OAAO;EAC9D;CACD;CAEA,SAAe;EACd,KAAKK,aAAa,YAAY,IAAI;EAClC,KAAK,KAAK,OAAO;CAClB;CAEA,QAAQ,SAAsC;EAC7C,MAAM,UAAwB;GAAE,IAAI,YAAY;GAAM,YAAY,KAAK,MAAM,YAAY,IAAI,IAAI,KAAKA,UAAU;GAAG;EAAQ;EAC3H,KAAKA,aAAa;EAClB,KAAK,KAAK,QAAQ,KAAK,YAAY,WAAW,OAAO;EACrD,OAAO;CACR;AACD"}
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "@wolfstar/cli",
3
+ "version": "0.1.0-next-20260904111115",
4
+ "description": "The stars command line interface for @wolfstar/http-framework projects: typed configuration, dev server, build and project info",
5
+ "keywords": [
6
+ "api",
7
+ "cli",
8
+ "discord",
9
+ "http",
10
+ "pnpm",
11
+ "stars",
12
+ "ts",
13
+ "typescript",
14
+ "wolfstar"
15
+ ],
16
+ "homepage": "https://github.com/wolfstar-project/stars-components/tree/main/packages/cli",
17
+ "bugs": {
18
+ "url": "https://github.com/wolfstar-project/stars-components/issues"
19
+ },
20
+ "license": "Apache-2.0",
21
+ "author": "@wolfstar",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/wolfstar-project/stars-components.git",
25
+ "directory": "packages/cli"
26
+ },
27
+ "bin": {
28
+ "stars": "./dist/cli.js"
29
+ },
30
+ "files": [
31
+ "dist/"
32
+ ],
33
+ "type": "module",
34
+ "sideEffects": false,
35
+ "main": "dist/index.js",
36
+ "module": "dist/index.js",
37
+ "types": "dist/index.d.ts",
38
+ "exports": {
39
+ ".": {
40
+ "import": {
41
+ "types": "./dist/index.d.ts",
42
+ "default": "./dist/index.js"
43
+ }
44
+ },
45
+ "./package.json": "./package.json"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public",
49
+ "provenance": true
50
+ },
51
+ "dependencies": {
52
+ "@clack/prompts": "^1.7.0",
53
+ "chokidar": "^4.0.3",
54
+ "citty": "^0.1.6",
55
+ "colorette": "^2.0.20",
56
+ "ink": "^7.1.1",
57
+ "react": "^19.2.8",
58
+ "@wolfstar/http-framework": "^3.4.0-next-20260904111115"
59
+ },
60
+ "devDependencies": {
61
+ "@types/react": "^19.2.18",
62
+ "@vitest/coverage-v8": "^4.1.11",
63
+ "golar": "^0.1.10",
64
+ "tsdown": "^0.22.14",
65
+ "typescript": "~7.0.2",
66
+ "vite": "^8.2.1",
67
+ "vitest": "^4.1.11"
68
+ },
69
+ "peerDependencies": {
70
+ "tsdown": ">=0.15.0",
71
+ "typescript": ">=5.0.0",
72
+ "vite": ">=6.0.0"
73
+ },
74
+ "peerDependenciesMeta": {
75
+ "tsdown": {
76
+ "optional": true
77
+ },
78
+ "typescript": {
79
+ "optional": true
80
+ },
81
+ "vite": {
82
+ "optional": true
83
+ }
84
+ },
85
+ "engines": {
86
+ "node": ">=22"
87
+ },
88
+ "scripts": {
89
+ "test": "vitest run",
90
+ "build": "tsdown --config-loader unrun",
91
+ "watch": "tsdown --config-loader unrun --watch",
92
+ "typecheck": "golar tsc -p ../../tsconfig.json",
93
+ "lint": "oxlint src --fix"
94
+ }
95
+ }