@wolfstar/cli 2.6.0-next-20261007152629 → 2.6.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"info-C_YDcVul.js","names":[],"sources":["../src/commands/info.ts"],"sourcesContent":["import { displayPath, type ResolvedStarsConfig, type StarsEnvSetupOptions } from '@wolfstar/schema';\nimport { defineCommand } from 'citty';\nimport { createColors } from 'colorette';\nimport { arch, platform } from 'node:os';\nimport { relative } from 'node:path';\nimport { projectArgs, resolveCwd, type ProjectArgs } from '../utils/args.js';\nimport { applyEnvOptions, loadProject } from '../utils/hooks.js';\nimport { modulesPreloadWarning } from '../utils/modules.js';\nimport { resolveOutputMode, shouldUseColor } from '../utils/output-mode.js';\nimport { findInstalledVersion } from '../utils/project.js';\nimport { readOwnPackageJson } from '../utils/version.js';\n\nexport interface InfoOptions extends ProjectArgs {\n\tjson?: boolean;\n\tstdout?: NodeJS.WritableStream;\n}\n\nexport interface ProjectInfo {\n\tcli: { version: string; node: string; platform: string; outputMode: 'tui' | 'plain' };\n\tproject: {\n\t\tcwd: string;\n\t\troot: string;\n\t\tconfigFile: string | null;\n\t\tname: string | null;\n\t\tversion: string | null;\n\t\tentry: string;\n\t\tframeworkVersion: string | null;\n\t};\n\tbuild: ResolvedStarsConfig['build'];\n\tdev: ResolvedStarsConfig['dev'];\n\tcodegen: ResolvedStarsConfig['codegen'];\n\timports: ResolvedStarsConfig['imports'];\n\texperimental: ResolvedStarsConfig['experimental'];\n\tfuture: ResolvedStarsConfig['future'];\n\tenv: ResolvedStarsConfig['env'];\n\t/** The names of the hooks registered in `stars.config`, flattened (`build:done`). */\n\thooks: string[];\n\t/** The installed modules, and the runtime plugins they registered (`module → export from package`). */\n\tmodules: { installed: string[]; plugins: string[] };\n\t/** Non-fatal configuration diagnostics, as `CODE: message`. */\n\twarnings: string[];\n\t/**\n\t * The option names set in the `tsdown`/`vite` blocks. Only the names: the values hold plugins and callbacks,\n\t * which neither serialize to JSON nor read usefully on a terminal.\n\t */\n\toptions: { tsdown: string[]; vite: string[] };\n}\n\nexport function collectInfo(config: ResolvedStarsConfig): ProjectInfo {\n\treturn {\n\t\tcli: {\n\t\t\tversion: readOwnPackageJson().version,\n\t\t\tnode: process.version,\n\t\t\tplatform: `${platform()} ${arch()}`,\n\t\t\toutputMode: resolveOutputMode()\n\t\t},\n\t\tproject: {\n\t\t\tcwd: config.cwd,\n\t\t\troot: config.root,\n\t\t\tconfigFile: config.configFile,\n\t\t\tname: config.packageJson?.name ?? null,\n\t\t\tversion: config.packageJson?.version ?? null,\n\t\t\tentry: config.entry,\n\t\t\tframeworkVersion: findInstalledVersion(config.root, '@wolfstar/http-framework')\n\t\t},\n\t\tbuild: config.build,\n\t\tdev: config.dev,\n\t\tcodegen: config.codegen,\n\t\timports: config.imports,\n\t\texperimental: config.experimental,\n\t\tfuture: config.future,\n\t\tenv: config.env,\n\t\thooks: Object.keys(config.hooks),\n\t\tmodules: {\n\t\t\tinstalled: (config.runtime?.modules ?? []).map((module) => module.name),\n\t\t\tplugins: (config.runtime?.plugins ?? []).map((plugin) => `${plugin.module} → ${plugin.export} from ${plugin.from}`)\n\t\t},\n\t\twarnings: [...config.warnings, modulesPreloadWarning(config)]\n\t\t\t.filter((warning) => warning !== null)\n\t\t\t.map((warning) => `${warning.code}: ${warning.message}`),\n\t\toptions: { tsdown: Object.keys(config.tsdown), vite: Object.keys(config.vite) }\n\t};\n}\n\nexport function formatInfo(info: ProjectInfo, useColor: boolean): string {\n\tconst colors = createColors({ useColor });\n\tconst { root } = info.project;\n\tconst show = (path: string | null) => (path === null ? colors.dim('none') : displayPath(root, path));\n\tconst row = (label: string, value: string) => ` ${colors.dim(label.padEnd(12))}${value}`;\n\tconst section = (title: string, rows: string[]) => [colors.bold(title), ...rows].join('\\n');\n\n\treturn [\n\t\tsection(`stars ${colors.green(`v${info.cli.version}`)}`, [\n\t\t\trow('node', info.cli.node),\n\t\t\trow('platform', info.cli.platform),\n\t\t\trow('output', info.cli.outputMode),\n\t\t\trow(\n\t\t\t\t'framework',\n\t\t\t\tinfo.project.frameworkVersion\n\t\t\t\t\t? `@wolfstar/http-framework v${info.project.frameworkVersion}`\n\t\t\t\t\t: colors.yellow('@wolfstar/http-framework not installed')\n\t\t\t)\n\t\t]),\n\t\tsection('Project', [\n\t\t\trow(\n\t\t\t\t'name',\n\t\t\t\tinfo.project.name\n\t\t\t\t\t? `${info.project.name}${info.project.version ? colors.dim(` v${info.project.version}`) : ''}`\n\t\t\t\t\t: colors.dim('unnamed')\n\t\t\t),\n\t\t\trow('root', root),\n\t\t\trow(\n\t\t\t\t'config',\n\t\t\t\tinfo.project.configFile ? relative(root, info.project.configFile) || info.project.configFile : colors.dim('none (defaults)')\n\t\t\t),\n\t\t\trow('entry', show(info.project.entry))\n\t\t]),\n\t\tsection('Build', [\n\t\t\trow('tool', info.build.tool),\n\t\t\t// Which file the build tool is configured from: its own, or this project's `stars.config`.\n\t\t\trow('config', info.build.configFile ? show(info.build.configFile) : colors.dim('stars.config')),\n\t\t\trow('options', describeOptions(info, colors)),\n\t\t\trow('outDir', show(info.build.outDir)),\n\t\t\trow('tsconfig', show(info.build.tsconfig)),\n\t\t\t// Everything `stars typecheck` checks: the generated app and node projects from compatibility version 6 (a `tsc`\n\t\t\t// or `none` project keeps its own tsconfig for the bot and gets the node one on top).\n\t\t\trow('typechecks', info.dev.typecheck.projects.length > 0 ? info.dev.typecheck.projects.map(show).join(', ') : colors.dim('none')),\n\t\t\trow('runs', show(info.build.output))\n\t\t]),\n\t\tsection('Dev', [\n\t\t\trow('watch', info.dev.watch.map(show).join(', ')),\n\t\t\trow('debounce', `${info.dev.debounce}ms`),\n\t\t\trow('node args', info.dev.nodeArgs.join(' ') || colors.dim('none')),\n\t\t\trow('args', info.dev.args.join(' ') || colors.dim('none')),\n\t\t\trow('url', info.dev.url ?? colors.dim('unknown, set dev.url or HTTP_PORT')),\n\t\t\trow('health', info.dev.health ?? colors.dim('none')),\n\t\t\trow(\n\t\t\t\t'typecheck',\n\t\t\t\tinfo.dev.typecheck.enabled ? `${info.dev.typecheck.checker} → ${show(info.dev.typecheck.tsconfig)}` : colors.dim('disabled')\n\t\t\t),\n\t\t\trow('tunnel', describeTunnel(info.dev.tunnel, colors)),\n\t\t\trow('log file', info.dev.logFile ? show(info.dev.logFile) : colors.dim('disabled')),\n\t\t\trow('log dir', info.dev.logs.dir ? `${show(info.dev.logs.dir)} (keeps ${info.dev.logs.keep})` : colors.dim('disabled')),\n\t\t\trow('layout', info.dev.layout),\n\t\t\trow('mouse', info.dev.mouse ? 'clickable dashboard' : colors.dim('left to the terminal')),\n\t\t\trow('channels', info.dev.logs.channels?.join(', ') ?? colors.dim('all')),\n\t\t\trow('levels', info.dev.logs.levels.join(', ')),\n\t\t\trow('commands', `refresh: ${info.dev.commands.refresh}`),\n\t\t\trow('hmr', info.dev.hmr ? 'left to the bot when it hot reloads' : colors.dim('always restart'))\n\t\t]),\n\t\tsection('Codegen', [\n\t\t\trow('i18n', info.codegen.i18n ? `${show(info.codegen.i18n.locales)} → ${show(info.codegen.i18n.output)}` : colors.dim('disabled')),\n\t\t\trow('commands', info.codegen.commands ? show(info.codegen.commands.output) : colors.dim('disabled'))\n\t\t]),\n\t\tsection('Future', [\n\t\t\trow('compat', `v${info.future.compatibilityVersion} defaults`),\n\t\t\t...info.warnings.map((warning) => row('warning', colors.yellow(warning)))\n\t\t]),\n\t\tsection('Env', [row('register', info.env.enabled ? colors.green(describeEnvOptions(info.env.options)) : colors.dim('disabled'))]),\n\t\tsection('Hooks', [row('registered', info.hooks.join(', ') || colors.dim('none'))]),\n\t\tsection('Modules', [\n\t\t\trow('installed', info.modules.installed.join(', ') || colors.dim('none')),\n\t\t\t...info.modules.plugins.map((plugin) => row('plugin', plugin))\n\t\t]),\n\t\tsection('Experimental', [\n\t\t\trow('vite', flag(info.experimental.enableVite, colors)),\n\t\t\trow('nitro', flag(info.experimental.enableNitro, colors)),\n\t\t\t...(info.experimental.enableNitro ? [row('nitro preset', info.experimental.nitro.preset)] : []),\n\t\t\trow('external', flag(info.experimental.enableExternalVite, colors))\n\t\t]),\n\t\tsection('Imports', [\n\t\t\trow('auto', info.imports.enabled ? colors.green('enabled') : colors.dim('disabled')),\n\t\t\trow('dirs', info.imports.dirs.map(show).join(', ')),\n\t\t\trow('presets', info.imports.presets.join(', ') || colors.dim('none')),\n\t\t\trow('dts', show(info.imports.dts))\n\t\t])\n\t].join('\\n\\n');\n}\n\nfunction describeOptions(info: ProjectInfo, colors: ReturnType<typeof createColors>): string {\n\tconst names = info.build.tool === 'vite' ? info.options.vite : info.options.tsdown;\n\treturn names.length > 0 ? names.join(', ') : colors.dim('none');\n}\n\nfunction describeEnvOptions(options: Readonly<StarsEnvSetupOptions>): string {\n\tconst entries = Object.entries(options).map(([key, value]) => `${key} ${String(value)}`);\n\treturn entries.length > 0 ? `enabled (${entries.join(', ')})` : 'enabled';\n}\n\nfunction flag(enabled: boolean, colors: ReturnType<typeof createColors>): string {\n\treturn enabled ? colors.green('enabled') : colors.dim('disabled');\n}\n\nfunction describeTunnel(tunnel: ResolvedStarsConfig['dev']['tunnel'], colors: ReturnType<typeof createColors>): string {\n\tswitch (tunnel.mode) {\n\t\tcase 'quick':\n\t\t\treturn 'cloudflared quick tunnel';\n\t\tcase 'url':\n\t\t\treturn tunnel.url;\n\t\tdefault:\n\t\t\treturn colors.dim('disabled');\n\t}\n}\n\nexport async function runInfo(options: InfoOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst project = await loadProject({ cwd: resolveCwd(options), configFile: options.config });\n\t// What the build would use: `env:options` may change the env files.\n\tconst config = await applyEnvOptions(project.config, project.hooks);\n\tconst info = collectInfo(config);\n\tstdout.write(`${options.json ? JSON.stringify(info, null, 2) : formatInfo(info, shouldUseColor())}\\n`);\n}\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'info',\n\t\tdescription: 'Print the resolved project configuration and environment'\n\t},\n\targs: {\n\t\t...projectArgs,\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 runInfo({ config: args.config, cwd: args.cwd, json: args.json });\n\t}\n});\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as commands } from "./commands-3Tgwy6cj.js";
1
+ import { t as commands } from "./commands-BiY_99sF.js";
2
2
  import { t as readOwnPackageJson } from "./version-BzclCE19.js";
3
3
  import { n as exitCodeOf, r as formatError } from "./errors-COFWKKKw.js";
4
4
  import { defineCommand, renderUsage, runCommand } from "citty";
@@ -52,4 +52,4 @@ async function runMain(argv = process.argv.slice(2)) {
52
52
 
53
53
  //#endregion
54
54
  export { runMain as t };
55
- //# sourceMappingURL=run-DlNY1QM1.js.map
55
+ //# sourceMappingURL=run-BkfjbY7l.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"run-DlNY1QM1.js","names":["_runCommand"],"sources":["../src/main.ts","../src/run.ts"],"sourcesContent":["import { defineCommand, type CommandDef } from 'citty';\nimport { commands } from './commands/index.js';\nimport { readOwnPackageJson } from './utils/version.js';\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\nconst packageJson = readOwnPackageJson();\n\nexport const main = defineCommand({\n\tmeta: {\n\t\tname: 'stars',\n\t\tversion: packageJson.version,\n\t\tdescription: packageJson.description\n\t},\n\tsubCommands: commands\n}) as AnyCommand;\n","import { renderUsage, runCommand as _runCommand, type CommandDef } from 'citty';\nimport process from 'node:process';\nimport { commands } from './commands/index.js';\nimport { main } from './main.js';\nimport { exitCodeOf, formatError } from './utils/errors.js';\nimport { readOwnPackageJson } from './utils/version.js';\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\n/**\n * Walks the command names of `stars <command> [<subcommand>] --help` so nested commands (`stars commands clean`)\n * render their own usage instead of their parent's.\n */\nasync function resolveHelpTarget(names: string[]): Promise<AnyCommand | null> {\n\tconst [first, ...rest] = names;\n\tif (!first || !Object.hasOwn(commands, first)) return null;\n\n\tlet command = await commands[first]!();\n\tfor (const name of rest) {\n\t\tconst resolved = typeof command.subCommands === 'function' ? await command.subCommands() : await command.subCommands;\n\t\tconst child: unknown = (resolved as Record<string, unknown> | undefined)?.[name];\n\t\tif (!child) break;\n\t\tcommand = (typeof child === 'function' ? await child() : child) as AnyCommand;\n\t}\n\n\treturn command;\n}\n\nexport async function runMain(argv: string[] = process.argv.slice(2)): Promise<void> {\n\tif (argv.includes('--version') || argv.includes('-V')) {\n\t\tconsole.log(readOwnPackageJson().version);\n\t\tprocess.exit(0);\n\t}\n\n\tif (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {\n\t\t// citty's showUsage logs through consola, which is muted in some environments; render and print directly.\n\t\tconst command = await resolveHelpTarget(argv.filter((argument) => !argument.startsWith('-')));\n\t\tconsole.log(await (command ? renderUsage(command, main) : renderUsage(main)));\n\t\tprocess.exit(0);\n\t}\n\n\ttry {\n\t\tawait _runCommand(main, { rawArgs: argv });\n\t} catch (error) {\n\t\tconsole.error(await formatError(error));\n\t\tprocess.exit(exitCodeOf(error));\n\t}\n}\n"],"mappings":";;;;;;;AAQA,MAAM,cAAc,mBAAmB;AAEvC,MAAa,OAAO,cAAc;CACjC,MAAM;EACL,MAAM;EACN,SAAS,YAAY;EACrB,aAAa,YAAY;CAC1B;CACA,aAAa;AACd,CAAC;;;;;;;;ACFD,eAAe,kBAAkB,OAA6C;CAC7E,MAAM,CAAC,OAAO,GAAG,QAAQ;CACzB,IAAI,CAAC,SAAS,CAAC,OAAO,OAAO,UAAU,KAAK,GAAG,OAAO;CAEtD,IAAI,UAAU,MAAM,SAAS,MAAM,CAAE;CACrC,KAAK,MAAM,QAAQ,MAAM;EAExB,MAAM,SADW,OAAO,QAAQ,gBAAgB,aAAa,MAAM,QAAQ,YAAY,IAAI,MAAM,QAAQ,YACzE,GAA2C;EAC3E,IAAI,CAAC,OAAO;EACZ,UAAW,OAAO,UAAU,aAAa,MAAM,MAAM,IAAI;CAC1D;CAEA,OAAO;AACR;AAEA,eAAsB,QAAQ,OAAiB,QAAQ,KAAK,MAAM,CAAC,GAAkB;CACpF,IAAI,KAAK,SAAS,WAAW,KAAK,KAAK,SAAS,IAAI,GAAG;EACtD,QAAQ,IAAI,mBAAmB,CAAC,CAAC,OAAO;EACxC,QAAQ,KAAK,CAAC;CACf;CAEA,IAAI,KAAK,WAAW,KAAK,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,IAAI,GAAG;EAExE,MAAM,UAAU,MAAM,kBAAkB,KAAK,QAAQ,aAAa,CAAC,SAAS,WAAW,GAAG,CAAC,CAAC;EAC5F,QAAQ,IAAI,OAAO,UAAU,YAAY,SAAS,IAAI,IAAI,YAAY,IAAI,EAAE;EAC5E,QAAQ,KAAK,CAAC;CACf;CAEA,IAAI;EACH,MAAMA,WAAY,MAAM,EAAE,SAAS,KAAK,CAAC;CAC1C,SAAS,OAAO;EACf,QAAQ,MAAM,MAAM,YAAY,KAAK,CAAC;EACtC,QAAQ,KAAK,WAAW,KAAK,CAAC;CAC/B;AACD"}
1
+ {"version":3,"file":"run-BkfjbY7l.js","names":["_runCommand"],"sources":["../src/main.ts","../src/run.ts"],"sourcesContent":["import { defineCommand, type CommandDef } from 'citty';\nimport { commands } from './commands/index.js';\nimport { readOwnPackageJson } from './utils/version.js';\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\nconst packageJson = readOwnPackageJson();\n\nexport const main = defineCommand({\n\tmeta: {\n\t\tname: 'stars',\n\t\tversion: packageJson.version,\n\t\tdescription: packageJson.description\n\t},\n\tsubCommands: commands\n}) as AnyCommand;\n","import { renderUsage, runCommand as _runCommand, type CommandDef } from 'citty';\nimport process from 'node:process';\nimport { commands } from './commands/index.js';\nimport { main } from './main.js';\nimport { exitCodeOf, formatError } from './utils/errors.js';\nimport { readOwnPackageJson } from './utils/version.js';\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\n/**\n * Walks the command names of `stars <command> [<subcommand>] --help` so nested commands (`stars commands clean`)\n * render their own usage instead of their parent's.\n */\nasync function resolveHelpTarget(names: string[]): Promise<AnyCommand | null> {\n\tconst [first, ...rest] = names;\n\tif (!first || !Object.hasOwn(commands, first)) return null;\n\n\tlet command = await commands[first]!();\n\tfor (const name of rest) {\n\t\tconst resolved = typeof command.subCommands === 'function' ? await command.subCommands() : await command.subCommands;\n\t\tconst child: unknown = (resolved as Record<string, unknown> | undefined)?.[name];\n\t\tif (!child) break;\n\t\tcommand = (typeof child === 'function' ? await child() : child) as AnyCommand;\n\t}\n\n\treturn command;\n}\n\nexport async function runMain(argv: string[] = process.argv.slice(2)): Promise<void> {\n\tif (argv.includes('--version') || argv.includes('-V')) {\n\t\tconsole.log(readOwnPackageJson().version);\n\t\tprocess.exit(0);\n\t}\n\n\tif (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {\n\t\t// citty's showUsage logs through consola, which is muted in some environments; render and print directly.\n\t\tconst command = await resolveHelpTarget(argv.filter((argument) => !argument.startsWith('-')));\n\t\tconsole.log(await (command ? renderUsage(command, main) : renderUsage(main)));\n\t\tprocess.exit(0);\n\t}\n\n\ttry {\n\t\tawait _runCommand(main, { rawArgs: argv });\n\t} catch (error) {\n\t\tconsole.error(await formatError(error));\n\t\tprocess.exit(exitCodeOf(error));\n\t}\n}\n"],"mappings":";;;;;;;AAQA,MAAM,cAAc,mBAAmB;AAEvC,MAAa,OAAO,cAAc;CACjC,MAAM;EACL,MAAM;EACN,SAAS,YAAY;EACrB,aAAa,YAAY;CAC1B;CACA,aAAa;AACd,CAAC;;;;;;;;ACFD,eAAe,kBAAkB,OAA6C;CAC7E,MAAM,CAAC,OAAO,GAAG,QAAQ;CACzB,IAAI,CAAC,SAAS,CAAC,OAAO,OAAO,UAAU,KAAK,GAAG,OAAO;CAEtD,IAAI,UAAU,MAAM,SAAS,MAAM,CAAE;CACrC,KAAK,MAAM,QAAQ,MAAM;EAExB,MAAM,SADW,OAAO,QAAQ,gBAAgB,aAAa,MAAM,QAAQ,YAAY,IAAI,MAAM,QAAQ,YACzE,GAA2C;EAC3E,IAAI,CAAC,OAAO;EACZ,UAAW,OAAO,UAAU,aAAa,MAAM,MAAM,IAAI;CAC1D;CAEA,OAAO;AACR;AAEA,eAAsB,QAAQ,OAAiB,QAAQ,KAAK,MAAM,CAAC,GAAkB;CACpF,IAAI,KAAK,SAAS,WAAW,KAAK,KAAK,SAAS,IAAI,GAAG;EACtD,QAAQ,IAAI,mBAAmB,CAAC,CAAC,OAAO;EACxC,QAAQ,KAAK,CAAC;CACf;CAEA,IAAI,KAAK,WAAW,KAAK,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,IAAI,GAAG;EAExE,MAAM,UAAU,MAAM,kBAAkB,KAAK,QAAQ,aAAa,CAAC,SAAS,WAAW,GAAG,CAAC,CAAC;EAC5F,QAAQ,IAAI,OAAO,UAAU,YAAY,SAAS,IAAI,IAAI,YAAY,IAAI,EAAE;EAC5E,QAAQ,KAAK,CAAC;CACf;CAEA,IAAI;EACH,MAAMA,WAAY,MAAM,EAAE,SAAS,KAAK,CAAC;CAC1C,SAAS,OAAO;EACf,QAAQ,MAAM,MAAM,YAAY,KAAK,CAAC;EACtC,QAAQ,KAAK,WAAW,KAAK,CAAC;CAC/B;AACD"}
@@ -9,6 +9,7 @@ import { stripVTControlCharacters } from "node:util";
9
9
  import { Box, Text, render, useInput, useWindowSize } from "ink";
10
10
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
+ import { PassThrough } from "node:stream";
12
13
  import { StringDecoder } from "node:string_decoder";
13
14
 
14
15
  //#region src/dev/tui/ink/hooks/useLogVersion.ts
@@ -364,6 +365,13 @@ function useUptime(startedAt) {
364
365
 
365
366
  //#endregion
366
367
  //#region src/dev/tui/ink/components/Sidebar.tsx
368
+ /** The region of the sidebar at a cell, if a click there does something. */
369
+ function findSidebarRegion(regions, row, column) {
370
+ return regions.find((region) => region.row === row && (region.kind !== "item" || column >= region.start && column < region.end)) ?? null;
371
+ }
372
+ /** The padding of the sidebar, then the indentation of the lines of a group. */
373
+ const PADDING = 1;
374
+ const INDENT = 2;
367
375
  const LEVEL_TOKENS = {
368
376
  error: "error",
369
377
  warn: "warning",
@@ -411,7 +419,7 @@ function readFrameworkVersion(root) {
411
419
  * short terminal the key help goes first, then the filters' own hints: the status and the filters are what it is for.
412
420
  */
413
421
  function Sidebar(props) {
414
- const { status, config, counters, filter, channels, focus, collapsed, live, grouped, frame, elapsedMs, width, height, confirmQuit } = props;
422
+ const { status, config, counters, filter, channels, focus, collapsed, live, grouped, frame, elapsedMs, width, height, confirmQuit, onRegions } = props;
415
423
  const paint = usePaint();
416
424
  const uptime = useUptime(status.startedAt);
417
425
  const state = describeBadge(status);
@@ -556,10 +564,28 @@ function Sidebar(props) {
556
564
  };
557
565
  const budget = Math.max(1, height - 1);
558
566
  const labelWidth = Math.max(1, inner - 2);
559
- const lines = (group, labels, nodes, range = [0, Infinity]) => wrapLabels(labels, labelWidth).slice(...range).map((line, index) => /* @__PURE__ */ jsxs(Text, {
560
- wrap: "truncate-end",
561
- children: [" ", line.flatMap((label, position) => position > 0 ? [" ", nodes[label]] : [nodes[label]])]
562
- }, `${group}-${index}`));
567
+ const lines = (group, labels, nodes, range = [0, Infinity]) => wrapLabels(labels, labelWidth).slice(...range).map((line, index) => {
568
+ let column = 3;
569
+ return {
570
+ hit: {
571
+ kind: "items",
572
+ group,
573
+ items: line.map((label) => {
574
+ const start = column;
575
+ column += labels[label].length + 1;
576
+ return {
577
+ index: label,
578
+ start,
579
+ end: column - 1
580
+ };
581
+ })
582
+ },
583
+ node: /* @__PURE__ */ jsxs(Text, {
584
+ wrap: "truncate-end",
585
+ children: [" ".repeat(INDENT), line.flatMap((label, position) => position > 0 ? [" ", nodes[label]] : [nodes[label]])]
586
+ }, `${group}-${index}`)
587
+ };
588
+ });
563
589
  const levelNodes = VIEW_LEVELS.map((level, index) => item("levels", index, level, filter.levels.has(level), LEVEL_TOKENS[level]));
564
590
  const levelLines = collapsed.levels ? [] : lines("levels", VIEW_LEVELS, levelNodes);
565
591
  const channelNodes = channels.map((channel, index) => item("channels", index, channel, isChannelVisible(filter, channel), channelToken(channel)));
@@ -568,46 +594,80 @@ function Sidebar(props) {
568
594
  const range = windowAround(channelLayout.length, focusedLine, Math.max(1, budget - head.length - 3 - levelLines.length));
569
595
  const hiddenChannels = channelLayout.length - (range[1] - range[0]);
570
596
  const filters = [
571
- /* @__PURE__ */ jsx(Text, { children: " " }, "gap-filters"),
572
- groupHeader("channels", !collapsed.channels && hiddenChannels > 0 ? ` · ${range[0] + 1}-${range[1]}/${channelLayout.length}` : ""),
597
+ { node: /* @__PURE__ */ jsx(Text, { children: " " }, "gap-filters") },
598
+ {
599
+ hit: {
600
+ kind: "header",
601
+ group: "channels"
602
+ },
603
+ node: groupHeader("channels", !collapsed.channels && hiddenChannels > 0 ? ` · ${range[0] + 1}-${range[1]}/${channelLayout.length}` : "")
604
+ },
573
605
  ...collapsed.channels ? [] : lines("channels", channels, channelNodes, range),
574
- groupHeader("levels"),
606
+ {
607
+ hit: {
608
+ kind: "header",
609
+ group: "levels"
610
+ },
611
+ node: groupHeader("levels")
612
+ },
575
613
  ...levelLines
576
614
  ];
577
- const keys = KEYS$1.map((section, sectionIndex) => [/* @__PURE__ */ jsx(Text, { children: " " }, `gap-keys-${sectionIndex}`), ...section.map(([key, label]) => /* @__PURE__ */ jsxs(Text, {
578
- wrap: "truncate-end",
579
- children: [
580
- /* @__PURE__ */ jsx(Text, {
581
- bold: true,
582
- color: paint("url"),
583
- children: key
584
- }),
585
- " ",
586
- /* @__PURE__ */ jsx(Text, {
587
- dimColor: true,
588
- children: label
589
- })
590
- ]
591
- }, key))]);
615
+ const keys = KEYS$1.map((section, sectionIndex) => [{ node: /* @__PURE__ */ jsx(Text, { children: " " }, `gap-keys-${sectionIndex}`) }, ...section.map(([key, label]) => ({
616
+ hit: {
617
+ kind: "key",
618
+ key
619
+ },
620
+ node: /* @__PURE__ */ jsxs(Text, {
621
+ wrap: "truncate-end",
622
+ children: [
623
+ /* @__PURE__ */ jsx(Text, {
624
+ bold: true,
625
+ color: paint("url"),
626
+ children: key
627
+ }),
628
+ " ",
629
+ /* @__PURE__ */ jsx(Text, {
630
+ dimColor: true,
631
+ children: label
632
+ })
633
+ ]
634
+ }, key)
635
+ }))]);
636
+ const statusRows = head.map((node) => ({ node }));
592
637
  let body = [
593
- ...head,
638
+ ...statusRows,
594
639
  ...filters,
595
640
  ...keys.flat()
596
641
  ];
597
642
  if (body.length > budget) body = [
598
- ...head,
643
+ ...statusRows,
599
644
  ...filters,
600
645
  ...keys[1]
601
646
  ];
602
- if (body.length > budget) body = [...head, ...filters];
647
+ if (body.length > budget) body = [...statusRows, ...filters];
603
648
  body = body.slice(0, budget);
649
+ const regions = body.flatMap(({ hit }, row) => {
650
+ if (hit === void 0) return [];
651
+ if (hit.kind === "items") return hit.items.map((item) => ({
652
+ kind: "item",
653
+ group: hit.group,
654
+ row,
655
+ ...item
656
+ }));
657
+ return [{
658
+ ...hit,
659
+ row
660
+ }];
661
+ });
662
+ const regionsKey = JSON.stringify(regions);
663
+ useEffect(() => onRegions?.(regions), [regionsKey, onRegions]);
604
664
  return /* @__PURE__ */ jsxs(Box, {
605
665
  flexDirection: "column",
606
666
  width,
607
667
  height,
608
- paddingX: 1,
668
+ paddingX: PADDING,
609
669
  children: [
610
- body.map((node, index) => /* @__PURE__ */ jsx(Box, {
670
+ body.map(({ node }, index) => /* @__PURE__ */ jsx(Box, {
611
671
  height: 1,
612
672
  width: inner,
613
673
  children: node
@@ -638,6 +698,17 @@ function Sidebar(props) {
638
698
 
639
699
  //#endregion
640
700
  //#region src/dev/tui/ink/components/Dashboard.tsx
701
+ /** How many rows a notch of the wheel scrolls. */
702
+ const WHEEL_ROWS = 3;
703
+ /** Two clicks on the same entry within this many milliseconds are a double click. */
704
+ const DOUBLE_CLICK_MS = 400;
705
+ const NO_KEY = {};
706
+ /** The keys of the sidebar a click can press, by how the sidebar writes them: the others stand for several keys. */
707
+ const HINT_KEYS = {
708
+ tab: ["", { tab: true }],
709
+ space: [" ", {}],
710
+ s: ["s", {}]
711
+ };
641
712
  /** The sidebar takes about a quarter of the terminal, within what its longest lines need and can spare. */
642
713
  function sidebarWidth(columns) {
643
714
  return Math.max(24, Math.min(34, Math.floor(columns * .26)));
@@ -648,7 +719,7 @@ function sidebarWidth(columns) {
648
719
  * here only change what is looked at.
649
720
  */
650
721
  function Dashboard(props) {
651
- const { service, status, counters, frame, elapsedMs, width, height, confirmQuit, initialFilter, active, onCapture } = props;
722
+ const { service, status, counters, frame, elapsedMs, width, height, confirmQuit, initialFilter, active, onCapture, mouse, onKey } = props;
652
723
  const paint = usePaint();
653
724
  const [filter, setFilter] = useState(initialFilter);
654
725
  const [focus, setFocus] = useState({
@@ -664,6 +735,11 @@ function Dashboard(props) {
664
735
  const [pinned, setPinned] = useState(null);
665
736
  const [marked, setMarked] = useState(null);
666
737
  const version = useLogVersion(service.logs);
738
+ const regions = useRef([]);
739
+ const lastClick = useRef(null);
740
+ const onRegions = useCallback((next) => {
741
+ regions.current = next;
742
+ }, []);
667
743
  const channels = useMemo(() => [.../* @__PURE__ */ new Set([...initialFilter.allow ?? [], ...service.logs.channels()])].sort(), [
668
744
  service,
669
745
  initialFilter,
@@ -694,7 +770,7 @@ function Dashboard(props) {
694
770
  const scroll = (delta) => {
695
771
  setPinned(pinAt(rows, Math.max(Math.min(paneHeight, rows.length), end + delta)));
696
772
  };
697
- useInput((input, key) => {
773
+ const handleKey = (input, key) => {
698
774
  if (key.ctrl || confirmQuit) return;
699
775
  if (searching) {
700
776
  if (key.escape) {
@@ -770,7 +846,64 @@ function Dashboard(props) {
770
846
  setMarked(row.kind === "entry" ? row.entry.id : null);
771
847
  setPinned(pinAt(rows, Math.max(paneHeight, index + Math.ceil(paneHeight / 2))));
772
848
  }
773
- }, { isActive: active });
849
+ };
850
+ useInput(handleKey, { isActive: active });
851
+ const select = (from, group, index, solo) => {
852
+ const target = (group === "channels" ? channels : VIEW_LEVELS)[index];
853
+ if (target === void 0) return;
854
+ setFocus({
855
+ group,
856
+ index
857
+ });
858
+ if (group === "channels") return change(solo ? soloChannel(from, target) : toggleChannel(from, target));
859
+ const level = target;
860
+ return change(solo ? soloLevel(from, level) : toggleLevel(from, level));
861
+ };
862
+ const handleMouse = (event) => {
863
+ if (confirmQuit) return;
864
+ if (event.kind === "wheel") {
865
+ if (event.column >= side) scroll(event.wheel * WHEEL_ROWS);
866
+ return;
867
+ }
868
+ if (event.kind !== "press" || event.button !== "left" || event.column >= side) return;
869
+ const region = findSidebarRegion(regions.current, event.row, event.column);
870
+ if (region === null) return;
871
+ if (region.kind === "header") {
872
+ setFocus((current) => current.group === region.group ? current : {
873
+ group: region.group,
874
+ index: 0
875
+ });
876
+ return setCollapsed((current) => ({
877
+ ...current,
878
+ [region.group]: !current[region.group]
879
+ }));
880
+ }
881
+ if (region.kind === "key") {
882
+ if (searching) return;
883
+ const own = HINT_KEYS[region.key];
884
+ return own ? handleKey(own[0], {
885
+ ...NO_KEY,
886
+ ...own[1]
887
+ }) : onKey(region.key);
888
+ }
889
+ const now = Date.now();
890
+ const previous = lastClick.current;
891
+ const double = previous !== null && previous.group === region.group && previous.index === region.index && now - previous.at <= DOUBLE_CLICK_MS;
892
+ lastClick.current = double ? null : {
893
+ group: region.group,
894
+ index: region.index,
895
+ at: now,
896
+ filter
897
+ };
898
+ if (double) return select(previous.filter, region.group, region.index, true);
899
+ return select(filter, region.group, region.index, event.alt || event.ctrl);
900
+ };
901
+ const mouseHandler = useRef(handleMouse);
902
+ mouseHandler.current = handleMouse;
903
+ useEffect(() => {
904
+ if (!mouse || !active) return void 0;
905
+ return mouse.subscribe((event) => mouseHandler.current(event));
906
+ }, [mouse, active]);
774
907
  return /* @__PURE__ */ jsxs(Box, {
775
908
  width,
776
909
  height,
@@ -788,7 +921,8 @@ function Dashboard(props) {
788
921
  elapsedMs,
789
922
  width: side,
790
923
  height,
791
- confirmQuit
924
+ confirmQuit,
925
+ onRegions
792
926
  }), /* @__PURE__ */ jsxs(Box, {
793
927
  flexDirection: "column",
794
928
  width: mainWidth + 1,
@@ -1587,7 +1721,8 @@ function resolveLayout(layout, columns, rows) {
1587
1721
  * the bottom of the normal buffer with the logs folded away, and is what a small terminal gets. The browsable
1588
1722
  * overlays use the alternate buffer from either, preserving the terminal's history and the panel when they close.
1589
1723
  */
1590
- function DevApp({ service, color, theme, reducedMotion, layout = "auto", filter, onQuit, onViewChange, onCopy, onThemeSave }) {
1724
+ function DevApp(props) {
1725
+ const { service, color, theme, reducedMotion, layout = "auto", filter, onQuit, onViewChange, onCopy, onThemeSave, mouse, onMouseChange } = props;
1591
1726
  const { columns, rows } = useWindowSize();
1592
1727
  const [overlay, setOverlay] = useState(null);
1593
1728
  const [chosen, setChosen] = useState(layout);
@@ -1620,18 +1755,13 @@ function DevApp({ service, color, theme, reducedMotion, layout = "auto", filter,
1620
1755
  onViewChange(fullScreen);
1621
1756
  repaint((count) => count + 1);
1622
1757
  }, [fullScreen, onViewChange]);
1758
+ const takesMouse = base === "dashboard" && overlay === null;
1759
+ useEffect(() => {
1760
+ onMouseChange?.(takesMouse);
1761
+ }, [takesMouse, onMouseChange]);
1623
1762
  const onCapture = useCallback((capturing) => setCaptured(capturing), []);
1624
- useInput((input, key) => {
1625
- if (key.ctrl && input === "c") return onQuit();
1626
- if (key.ctrl && input === "l") return service.clearLogs();
1627
- if (overlay !== null || captured) return;
1628
- if (confirmQuit) {
1629
- if (input === "y") return onQuit();
1630
- return setConfirmQuit(false);
1631
- }
1632
- if (key.ctrl && input === "r") return void service.restart("manual");
1633
- if (key.ctrl && input === "d") return busy ? setConfirmQuit(true) : onQuit();
1634
- if (status.prompt && (input === "y" || input === "n")) return service.answerPrompt(input === "y");
1763
+ /** The keys that act on the session or change the view; also what a click on their hint in the sidebar runs. */
1764
+ const runKey = (input) => {
1635
1765
  switch (input) {
1636
1766
  case "q": return busy ? setConfirmQuit(true) : onQuit();
1637
1767
  case "r":
@@ -1655,6 +1785,19 @@ function DevApp({ service, color, theme, reducedMotion, layout = "auto", filter,
1655
1785
  case "h":
1656
1786
  case "?": return show("help");
1657
1787
  }
1788
+ };
1789
+ useInput((input, key) => {
1790
+ if (key.ctrl && input === "c") return onQuit();
1791
+ if (key.ctrl && input === "l") return service.clearLogs();
1792
+ if (overlay !== null || captured) return;
1793
+ if (confirmQuit) {
1794
+ if (input === "y") return onQuit();
1795
+ return setConfirmQuit(false);
1796
+ }
1797
+ if (key.ctrl && input === "r") return void service.restart("manual");
1798
+ if (key.ctrl && input === "d") return busy ? setConfirmQuit(true) : onQuit();
1799
+ if (status.prompt && (input === "y" || input === "n")) return service.answerPrompt(input === "y");
1800
+ runKey(input);
1658
1801
  });
1659
1802
  return /* @__PURE__ */ jsx(ThemeProvider, {
1660
1803
  color,
@@ -1712,7 +1855,9 @@ function DevApp({ service, color, theme, reducedMotion, layout = "auto", filter,
1712
1855
  confirmQuit,
1713
1856
  initialFilter,
1714
1857
  active: overlay === null,
1715
- onCapture
1858
+ onCapture,
1859
+ mouse,
1860
+ onKey: runKey
1716
1861
  })
1717
1862
  }),
1718
1863
  base === "panel" && overlay === null && /* @__PURE__ */ jsx(Panel, {
@@ -1729,6 +1874,113 @@ function DevApp({ service, color, theme, reducedMotion, layout = "auto", filter,
1729
1874
  });
1730
1875
  }
1731
1876
 
1877
+ //#endregion
1878
+ //#region src/dev/tui/mouse.ts
1879
+ /**
1880
+ * Asks the terminal to report button presses and the wheel (`1000`), with coordinates in the SGR form (`1006`),
1881
+ * which is not limited to 223 columns. Motion is left off: nothing here reacts to a hover, and it is one sequence
1882
+ * per cell the pointer crosses. A terminal that knows neither ignores both.
1883
+ */
1884
+ const ENABLE_MOUSE = "\x1B[?1000h\x1B[?1006h";
1885
+ const DISABLE_MOUSE = "\x1B[?1006l\x1B[?1000l";
1886
+ const SGR_MOUSE = /\u001B\[<(\d+);(\d+);(\d+)([Mm])/g;
1887
+ /** The start of a report whose end has not arrived yet, at the end of a chunk. */
1888
+ const PARTIAL_SGR_MOUSE = /\u001B(?:\[(?:<[\d;]*)?)?$/;
1889
+ const BUTTONS = [
1890
+ "left",
1891
+ "middle",
1892
+ "right",
1893
+ "none"
1894
+ ];
1895
+ /**
1896
+ * Splits what the terminal sent into the mouse reports and everything else.
1897
+ *
1898
+ * @param chunk - A chunk read from the terminal.
1899
+ * @returns The mouse events of the chunk, and the chunk without their sequences: the keys the user typed.
1900
+ */
1901
+ function parseMouse(chunk) {
1902
+ const events = [];
1903
+ return {
1904
+ events,
1905
+ rest: chunk.replace(SGR_MOUSE, (_match, code, column, row, final) => {
1906
+ const bits = Number(code);
1907
+ if ((bits & 32) !== 0 || (bits & 128) !== 0) return "";
1908
+ const wheel = (bits & 64) !== 0;
1909
+ if (wheel && (bits & 2) !== 0) return "";
1910
+ const event = {
1911
+ column: Number(column) - 1,
1912
+ row: Number(row) - 1,
1913
+ shift: (bits & 4) !== 0,
1914
+ alt: (bits & 8) !== 0,
1915
+ ctrl: (bits & 16) !== 0
1916
+ };
1917
+ if (wheel) events.push({
1918
+ ...event,
1919
+ kind: "wheel",
1920
+ button: "none",
1921
+ wheel: (bits & 1) === 0 ? -1 : 1
1922
+ });
1923
+ else events.push({
1924
+ ...event,
1925
+ kind: final === "M" ? "press" : "release",
1926
+ button: BUTTONS[bits & 3],
1927
+ wheel: 0
1928
+ });
1929
+ return "";
1930
+ })
1931
+ };
1932
+ }
1933
+ /**
1934
+ * Reads the terminal's input ahead of the UI, so that a mouse report is never taken for typed text: Ink would
1935
+ * otherwise hand `ESC[<0;12;5M` to its key handlers, and a click while searching would type it into the query.
1936
+ *
1937
+ * @param source - The terminal's input.
1938
+ */
1939
+ function createMouseInput(source) {
1940
+ const filtered = new PassThrough();
1941
+ const listeners = /* @__PURE__ */ new Set();
1942
+ const decoder = new StringDecoder("utf8");
1943
+ let pending = "";
1944
+ const onData = (data) => {
1945
+ const { events, rest } = parseMouse(pending + (typeof data === "string" ? data : decoder.write(data)));
1946
+ const partial = rest.length > 1 ? PARTIAL_SGR_MOUSE.exec(rest) : null;
1947
+ const held = partial && partial[0].length > 1 ? partial[0] : "";
1948
+ pending = held;
1949
+ const keys = held ? rest.slice(0, -held.length) : rest;
1950
+ if (keys) filtered.write(keys);
1951
+ for (const event of events) for (const listener of listeners) listener(event);
1952
+ };
1953
+ source.on("data", onData);
1954
+ const stdin = Object.assign(filtered, {
1955
+ isTTY: source.isTTY,
1956
+ setRawMode(mode) {
1957
+ source.setRawMode?.(mode);
1958
+ return stdin;
1959
+ },
1960
+ ref() {
1961
+ source.ref?.();
1962
+ return stdin;
1963
+ },
1964
+ unref() {
1965
+ source.unref?.();
1966
+ return stdin;
1967
+ }
1968
+ });
1969
+ return {
1970
+ stdin,
1971
+ subscribe(listener) {
1972
+ listeners.add(listener);
1973
+ return () => listeners.delete(listener);
1974
+ },
1975
+ dispose() {
1976
+ source.off("data", onData);
1977
+ source.pause?.();
1978
+ listeners.clear();
1979
+ filtered.end();
1980
+ }
1981
+ };
1982
+ }
1983
+
1732
1984
  //#endregion
1733
1985
  //#region src/dev/tui/capture-output.ts
1734
1986
  /** Capture third-party writes before build plugins run. Ink writes through a separate, unpatched sink. */
@@ -1781,6 +2033,13 @@ function createTuiRenderer(service, options = {}) {
1781
2033
  const restoreOutput = stdout === process.stdout ? [captureOutput(service, process.stdout, "info"), captureOutput(service, process.stderr, "warn")] : [];
1782
2034
  const layout = options.layout ?? service.config.dev.layout ?? "auto";
1783
2035
  let alternate = resolveLayout(layout, stdout.columns ?? 80, stdout.rows ?? 24) === "dashboard";
2036
+ const mouse = options.mouse ?? service.config.dev.mouse ? createMouseInput(stdin) : null;
2037
+ let reporting = false;
2038
+ const reportMouse = (wanted) => {
2039
+ if (!mouse || wanted === reporting) return;
2040
+ reporting = wanted;
2041
+ write(wanted ? ENABLE_MOUSE : DISABLE_MOUSE);
2042
+ };
1784
2043
  if (alternate) write("\x1B[?1049h\x1B[H");
1785
2044
  let stopped = false;
1786
2045
  const switchView = (fullScreen) => {
@@ -1804,10 +2063,14 @@ function createTuiRenderer(service, options = {}) {
1804
2063
  filter: options.filter,
1805
2064
  onQuit: quit,
1806
2065
  onViewChange: switchView,
1807
- onCopy: (text) => write(`\u001B]52;c;${Buffer.from(text.slice(0, 65536)).toString("base64")}\u0007`)
2066
+ onCopy: (text) => write(`\u001B]52;c;${Buffer.from(text.slice(0, 65536)).toString("base64")}\u0007`),
2067
+ mouse: mouse ?? void 0,
2068
+ onMouseChange: (wanted) => {
2069
+ if (!stopped) reportMouse(wanted);
2070
+ }
1808
2071
  }), {
1809
2072
  stdout: sink,
1810
- stdin,
2073
+ stdin: mouse?.stdin ?? stdin,
1811
2074
  exitOnCtrlC: false,
1812
2075
  patchConsole: false,
1813
2076
  interactive: true,
@@ -1823,7 +2086,9 @@ function createTuiRenderer(service, options = {}) {
1823
2086
  service.promptable = false;
1824
2087
  if (alternate) instance.clear();
1825
2088
  instance.unmount();
2089
+ reportMouse(false);
1826
2090
  if (alternate) write("\x1B[?1049l");
2091
+ mouse?.dispose();
1827
2092
  write("\x1B[?25h");
1828
2093
  for (const restore of restoreOutput) restore();
1829
2094
  if (service.status.progress.readyMs === null) {
@@ -1837,4 +2102,4 @@ function createTuiRenderer(service, options = {}) {
1837
2102
 
1838
2103
  //#endregion
1839
2104
  export { createTuiRenderer };
1840
- //# sourceMappingURL=tui-CB_qk2Z1.js.map
2105
+ //# sourceMappingURL=tui-BOkOx13K.js.map