wp-typia 0.19.0 → 0.20.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.
- package/README.md +4 -2
- package/dist-bunli/.bunli/commands.gen.js +1843 -1216
- package/dist-bunli/.bunli/commands.gen.js.map +36 -33
- package/dist-bunli/cli-03j0axbt.js +163 -0
- package/dist-bunli/cli-03j0axbt.js.map +11 -0
- package/dist-bunli/{cli-7svz19s1.js → cli-2ybmc22r.js} +370 -86
- package/dist-bunli/{cli-7svz19s1.js.map → cli-2ybmc22r.js.map} +15 -14
- package/dist-bunli/cli-7yg38ht2.js +427 -0
- package/dist-bunli/cli-7yg38ht2.js.map +12 -0
- package/dist-bunli/{cli-yw0mq0wq.js → cli-93wd227r.js} +108 -3
- package/dist-bunli/cli-93wd227r.js.map +10 -0
- package/dist-bunli/{cli-kan7a6db.js → cli-a6dwqnhq.js} +24 -2
- package/dist-bunli/cli-a6dwqnhq.js.map +11 -0
- package/dist-bunli/{cli-add-j2c81sh1.js → cli-add-pq6wm87p.js} +16 -9
- package/dist-bunli/{cli-add-j2c81sh1.js.map → cli-add-pq6wm87p.js.map} +3 -3
- package/dist-bunli/{cli-diagnostics-c65hhyhx.js → cli-diagnostics-e5gxeprp.js} +8 -4
- package/dist-bunli/{cli-diagnostics-c65hhyhx.js.map → cli-diagnostics-e5gxeprp.js.map} +1 -1
- package/dist-bunli/{cli-doctor-hft0wr0e.js → cli-doctor-qk6fwpds.js} +14 -13
- package/dist-bunli/{cli-doctor-hft0wr0e.js.map → cli-doctor-qk6fwpds.js.map} +3 -3
- package/dist-bunli/{cli-572d6g4m.js → cli-hv2yedw2.js} +2 -157
- package/dist-bunli/{cli-572d6g4m.js.map → cli-hv2yedw2.js.map} +4 -6
- package/dist-bunli/{cli-scaffold-vcg6wem5.js → cli-scaffold-s3nhwe7x.js} +9 -7
- package/dist-bunli/cli-scaffold-s3nhwe7x.js.map +10 -0
- package/dist-bunli/cli-t73q5aqz.js +103 -0
- package/dist-bunli/cli-t73q5aqz.js.map +10 -0
- package/dist-bunli/{cli-templates-4qxszbmc.js → cli-templates-j65r4k9v.js} +1 -1
- package/dist-bunli/{cli-wtrvnce5.js → cli-w4r0rr8a.js} +8 -8
- package/dist-bunli/cli-w4r0rr8a.js.map +10 -0
- package/dist-bunli/cli.js +127 -2863
- package/dist-bunli/cli.js.map +5 -56
- package/dist-bunli/command-list-37n1za5q.js +2485 -0
- package/dist-bunli/command-list-37n1za5q.js.map +58 -0
- package/dist-bunli/node-cli.js +432 -323
- package/dist-bunli/node-cli.js.map +11 -10
- package/dist-bunli/{sync-x91y9jtv.js → sync-k2k8svyc.js} +3 -2
- package/dist-bunli/{sync-x91y9jtv.js.map → sync-k2k8svyc.js.map} +1 -1
- package/package.json +6 -3
- package/dist-bunli/cli-kan7a6db.js.map +0 -11
- package/dist-bunli/cli-scaffold-vcg6wem5.js.map +0 -10
- package/dist-bunli/cli-wtrvnce5.js.map +0 -10
- package/dist-bunli/cli-yw0mq0wq.js.map +0 -10
- /package/dist-bunli/{cli-templates-4qxszbmc.js.map → cli-templates-j65r4k9v.js.map} +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/commands/add.ts", "../src/render-loader.ts", "../src/runtime-bridge-add-dry-run.ts", "../src/runtime-bridge-output.ts", "../src/runtime-capabilities.ts", "../src/runtime-bridge-sync.ts", "../src/runtime-bridge.ts", "../src/ui/lazy-flow.tsx", "../src/ui/alternate-buffer-lifecycle.ts", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/utils/format.ts", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/form.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/form-context.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/form-field.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/select-field.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/multi-select-field.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/number-field.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/password-field.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/textarea-field.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/checkbox-field.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/schema-form.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/key-value-list.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/grid.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/modal.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/tabs.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/confirm.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/menu.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/nav-list.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/command-palette.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/filter.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/data-table.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/sidebar-layout.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/spinner.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/pager.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/choose.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/components/file-picker.tsx", "../../../node_modules/.bun/@bunli+tui@0.6.0+df522c502ed1ba91/node_modules/@bunli/tui/src/charts/index.tsx", "../src/commands/create.ts", "../src/commands/doctor.ts", "../src/mcp.ts", "../../../node_modules/.bun/@bunli+plugin-mcp@0.2.5+ef72ce197b058209/node_modules/@bunli/plugin-mcp/src/errors.ts", "../../../node_modules/.bun/@bunli+plugin-mcp@0.2.5+ef72ce197b058209/node_modules/@bunli/plugin-mcp/src/schema-to-zod.ts", "../../../node_modules/.bun/@bunli+plugin-mcp@0.2.5+ef72ce197b058209/node_modules/@bunli/plugin-mcp/src/utils.ts", "../../../node_modules/.bun/@bunli+plugin-mcp@0.2.5+ef72ce197b058209/node_modules/@bunli/plugin-mcp/src/converter.ts", "../../../node_modules/.bun/@bunli+plugin-mcp@0.2.5+ef72ce197b058209/node_modules/@bunli/plugin-mcp/src/codegen.ts", "../src/commands/mcp.ts", "../src/commands/migrate.ts", "../src/commands/sync.ts", "../src/commands/templates.ts", "../src/command-list.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { createElement } from \"react\";\n\nimport { defineCommand } from \"@bunli/core\";\n\nimport {\n\tADD_OPTION_METADATA,\n\tbuildCommandOptions,\n\tresolveCommandOptionValues,\n} from \"../command-option-metadata\";\nimport { getAddBlockDefaults } from \"../config\";\nimport { resolveBundledModuleHref } from \"../render-loader\";\nimport { executeAddCommand } from \"../runtime-bridge\";\nimport { supportsInteractiveTui } from \"../runtime-capabilities\";\nimport type { WpTypiaRenderArgs } from \"./render-types\";\nimport { LazyFlow } from \"../ui/lazy-flow\";\n\nfunction loadAddFlow() {\n\treturn import(\n\t\tresolveBundledModuleHref(import.meta.url, [\n\t\t\t\"./ui/add-flow.js\",\n\t\t\t\"../ui/add-flow.js\",\n\t\t\t\"../ui/add-flow.tsx\",\n\t\t], {\n\t\t\tmoduleLabel: \"the add-flow UI\",\n\t\t}),\n\t).then((module) => ({ default: module.AddFlow }));\n}\n\nconst addOptions = buildCommandOptions(ADD_OPTION_METADATA);\n\nexport const addCommand = defineCommand({\n\tdefaultFormat: \"toon\",\n\tdescription: \"Extend an official wp-typia workspace with blocks, variations, patterns, binding sources, plugin-level REST resources, editor plugins, or hooked blocks.\",\n\thandler: async (args) => {\n\t\tawait executeAddCommand({\n\t\t\tcwd: args.cwd,\n\t\t\tflags: args.flags as Record<string, unknown>,\n\t\t\tkind: args.positional[0],\n\t\t\tname: args.positional[1],\n\t\t});\n\t},\n\tname: \"add\",\n\toptions: addOptions,\n\t...(supportsInteractiveTui()\n\t\t? {\n\t\t\t\trender: (args: WpTypiaRenderArgs) => {\n\t\t\t\t\tconst config =\n\t\t\t\t\t\targs.context?.store?.wpTypiaUserConfig &&\n\t\t\t\t\t\ttypeof args.context.store.wpTypiaUserConfig === \"object\"\n\t\t\t\t\t\t\t? getAddBlockDefaults(args.context.store.wpTypiaUserConfig)\n\t\t\t\t\t\t\t: {};\n\t\t\t\t\tconst initialValues = resolveCommandOptionValues(ADD_OPTION_METADATA, {\n\t\t\t\t\t\tdefaults: config,\n\t\t\t\t\t\tflags: args.flags as Record<string, unknown>,\n\t\t\t\t\t\toptionNames: Object.keys(ADD_OPTION_METADATA).filter(\n\t\t\t\t\t\t\t(optionName) => optionName !== \"dry-run\",\n\t\t\t\t\t\t),\n\t\t\t\t\t});\n\t\t\t\t\treturn createElement(LazyFlow, {\n\t\t\t\t\t\tloader: loadAddFlow,\n\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\tcwd: args.cwd,\n\t\t\t\t\t\t\tinitialValues: {\n\t\t\t\t\t\t\t\t...initialValues,\n\t\t\t\t\t\t\t\tkind:\n\t\t\t\t\t\t\t\t\t(args.positional[0] as\n\t\t\t\t\t\t\t\t\t\t| \"block\"\n\t\t\t\t\t\t\t\t\t\t| \"variation\"\n\t\t\t\t\t\t\t\t\t\t| \"pattern\"\n\t\t\t\t\t\t\t\t\t\t| \"binding-source\"\n\t\t\t\t\t\t\t\t\t\t| \"rest-resource\"\n\t\t\t\t\t\t\t\t\t\t| \"editor-plugin\"\n\t\t\t\t\t\t\t\t\t\t| \"hooked-block\"\n\t\t\t\t\t\t\t\t\t\t| undefined) ?? \"block\",\n\t\t\t\t\t\t\t\tname: args.positional[1] ?? \"\",\n\t\t\t\t\t\t\t\tposition: initialValues.position ?? \"after\",\n\t\t\t\t\t\t\t\tslot: initialValues.slot ?? \"PluginSidebar\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\ttui: {\n\t\t\t\t\trenderer: {\n\t\t\t\t\t\tbufferMode: \"alternate\" as const,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t: {}),\n});\n\nexport default addCommand;\n",
|
|
6
|
+
"import fs from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nexport function resolveBundledModuleHref(\n\tbaseUrl: string,\n\tcandidates: string[],\n\toptions: {\n\t\tmoduleLabel?: string;\n\t} = {},\n): string {\n\tfor (const candidate of candidates) {\n\t\tconst url = new URL(candidate, baseUrl);\n\t\tif (fs.existsSync(fileURLToPath(url))) {\n\t\t\treturn url.href;\n\t\t}\n\t}\n\n\tconst missingCandidates = candidates.map((candidate) =>\n\t\tfileURLToPath(new URL(candidate, baseUrl)),\n\t);\n\tconst label = options.moduleLabel ?? \"bundled wp-typia runtime module\";\n\tthrow new Error(\n\t\t[\n\t\t\t`Missing bundled build artifacts for ${label}.`,\n\t\t\t\"None of the expected files were found:\",\n\t\t\t...missingCandidates.map((candidatePath) => `- ${candidatePath}`),\n\t\t\t\"Run `bun run --filter wp-typia build` in a source checkout, or reinstall the published package/standalone binary if its bundled files are missing.\",\n\t\t].join(\"\\n\"),\n\t);\n}\n",
|
|
7
|
+
"import fs from \"node:fs\";\nimport { promises as fsp } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { createManagedTempRoot } from \"@wp-typia/project-tools/temp-roots\";\n\ntype WorkspaceFileOperation = `delete ${string}` | `update ${string}` | `write ${string}`;\n\nconst SKIPPED_COPY_ROOT_ENTRIES = new Set([\".git\", \"node_modules\"]);\nconst SKIPPED_COMPARE_ROOT_ENTRIES = new Set([\n\t\".git\",\n\t\".pnp.cjs\",\n\t\".pnp.loader.mjs\",\n\t\"node_modules\",\n]);\n\nasync function copyWorkspaceProject(sourceDir: string, targetDir: string): Promise<void> {\n\tawait fsp.cp(sourceDir, targetDir, {\n\t\tfilter: (sourcePath) => {\n\t\t\tconst relativePath = path.relative(sourceDir, sourcePath);\n\t\t\tif (relativePath === \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst [rootEntry] = relativePath.split(path.sep);\n\t\t\treturn !SKIPPED_COPY_ROOT_ENTRIES.has(rootEntry ?? \"\");\n\t\t},\n\t\trecursive: true,\n\t});\n}\n\nfunction ensureWorkspaceInstallMarkers(sourceDir: string, targetDir: string): void {\n\tconst sourceNodeModules = path.join(sourceDir, \"node_modules\");\n\tif (fs.existsSync(sourceNodeModules)) {\n\t\tfs.symlinkSync(sourceNodeModules, path.join(targetDir, \"node_modules\"), \"junction\");\n\t}\n\n\tfor (const marker of [\".pnp.cjs\", \".pnp.loader.mjs\"] as const) {\n\t\tconst sourceMarker = path.join(sourceDir, marker);\n\t\tif (!fs.existsSync(sourceMarker)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst targetMarker = path.join(targetDir, marker);\n\t\ttry {\n\t\t\tfs.symlinkSync(sourceMarker, targetMarker);\n\t\t} catch {\n\t\t\ttry {\n\t\t\t\tfs.linkSync(sourceMarker, targetMarker);\n\t\t\t} catch {\n\t\t\t\tfs.copyFileSync(sourceMarker, targetMarker);\n\t\t\t}\n\t\t}\n\t}\n}\n\nasync function listWorkspaceFiles(rootDir: string): Promise<Map<string, Buffer>> {\n\tconst files = new Map<string, Buffer>();\n\n\tasync function visit(currentDir: string): Promise<void> {\n\t\tconst entries = await fsp.readdir(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst absolutePath = path.join(currentDir, entry.name);\n\t\t\tconst relativePath = path.relative(rootDir, absolutePath);\n\t\t\tconst [rootEntry] = relativePath.split(path.sep);\n\t\t\tif (SKIPPED_COMPARE_ROOT_ENTRIES.has(rootEntry ?? \"\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tawait visit(absolutePath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!entry.isFile()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfiles.set(\n\t\t\t\trelativePath.replace(path.sep === \"\\\\\" ? /\\\\/gu : /\\//gu, \"/\"),\n\t\t\t\tawait fsp.readFile(absolutePath),\n\t\t\t);\n\t\t}\n\t}\n\n\tawait visit(rootDir);\n\treturn files;\n}\n\nfunction compareStrings(left: string, right: string): number {\n\tif (left < right) {\n\t\treturn -1;\n\t}\n\tif (left > right) {\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nasync function collectWorkspaceFileOperations(\n\tsourceDir: string,\n\tsimulatedDir: string,\n): Promise<WorkspaceFileOperation[]> {\n\tconst [sourceFiles, simulatedFiles] = await Promise.all([\n\t\tlistWorkspaceFiles(sourceDir),\n\t\tlistWorkspaceFiles(simulatedDir),\n\t]);\n\tconst operations: WorkspaceFileOperation[] = [];\n\n\tfor (const [relativePath, simulatedSource] of simulatedFiles) {\n\t\tconst originalSource = sourceFiles.get(relativePath);\n\t\tif (originalSource === undefined) {\n\t\t\toperations.push(`write ${relativePath}`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!originalSource.equals(simulatedSource)) {\n\t\t\toperations.push(`update ${relativePath}`);\n\t\t}\n\t}\n\n\tfor (const relativePath of sourceFiles.keys()) {\n\t\tif (!simulatedFiles.has(relativePath)) {\n\t\t\toperations.push(`delete ${relativePath}`);\n\t\t}\n\t}\n\n\treturn operations.sort(compareStrings);\n}\n\n/**\n * Runs a mutating workspace add command against a temporary clone and reports\n * the file operations that would be applied to the real workspace.\n *\n * @param options Workspace add dry-run options.\n * @returns The simulated command result plus normalized file operations.\n */\nexport async function simulateWorkspaceAddDryRun<T>({\n\tcwd,\n\texecute,\n}: {\n\tcwd: string;\n\texecute: (simulatedCwd: string) => Promise<T>;\n}): Promise<{\n\tfileOperations: WorkspaceFileOperation[];\n\tresult: T;\n}> {\n\tconst { resolveWorkspaceProject } = await import(\"@wp-typia/project-tools/workspace-project\");\n\tconst workspace = resolveWorkspaceProject(cwd);\n\tconst relativeCwd = path.relative(workspace.projectDir, path.resolve(cwd));\n\tconst { path: tempRoot, cleanup } = await createManagedTempRoot(\n\t\t\"wp-typia-add-plan-\",\n\t);\n\tconst simulatedProjectDir = path.join(tempRoot, \"workspace\");\n\n\ttry {\n\t\tawait copyWorkspaceProject(workspace.projectDir, simulatedProjectDir);\n\t\tensureWorkspaceInstallMarkers(workspace.projectDir, simulatedProjectDir);\n\n\t\tconst simulatedCwd =\n\t\t\trelativeCwd.length > 0 ? path.join(simulatedProjectDir, relativeCwd) : simulatedProjectDir;\n\t\tconst result = await execute(simulatedCwd);\n\t\tconst fileOperations = await collectWorkspaceFileOperations(\n\t\t\tworkspace.projectDir,\n\t\t\tsimulatedProjectDir,\n\t\t);\n\n\t\treturn {\n\t\t\tfileOperations,\n\t\t\tresult,\n\t\t};\n\t} finally {\n\t\tawait cleanup();\n\t}\n}\n",
|
|
8
|
+
"import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport packageJson from \"../package.json\";\nimport { formatPackageExecCommand } from \"@wp-typia/project-tools/package-managers\";\nimport type { AlternateBufferCompletionPayload } from \"./ui/alternate-buffer-lifecycle\";\n\ntype PrintLine = (line: string) => void;\ntype PackageManagerId = \"bun\" | \"npm\" | \"pnpm\" | \"yarn\";\n\nexport type CreateProgressPayload = {\n\tdetail: string;\n\ttitle: string;\n};\n\ntype ExternalLayerSelectOption = {\n\tdescription?: string;\n\textends: string[];\n\tid: string;\n};\n\n/**\n * Prints a formatted alternate-buffer completion payload to the provided writers.\n *\n * @param payload Structured completion payload to render.\n * @param options Optional line printers for normal output and warnings.\n * @returns Nothing.\n */\nexport function printCompletionPayload(\n\tpayload: AlternateBufferCompletionPayload,\n\toptions: {\n\t\tprintLine?: PrintLine;\n\t\twarnLine?: PrintLine;\n\t} = {},\n): void {\n\tconst printLine = options.printLine ?? (console.log as PrintLine);\n\tconst warnLine = options.warnLine ?? printLine;\n\n\tfor (const line of payload.preambleLines ?? []) {\n\t\tprintLine(line);\n\t}\n\tfor (const warning of payload.warningLines ?? []) {\n\t\twarnLine(`⚠️ ${warning}`);\n\t}\n\n\tconst hasDetails =\n\t\t(payload.summaryLines?.length ?? 0) > 0 ||\n\t\t(payload.nextSteps?.length ?? 0) > 0 ||\n\t\t(payload.optionalLines?.length ?? 0) > 0 ||\n\t\tBoolean(payload.optionalNote);\n\tconst hasLeadingContext =\n\t\t(payload.preambleLines?.length ?? 0) > 0 ||\n\t\t(payload.warningLines?.length ?? 0) > 0;\n\n\tprintLine(hasLeadingContext && hasDetails ? `\\n${payload.title}` : payload.title);\n\tfor (const line of payload.summaryLines ?? []) {\n\t\tprintLine(line);\n\t}\n\tif ((payload.nextSteps?.length ?? 0) > 0) {\n\t\tprintLine(\"Next steps:\");\n\t\tfor (const step of payload.nextSteps ?? []) {\n\t\t\tprintLine(` ${step}`);\n\t\t}\n\t}\n\tif ((payload.optionalLines?.length ?? 0) > 0) {\n\t\tprintLine(`\\n${payload.optionalTitle ?? \"Optional:\"}`);\n\t\tfor (const step of payload.optionalLines ?? []) {\n\t\t\tprintLine(` ${step}`);\n\t\t}\n\t}\n\tif (payload.optionalNote) {\n\t\tprintLine(`Note: ${payload.optionalNote}`);\n\t}\n}\n\n/**\n * Formats a lightweight create-progress line for fallback CLI output.\n *\n * @param payload User-facing scaffold progress payload.\n * @returns A single readable status line.\n */\nexport function formatCreateProgressLine(payload: CreateProgressPayload): string {\n\treturn `⏳ ${payload.title}: ${payload.detail}`;\n}\n\n/**\n * Builds the completion payload shown after a create flow succeeds.\n *\n * @param flow Resolved create-flow data including onboarding steps and warnings.\n * @returns A structured alternate-buffer completion payload.\n */\nexport function buildCreateCompletionPayload(flow: {\n\tnextSteps: string[];\n\toptionalOnboarding: {\n\t\tnote: string;\n\t\tsteps: string[];\n\t};\n\tpackageManager: string;\n\tprojectDir: string;\n\tresult: {\n\t\tselectedVariant?: string | null;\n\t\tvariables: {\n\t\t\ttitle: string;\n\t\t};\n\t\twarnings: string[];\n\t};\n}): AlternateBufferCompletionPayload {\n\tconst verificationSteps = [\n\t\tformatPackageExecCommand(\n\t\t\tflow.packageManager as \"bun\" | \"npm\" | \"pnpm\" | \"yarn\",\n\t\t\t`wp-typia@${packageJson.version}`,\n\t\t\t\"doctor\",\n\t\t),\n\t\t...flow.optionalOnboarding.steps,\n\t];\n\n\treturn {\n\t\tnextSteps: flow.nextSteps,\n\t\toptionalLines: verificationSteps,\n\t\toptionalNote: flow.optionalOnboarding.note,\n\t\toptionalTitle: \"Verify and sync (optional):\",\n\t\tpreambleLines: flow.result.selectedVariant\n\t\t\t? [`Template variant: ${flow.result.selectedVariant}`]\n\t\t\t: undefined,\n\t\tsummaryLines: [`Project directory: ${flow.projectDir}`],\n\t\ttitle: `✅ Created ${flow.result.variables.title} in ${flow.projectDir}`,\n\t\twarningLines: flow.result.warnings,\n\t};\n}\n\n/**\n * Builds the completion payload shown after a dry-run create flow succeeds.\n *\n * @param flow Resolved create-flow data including the non-mutating scaffold plan.\n * @returns A structured alternate-buffer completion payload.\n */\nexport function buildCreateDryRunPayload(flow: {\n\tpackageManager: string;\n\tplan: {\n\t\tdependencyInstall: \"skipped-by-flag\" | \"would-install\";\n\t\tfiles: string[];\n\t};\n\tprojectDir: string;\n\tresult: {\n\t\tselectedVariant?: string | null;\n\t\ttemplateId: string;\n\t\tvariables: {\n\t\t\ttitle: string;\n\t\t};\n\t\twarnings: string[];\n\t};\n}): AlternateBufferCompletionPayload {\n\tlet dependencyInstallLine: string;\n\tswitch (flow.plan.dependencyInstall) {\n\t\tcase \"skipped-by-flag\":\n\t\t\tdependencyInstallLine = \"Dependency install: already skipped via --no-install\";\n\t\t\tbreak;\n\t\tcase \"would-install\":\n\t\t\tdependencyInstallLine = \"Dependency install: would run during a real scaffold\";\n\t\t\tbreak;\n\t}\n\n\treturn {\n\t\toptionalLines: flow.plan.files.map((relativePath) => `write ${relativePath}`),\n\t\toptionalNote:\n\t\t\t\"No files were written because --dry-run was enabled. Re-run without --dry-run to materialize this scaffold.\",\n\t\toptionalTitle: `Planned files (${flow.plan.files.length}):`,\n\t\tpreambleLines: flow.result.selectedVariant\n\t\t\t? [`Template variant: ${flow.result.selectedVariant}`]\n\t\t\t: undefined,\n\t\tsummaryLines: [\n\t\t\t`Project directory: ${flow.projectDir}`,\n\t\t\t`Template: ${flow.result.templateId}`,\n\t\t\t`Package manager: ${flow.packageManager}`,\n\t\t\tdependencyInstallLine,\n\t\t],\n\t\ttitle: `🧪 Dry run for ${flow.result.variables.title} at ${flow.projectDir}`,\n\t\twarningLines: flow.result.warnings,\n\t};\n}\n\nfunction inferProjectPackageManager(projectDir: string): PackageManagerId {\n\ttry {\n\t\tconst packageJsonPath = path.join(projectDir, \"package.json\");\n\t\tif (fs.existsSync(packageJsonPath)) {\n\t\t\tconst manifest = JSON.parse(fs.readFileSync(packageJsonPath, \"utf8\")) as {\n\t\t\t\tpackageManager?: string;\n\t\t\t};\n\t\t\tif (manifest.packageManager?.startsWith(\"bun@\")) return \"bun\";\n\t\t\tif (manifest.packageManager?.startsWith(\"pnpm@\")) return \"pnpm\";\n\t\t\tif (manifest.packageManager?.startsWith(\"yarn@\")) return \"yarn\";\n\t\t\tif (manifest.packageManager?.startsWith(\"npm@\")) return \"npm\";\n\t\t}\n\t} catch {}\n\n\tif (\n\t\tfs.existsSync(path.join(projectDir, \"bun.lock\")) ||\n\t\tfs.existsSync(path.join(projectDir, \"bun.lockb\"))\n\t) {\n\t\treturn \"bun\";\n\t}\n\tif (fs.existsSync(path.join(projectDir, \"pnpm-lock.yaml\"))) {\n\t\treturn \"pnpm\";\n\t}\n\tif (\n\t\tfs.existsSync(path.join(projectDir, \"yarn.lock\")) ||\n\t\tfs.existsSync(path.join(projectDir, \".yarnrc.yml\"))\n\t) {\n\t\treturn \"yarn\";\n\t}\n\n\treturn \"npm\";\n}\n\n/**\n * Builds the completion payload shown after a migrate command succeeds.\n *\n * @param options Completed migrate command metadata plus rendered lines.\n * @returns A structured alternate-buffer completion payload.\n */\nexport function buildMigrationCompletionPayload(options: {\n\tcommand: string;\n\tlines: string[];\n}): AlternateBufferCompletionPayload {\n\tconst summaryLines = options.lines.filter((line) => line.trim().length > 0);\n\n\treturn {\n\t\tsummaryLines,\n\t\ttitle: `✅ Completed wp-typia migrate ${options.command}`,\n\t};\n}\n\n/**\n * Builds the completion payload shown after an add flow succeeds.\n *\n * @param options Add-flow kind plus normalized values to summarize.\n * @returns A structured alternate-buffer completion payload.\n */\nexport function buildAddCompletionPayload(options: {\n\tkind:\n\t\t| \"binding-source\"\n\t\t| \"block\"\n\t\t| \"editor-plugin\"\n\t\t| \"hooked-block\"\n\t\t| \"pattern\"\n\t\t| \"rest-resource\"\n\t\t| \"variation\";\n\tpackageManager?: PackageManagerId;\n\tprojectDir: string;\n\tvalues: Record<string, string>;\n\twarnings?: string[];\n}): AlternateBufferCompletionPayload {\n\tconst verificationLines = [\n\t\tformatPackageExecCommand(\n\t\t\toptions.packageManager ?? inferProjectPackageManager(options.projectDir),\n\t\t\t`wp-typia@${packageJson.version}`,\n\t\t\t\"doctor\",\n\t\t),\n\t];\n\tconst verificationNote =\n\t\t\"Run doctor via your package manager for a quick inventory and generated-artifact check after the add workflow.\";\n\n\tswitch (options.kind) {\n\t\tcase \"variation\":\n\t\t\treturn {\n\t\t\t\tnextSteps: [\n\t\t\t\t\t`Review src/blocks/${options.values.blockSlug}/variations/${options.values.variationSlug}.ts.`,\n\t\t\t\t\t\"Run your workspace build or dev command to pick up the new variation.\",\n\t\t\t\t],\n\t\t\t\toptionalLines: verificationLines,\n\t\t\t\toptionalNote: verificationNote,\n\t\t\t\toptionalTitle: \"Verify workspace health (optional):\",\n\t\t\t\tsummaryLines: [\n\t\t\t\t\t`Variation: ${options.values.variationSlug}`,\n\t\t\t\t\t`Target block: ${options.values.blockSlug}`,\n\t\t\t\t\t`Project directory: ${options.projectDir}`,\n\t\t\t\t],\n\t\t\t\ttitle: \"✅ Added workspace variation\",\n\t\t\t};\n\t\tcase \"pattern\":\n\t\t\treturn {\n\t\t\t\tnextSteps: [\n\t\t\t\t\t`Review src/patterns/${options.values.patternSlug}.php.`,\n\t\t\t\t\t\"Run your workspace build or dev command to verify the new pattern registration.\",\n\t\t\t\t],\n\t\t\t\toptionalLines: verificationLines,\n\t\t\t\toptionalNote: verificationNote,\n\t\t\t\toptionalTitle: \"Verify workspace health (optional):\",\n\t\t\t\tsummaryLines: [\n\t\t\t\t\t`Pattern: ${options.values.patternSlug}`,\n\t\t\t\t\t`Project directory: ${options.projectDir}`,\n\t\t\t\t],\n\t\t\t\ttitle: \"✅ Added workspace pattern\",\n\t\t\t};\n\t\tcase \"binding-source\":\n\t\t\treturn {\n\t\t\t\tnextSteps: [\n\t\t\t\t\t`Review src/bindings/${options.values.bindingSourceSlug}/server.php and src/bindings/${options.values.bindingSourceSlug}/editor.ts.`,\n\t\t\t\t\t\"Run your workspace build or dev command to verify the binding source hooks and editor registration.\",\n\t\t\t\t],\n\t\t\t\toptionalLines: verificationLines,\n\t\t\t\toptionalNote: verificationNote,\n\t\t\t\toptionalTitle: \"Verify workspace health (optional):\",\n\t\t\t\tsummaryLines: [\n\t\t\t\t\t`Binding source: ${options.values.bindingSourceSlug}`,\n\t\t\t\t\t`Project directory: ${options.projectDir}`,\n\t\t\t\t],\n\t\t\t\ttitle: \"✅ Added binding source\",\n\t\t\t};\n\t\tcase \"rest-resource\":\n\t\t\treturn {\n\t\t\t\tnextSteps: [\n\t\t\t\t\t`Review src/rest/${options.values.restResourceSlug}/ and inc/rest/${options.values.restResourceSlug}.php.`,\n\t\t\t\t\t\"Run your workspace build or dev command to verify the generated REST resource contract.\",\n\t\t\t\t],\n\t\t\t\toptionalLines: verificationLines,\n\t\t\t\toptionalNote: verificationNote,\n\t\t\t\toptionalTitle: \"Verify workspace health (optional):\",\n\t\t\t\tsummaryLines: [\n\t\t\t\t\t`REST resource: ${options.values.restResourceSlug}`,\n\t\t\t\t\t`Namespace: ${options.values.namespace}`,\n\t\t\t\t\t`Methods: ${options.values.methods}`,\n\t\t\t\t\t`Project directory: ${options.projectDir}`,\n\t\t\t\t],\n\t\t\t\ttitle: \"✅ Added plugin-level REST resource\",\n\t\t\t};\n\t\tcase \"editor-plugin\":\n\t\t\treturn {\n\t\t\t\tnextSteps: [\n\t\t\t\t\t`Review src/editor-plugins/${options.values.editorPluginSlug}/.`,\n\t\t\t\t\t\"Run your workspace build or dev command to verify the new editor plugin registration.\",\n\t\t\t\t],\n\t\t\t\toptionalLines: verificationLines,\n\t\t\t\toptionalNote: verificationNote,\n\t\t\t\toptionalTitle: \"Verify workspace health (optional):\",\n\t\t\t\tsummaryLines: [\n\t\t\t\t\t`Editor plugin: ${options.values.editorPluginSlug}`,\n\t\t\t\t\t`Slot: ${options.values.slot}`,\n\t\t\t\t\t`Project directory: ${options.projectDir}`,\n\t\t\t\t],\n\t\t\t\ttitle: \"✅ Added editor plugin\",\n\t\t\t};\n\t\tcase \"hooked-block\":\n\t\t\treturn {\n\t\t\t\tnextSteps: [\n\t\t\t\t\t`Review src/blocks/${options.values.blockSlug}/block.json for the new blockHooks entry.`,\n\t\t\t\t\t\"Run your workspace build or dev command to verify the updated hooked-block metadata.\",\n\t\t\t\t],\n\t\t\t\toptionalLines: verificationLines,\n\t\t\t\toptionalNote: verificationNote,\n\t\t\t\toptionalTitle: \"Verify workspace health (optional):\",\n\t\t\t\tsummaryLines: [\n\t\t\t\t\t`Block: ${options.values.blockSlug}`,\n\t\t\t\t\t`Anchor: ${options.values.anchorBlockName}`,\n\t\t\t\t\t`Position: ${options.values.position}`,\n\t\t\t\t\t`Project directory: ${options.projectDir}`,\n\t\t\t\t],\n\t\t\t\ttitle: \"✅ Added blockHooks metadata\",\n\t\t\t};\n\t\tdefault:\n\t\t\treturn {\n\t\t\t\tnextSteps: [\n\t\t\t\t\t\"Review the generated sources under src/blocks/ and the updated scripts/block-config.ts entry.\",\n\t\t\t\t\t\"Run your workspace build or dev command to verify the new scaffolded block family.\",\n\t\t\t\t],\n\t\t\t\toptionalLines: verificationLines,\n\t\t\t\toptionalNote: verificationNote,\n\t\t\t\toptionalTitle: \"Verify workspace health (optional):\",\n\t\t\t\tsummaryLines: [\n\t\t\t\t\t`Blocks: ${options.values.blockSlugs}`,\n\t\t\t\t\t`Template family: ${options.values.templateId}`,\n\t\t\t\t\t`Project directory: ${options.projectDir}`,\n\t\t\t\t],\n\t\t\t\ttitle: \"✅ Added workspace block\",\n\t\t\t\twarningLines: options.warnings,\n\t\t\t};\n\t}\n}\n\n/**\n * Builds the completion payload shown after a dry-run add flow succeeds.\n *\n * @param options Existing add completion metadata plus the planned file updates.\n * @returns A structured alternate-buffer completion payload.\n */\nexport function buildAddDryRunPayload(options: {\n\tcompletion: AlternateBufferCompletionPayload;\n\tfileOperations: string[];\n}): AlternateBufferCompletionPayload {\n\tconst normalizedTitle = options.completion.title.replace(/^✅\\s*Added\\s*/u, \"\");\n\n\treturn {\n\t\toptionalLines: options.fileOperations,\n\t\toptionalNote:\n\t\t\t\"No workspace files were changed because --dry-run was enabled. Re-run without --dry-run to apply this add command.\",\n\t\toptionalTitle: `Planned workspace updates (${options.fileOperations.length}):`,\n\t\tpreambleLines: options.completion.preambleLines,\n\t\tsummaryLines: options.completion.summaryLines,\n\t\ttitle: `🧪 Dry run for ${normalizedTitle || \"workspace add command\"}`,\n\t\twarningLines: options.completion.warningLines,\n\t};\n}\n\n/**\n * Prints a block of text lines using a shared line printer.\n *\n * @param lines Lines to print in order.\n * @param printLine Line printer to use.\n * @returns Nothing.\n */\nexport function printBlock(lines: string[], printLine: PrintLine): void {\n\tfor (const line of lines) {\n\t\tprintLine(line);\n\t}\n}\n\nfunction formatExternalLayerSelectHint(option: ExternalLayerSelectOption): string | undefined {\n\tconst details = [\n\t\toption.description,\n\t\toption.extends.length > 0 ? `extends ${option.extends.join(\", \")}` : undefined,\n\t].filter((value): value is string => typeof value === \"string\" && value.length > 0);\n\n\treturn details.length > 0 ? details.join(\" · \") : undefined;\n}\n\n/**\n * Converts external layer options into prompt-compatible select items.\n *\n * @param options External layer options returned by the block generator.\n * @returns Prompt select options with labels and hints.\n */\nexport function toExternalLayerPromptOptions(options: ExternalLayerSelectOption[]) {\n\treturn options.map((option) => ({\n\t\thint: formatExternalLayerSelectHint(option),\n\t\tlabel: option.id,\n\t\tvalue: option.id,\n\t}));\n}\n",
|
|
9
|
+
"type TtyLike = {\n\tisTTY?: boolean;\n};\n\ntype RuntimeCapabilityOptions = {\n\thasBunRuntime?: boolean;\n\tstdin?: TtyLike | null | undefined;\n\tstdout?: TtyLike | null | undefined;\n\tterm?: string | undefined;\n};\n\n/**\n * Detect whether the current runtime can safely prompt on an interactive terminal.\n */\nexport function isInteractiveTerminal({\n\tstdin = process.stdin,\n\tstdout = process.stdout,\n\tterm = process.env.TERM,\n}: RuntimeCapabilityOptions = {}): boolean {\n\treturn Boolean(stdin?.isTTY) && Boolean(stdout?.isTTY) && term !== \"dumb\";\n}\n\n/**\n * Detect whether the Bun-powered TUI runtime should be enabled for this process.\n */\nexport function supportsInteractiveTui(\n\toptions: RuntimeCapabilityOptions = {},\n): boolean {\n\tconst hasBunRuntime =\n\t\toptions.hasBunRuntime ?? typeof Bun !== \"undefined\";\n\treturn hasBunRuntime && isInteractiveTerminal(options);\n}\n",
|
|
10
|
+
"import { spawnSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\ntype PackageManagerId = \"bun\" | \"npm\" | \"pnpm\" | \"yarn\";\n\ntype SyncExecutionInput = {\n\tcheck?: boolean;\n\tcwd: string;\n};\n\ntype SyncProjectContext = {\n\tcwd: string;\n\tpackageJsonPath: string;\n\tpackageManager: PackageManagerId;\n\tscripts: Partial<Record<\"sync\" | \"sync-rest\" | \"sync-types\", string>>;\n};\n\nconst SYNC_INSTALL_MARKERS = [\"node_modules\", \".pnp.cjs\", \".pnp.loader.mjs\"] as const;\nconst LOCAL_SYNC_TOOL_PATTERN = /(^|[\\s;&|()])(?:tsx|wp-scripts)(?=($|[\\s;&|()]))/u;\n\nfunction formatRunScript(\n\tpackageManagerId: PackageManagerId,\n\tscriptName: string,\n\textraArgs = \"\",\n) {\n\tconst args = extraArgs.trim();\n\tif (packageManagerId === \"bun\") {\n\t\treturn args ? `bun run ${scriptName} ${args}` : `bun run ${scriptName}`;\n\t}\n\tif (packageManagerId === \"npm\") {\n\t\treturn args ? `npm run ${scriptName} -- ${args}` : `npm run ${scriptName}`;\n\t}\n\tif (packageManagerId === \"pnpm\") {\n\t\treturn args ? `pnpm run ${scriptName} ${args}` : `pnpm run ${scriptName}`;\n\t}\n\treturn args ? `yarn run ${scriptName} ${args}` : `yarn run ${scriptName}`;\n}\n\nfunction formatInstallCommand(packageManagerId: PackageManagerId): string {\n\tswitch (packageManagerId) {\n\t\tcase \"bun\":\n\t\t\treturn \"bun install\";\n\t\tcase \"pnpm\":\n\t\t\treturn \"pnpm install\";\n\t\tcase \"yarn\":\n\t\t\treturn \"yarn install\";\n\t\tdefault:\n\t\t\treturn \"npm install\";\n\t}\n}\n\nfunction getSyncRootError(cwd: string): Error {\n\treturn new Error(\n\t\t`No generated wp-typia project root was found at ${cwd}. Run \\`wp-typia sync\\` from a scaffolded project or official workspace root that already contains generated sync scripts. If you expected this directory to work, cd into the scaffold root first or rerun the scaffold before syncing.`,\n\t);\n}\n\nfunction inferSyncPackageManager(cwd: string, packageManagerField?: string): PackageManagerId {\n\tconst field = String(packageManagerField ?? \"\");\n\tif (field.startsWith(\"bun@\")) return \"bun\";\n\tif (field.startsWith(\"npm@\")) return \"npm\";\n\tif (field.startsWith(\"pnpm@\")) return \"pnpm\";\n\tif (field.startsWith(\"yarn@\")) return \"yarn\";\n\n\tif (\n\t\tfs.existsSync(path.join(cwd, \"bun.lock\")) ||\n\t\tfs.existsSync(path.join(cwd, \"bun.lockb\"))\n\t) {\n\t\treturn \"bun\";\n\t}\n\tif (fs.existsSync(path.join(cwd, \"pnpm-lock.yaml\"))) {\n\t\treturn \"pnpm\";\n\t}\n\tif (\n\t\tfs.existsSync(path.join(cwd, \"yarn.lock\")) ||\n\t\tfs.existsSync(path.join(cwd, \".pnp.cjs\")) ||\n\t\tfs.existsSync(path.join(cwd, \".pnp.loader.mjs\")) ||\n\t\tfs.existsSync(path.join(cwd, \".yarnrc.yml\"))\n\t) {\n\t\treturn \"yarn\";\n\t}\n\tif (\n\t\tfs.existsSync(path.join(cwd, \"package-lock.json\")) ||\n\t\tfs.existsSync(path.join(cwd, \"npm-shrinkwrap.json\"))\n\t) {\n\t\treturn \"npm\";\n\t}\n\n\treturn \"npm\";\n}\n\nfunction resolveSyncProjectContext(cwd: string): SyncProjectContext {\n\tconst packageJsonPath = path.join(cwd, \"package.json\");\n\tif (!fs.existsSync(packageJsonPath)) {\n\t\tthrow getSyncRootError(cwd);\n\t}\n\n\tconst packageJson = JSON.parse(fs.readFileSync(packageJsonPath, \"utf8\")) as {\n\t\tpackageManager?: string;\n\t\tscripts?: Record<string, unknown>;\n\t};\n\tconst scripts = packageJson.scripts ?? {};\n\tconst syncScripts = {\n\t\tsync: typeof scripts.sync === \"string\" ? scripts.sync : undefined,\n\t\t\"sync-rest\":\n\t\t\ttypeof scripts[\"sync-rest\"] === \"string\" ? scripts[\"sync-rest\"] : undefined,\n\t\t\"sync-types\":\n\t\t\ttypeof scripts[\"sync-types\"] === \"string\" ? scripts[\"sync-types\"] : undefined,\n\t} satisfies SyncProjectContext[\"scripts\"];\n\n\tif (!syncScripts.sync && !syncScripts[\"sync-types\"]) {\n\t\tthrow new Error(\n\t\t\t`Expected ${packageJsonPath} to define either a \\`sync\\` or \\`sync-types\\` script.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tcwd,\n\t\tpackageJsonPath,\n\t\tpackageManager: inferSyncPackageManager(cwd, packageJson.packageManager),\n\t\tscripts: syncScripts,\n\t};\n}\n\nfunction findInstalledDependencyMarkerDir(projectDir: string): string | null {\n\tlet currentDir = path.resolve(projectDir);\n\n\twhile (true) {\n\t\tif (\n\t\t\tSYNC_INSTALL_MARKERS.some((marker) =>\n\t\t\t\tfs.existsSync(path.join(currentDir, marker)),\n\t\t\t)\n\t\t) {\n\t\t\treturn currentDir;\n\t\t}\n\n\t\tconst parentDir = path.dirname(currentDir);\n\t\tif (parentDir === currentDir) {\n\t\t\treturn null;\n\t\t}\n\t\tcurrentDir = parentDir;\n\t}\n}\n\nfunction scriptsLikelyNeedInstalledDependencies(project: SyncProjectContext): boolean {\n\tconst candidateScripts = project.scripts.sync\n\t\t? [project.scripts.sync]\n\t\t: [project.scripts[\"sync-types\"], project.scripts[\"sync-rest\"]];\n\n\treturn candidateScripts.some(\n\t\t(script): script is string =>\n\t\t\ttypeof script === \"string\" && LOCAL_SYNC_TOOL_PATTERN.test(script),\n\t);\n}\n\nfunction assertSyncDependenciesInstalled(project: SyncProjectContext): void {\n\tif (!scriptsLikelyNeedInstalledDependencies(project)) {\n\t\treturn;\n\t}\n\tconst markerDir = findInstalledDependencyMarkerDir(project.cwd);\n\tif (markerDir) {\n\t\treturn;\n\t}\n\n\tthrow new Error(\n\t\t`Project dependencies have not been installed yet. Run \\`${formatInstallCommand(project.packageManager)}\\` from the project root before \\`wp-typia sync\\`. The generated sync scripts rely on local tools such as \\`tsx\\`.`,\n\t);\n}\n\nfunction getPackageManagerRunInvocation(\n\tpackageManager: PackageManagerId,\n\tscriptName: string,\n\textraArgs: string[],\n): { args: string[]; command: string } {\n\tswitch (packageManager) {\n\t\tcase \"bun\":\n\t\t\treturn { args: [\"run\", scriptName, ...extraArgs], command: \"bun\" };\n\t\tcase \"npm\":\n\t\t\treturn {\n\t\t\t\targs: [\"run\", scriptName, ...(extraArgs.length > 0 ? [\"--\", ...extraArgs] : [])],\n\t\t\t\tcommand: \"npm\",\n\t\t\t};\n\t\tcase \"pnpm\":\n\t\t\treturn { args: [\"run\", scriptName, ...extraArgs], command: \"pnpm\" };\n\t\tcase \"yarn\":\n\t\t\treturn { args: [\"run\", scriptName, ...extraArgs], command: \"yarn\" };\n\t}\n}\n\nfunction runProjectScript(\n\tproject: SyncProjectContext,\n\tscriptName: \"sync\" | \"sync-rest\" | \"sync-types\",\n\textraArgs: string[],\n): void {\n\tconst script = project.scripts[scriptName];\n\tif (!script) {\n\t\treturn;\n\t}\n\n\tconst invocation = getPackageManagerRunInvocation(\n\t\tproject.packageManager,\n\t\tscriptName,\n\t\textraArgs,\n\t);\n\n\tconst result = spawnSync(invocation.command, invocation.args, {\n\t\tcwd: project.cwd,\n\t\tshell: process.platform === \"win32\",\n\t\tstdio: \"inherit\",\n\t});\n\n\tif (result.error || result.status !== 0) {\n\t\tthrow new Error(\n\t\t\t`\\`${formatRunScript(project.packageManager, scriptName, extraArgs.join(\" \"))}\\` failed.`,\n\t\t\t{\n\t\t\t\tcause: result.error,\n\t\t\t},\n\t\t);\n\t}\n}\n\n/**\n * Executes the generated-project sync flow through the local project scripts.\n *\n * @param options Sync execution options including cwd and optional `--check`.\n * @returns A promise that resolves after the relevant sync scripts complete.\n */\nexport async function executeSyncCommand({\n\tcheck = false,\n\tcwd,\n}: SyncExecutionInput): Promise<void> {\n\tconst project = resolveSyncProjectContext(cwd);\n\tassertSyncDependenciesInstalled(project);\n\tconst extraArgs = check ? [\"--check\"] : [];\n\n\tif (project.scripts.sync) {\n\t\trunProjectScript(project, \"sync\", extraArgs);\n\t\treturn;\n\t}\n\n\trunProjectScript(project, \"sync-types\", extraArgs);\n\n\tif (project.scripts[\"sync-rest\"]) {\n\t\trunProjectScript(project, \"sync-rest\", extraArgs);\n\t}\n}\n",
|
|
11
|
+
"import type { ReadlinePrompt } from \"@wp-typia/project-tools/cli-prompt\";\nimport { simulateWorkspaceAddDryRun } from \"./runtime-bridge-add-dry-run\";\nimport type { AlternateBufferCompletionPayload } from \"./ui/alternate-buffer-lifecycle\";\nimport {\n\tbuildAddCompletionPayload,\n\tbuildAddDryRunPayload,\n\tbuildCreateCompletionPayload,\n\tbuildCreateDryRunPayload,\n\tbuildMigrationCompletionPayload,\n\tformatCreateProgressLine,\n\tprintBlock,\n\tprintCompletionPayload,\n\ttoExternalLayerPromptOptions,\n} from \"./runtime-bridge-output\";\nimport { isInteractiveTerminal } from \"./runtime-capabilities\";\nexport {\n\tbuildCreateCompletionPayload,\n\tbuildCreateDryRunPayload,\n\tbuildMigrationCompletionPayload,\n\tformatCreateProgressLine,\n\tprintCompletionPayload,\n} from \"./runtime-bridge-output\";\nexport { executeSyncCommand } from \"./runtime-bridge-sync\";\n\ntype CreateProgressPayload = {\n\tdetail: string;\n\ttitle: string;\n};\n\ntype CreateExecutionInput = {\n\tprojectDir: string;\n\tcwd: string;\n\temitOutput?: boolean;\n\tflags: Record<string, unknown>;\n\tinteractive?: boolean;\n\tonProgress?: (payload: CreateProgressPayload) => void;\n\tprintLine?: PrintLine;\n\tprompt?: ReadlinePrompt;\n\twarnLine?: PrintLine;\n};\n\ntype AddExecutionInput = {\n\tcwd: string;\n\temitOutput?: boolean;\n\tflags: Record<string, unknown>;\n\tinteractive?: boolean;\n\tkind?: string;\n\tname?: string;\n\tprintLine?: PrintLine;\n\tprompt?: ReadlinePrompt;\n\twarnLine?: PrintLine;\n};\n\ntype TemplatesExecutionInput = {\n\tflags: {\n\t\tid?: string;\n\t\tsubcommand?: string;\n\t};\n};\n\ntype MigrateExecutionInput = {\n\tcommand?: string;\n\tcwd: string;\n\tflags: Record<string, unknown>;\n\tprompt?: ReadlinePrompt;\n\trenderLine?: (line: string) => void;\n};\n\ntype PrintLine = (line: string) => void;\ntype CliCommandId = \"add\" | \"create\" | \"doctor\" | \"migrate\";\n\nconst loadCliAddRuntime = () => import(\"@wp-typia/project-tools/cli-add\");\nconst loadCliDiagnosticsRuntime = () => import(\"@wp-typia/project-tools/cli-diagnostics\");\nconst loadCliDoctorRuntime = () => import(\"@wp-typia/project-tools/cli-doctor\");\nconst loadCliPromptRuntime = () => import(\"@wp-typia/project-tools/cli-prompt\");\nconst loadCliScaffoldRuntime = () => import(\"@wp-typia/project-tools/cli-scaffold\");\nconst loadCliTemplatesRuntime = () => import(\"@wp-typia/project-tools/cli-templates\");\nconst loadWorkspaceProjectRuntime = () => import(\"@wp-typia/project-tools/workspace-project\");\nconst loadMigrationsRuntime = () => import(\"@wp-typia/project-tools/migrations\");\n\nasync function wrapCliCommandError(command: CliCommandId, error: unknown) {\n\tconst { createCliCommandError } = await loadCliDiagnosticsRuntime();\n\treturn createCliCommandError({ command, error });\n}\n\nfunction shouldWrapCliCommandError(options: {\n\temitOutput?: boolean;\n\trenderLine?: ((line: string) => void) | undefined;\n}): boolean {\n\tif (options.emitOutput === false) {\n\t\treturn false;\n\t}\n\n\tif (options.renderLine) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nfunction readOptionalStringFlag(\n\tflags: Record<string, unknown>,\n\tname: string,\n): string | undefined {\n\tconst value = flags[name];\n\tif (value === undefined || value === null) {\n\t\treturn undefined;\n\t}\n\tif (typeof value !== \"string\" || value.trim().length === 0) {\n\t\tthrow new Error(`\\`--${name}\\` requires a value.`);\n\t}\n\treturn value;\n}\n\nfunction readOptionalLooseStringFlag(\n\tflags: Record<string, unknown>,\n\tname: string,\n): string | undefined {\n\tconst value = flags[name];\n\tif (value === undefined || value === null) {\n\t\treturn undefined;\n\t}\n\tif (typeof value !== \"string\") {\n\t\tthrow new Error(`\\`--${name}\\` requires a value.`);\n\t}\n\tconst trimmed = value.trim();\n\treturn trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction pushFlag(argv: string[], name: string, value: unknown): void {\n\tif (value === undefined || value === null || value === false) {\n\t\treturn;\n\t}\n\tif (value === true) {\n\t\targv.push(`--${name}`);\n\t\treturn;\n\t}\n\targv.push(`--${name}`, String(value));\n}\n\nasync function executeWorkspaceAddWithOptionalDryRun<TResult>(options: {\n\tbuildCompletion: (\n\t\tresult: TResult,\n\t) => ReturnType<typeof buildAddCompletionPayload>;\n\tcwd: string;\n\tdryRun: boolean;\n\temitOutput: boolean | undefined;\n\texecute: (cwd: string) => Promise<TResult>;\n\tprintLine: PrintLine;\n\twarnLine?: PrintLine;\n}): Promise<AlternateBufferCompletionPayload | void> {\n\tconst simulated = options.dryRun\n\t\t? await simulateWorkspaceAddDryRun({\n\t\t\t\tcwd: options.cwd,\n\t\t\t\texecute: options.execute,\n\t\t\t})\n\t\t: null;\n\tconst result = simulated?.result ?? (await options.execute(options.cwd));\n\tconst completion = options.buildCompletion(result);\n\n\tif (!options.dryRun) {\n\t\treturn emitCompletion(completion, {\n\t\t\temitOutput: options.emitOutput ?? true,\n\t\t\tprintLine: options.printLine,\n\t\t\twarnLine: options.warnLine,\n\t\t});\n\t}\n\n\treturn emitCompletion(\n\t\tbuildAddDryRunPayload({\n\t\t\tcompletion,\n\t\t\tfileOperations: simulated!.fileOperations,\n\t\t}),\n\t\t{\n\t\t\temitOutput: options.emitOutput ?? true,\n\t\t\tprintLine: options.printLine,\n\t\t\twarnLine: options.warnLine,\n\t\t},\n\t);\n}\n\nconst PACKAGE_MANAGER_PROMPT_OPTIONS = [\n\t{ label: \"npm\", value: \"npm\", hint: \"Use npm\" },\n\t{ label: \"pnpm\", value: \"pnpm\", hint: \"Use pnpm\" },\n\t{ label: \"yarn\", value: \"yarn\", hint: \"Use yarn\" },\n\t{ label: \"bun\", value: \"bun\", hint: \"Use bun\" },\n] as const;\n\nconst DATA_STORAGE_PROMPT_OPTIONS = [\n\t{ label: \"custom-table\", value: \"custom-table\", hint: \"Dedicated custom table storage\" },\n\t{ label: \"post-meta\", value: \"post-meta\", hint: \"Persist through post meta\" },\n] as const;\n\nconst PERSISTENCE_POLICY_PROMPT_OPTIONS = [\n\t{ label: \"authenticated\", value: \"authenticated\", hint: \"Authenticated write policy\" },\n\t{ label: \"public\", value: \"public\", hint: \"Public token policy\" },\n] as const;\n\nconst BOOLEAN_PROMPT_OPTIONS = [\n\t{ label: \"Yes\", value: \"yes\", hint: \"Enable this option\" },\n\t{ label: \"No\", value: \"no\", hint: \"Keep the default disabled state\" },\n] as const;\n\nfunction emitCompletion(\n\tpayload: AlternateBufferCompletionPayload,\n\toptions: {\n\t\temitOutput: boolean;\n\t\tprintLine: PrintLine;\n\t\twarnLine?: PrintLine;\n\t},\n): AlternateBufferCompletionPayload {\n\tif (options.emitOutput) {\n\t\tprintCompletionPayload(payload, {\n\t\t\tprintLine: options.printLine,\n\t\t\twarnLine: options.warnLine,\n\t\t});\n\t}\n\n\treturn payload;\n}\n\nexport async function executeCreateCommand({\n\tprojectDir,\n\tcwd,\n\temitOutput = true,\n\tflags,\n\tinteractive,\n\tonProgress,\n\tprintLine = console.log as PrintLine,\n\tprompt,\n\twarnLine = console.warn as PrintLine,\n}: CreateExecutionInput): Promise<AlternateBufferCompletionPayload> {\n\tconst [\n\t\t{ createReadlinePrompt },\n\t\t{ runScaffoldFlow },\n\t\t{ getTemplateSelectOptions },\n\t] = await Promise.all([\n\t\tloadCliPromptRuntime(),\n\t\tloadCliScaffoldRuntime(),\n\t\tloadCliTemplatesRuntime(),\n\t]);\n\tconst shouldPrompt =\n\t\tinteractive ?? (!Boolean(flags.yes) && isInteractiveTerminal());\n\tconst activePrompt = shouldPrompt ? (prompt ?? createReadlinePrompt()) : undefined;\n\tconst shouldPromptForExternalLayerSelection =\n\t\tBoolean(activePrompt) && activePrompt !== prompt;\n\tconst effectiveYes = Boolean(flags.yes) || (Boolean(flags[\"dry-run\"]) && !Boolean(activePrompt));\n\n\ttry {\n\t\tconst flow = await runScaffoldFlow({\n\t\t\talternateRenderTargets: readOptionalLooseStringFlag(\n\t\t\t\tflags,\n\t\t\t\t\"alternate-render-targets\",\n\t\t\t),\n\t\t\tcwd,\n\t\t\tdataStorageMode: readOptionalLooseStringFlag(flags, \"data-storage\"),\n\t\t\tdryRun: Boolean(flags[\"dry-run\"]),\n\t\t\texternalLayerId: readOptionalLooseStringFlag(flags, \"external-layer-id\"),\n\t\t\texternalLayerSource: readOptionalLooseStringFlag(flags, \"external-layer-source\"),\n\t\t\tinnerBlocksPreset: readOptionalLooseStringFlag(flags, \"inner-blocks-preset\"),\n\t\t\tisInteractive: Boolean(activePrompt),\n\t\t\tnamespace: readOptionalLooseStringFlag(flags, \"namespace\"),\n\t\t\tnoInstall: Boolean(flags[\"no-install\"]),\n\t\t\tpackageManager: readOptionalLooseStringFlag(flags, \"package-manager\"),\n\t\t\tpersistencePolicy: readOptionalLooseStringFlag(flags, \"persistence-policy\"),\n\t\t\tphpPrefix: readOptionalLooseStringFlag(flags, \"php-prefix\"),\n\t\t\tprojectInput: projectDir,\n\t\t\tonProgress: async (progress) => {\n\t\t\t\tconst payload = {\n\t\t\t\t\tdetail: progress.detail,\n\t\t\t\t\ttitle: progress.title,\n\t\t\t\t};\n\t\t\t\tonProgress?.(payload);\n\t\t\t\tif (emitOutput) {\n\t\t\t\t\tprintLine(formatCreateProgressLine(payload));\n\t\t\t\t}\n\t\t\t},\n\t\t\tpromptText: activePrompt\n\t\t\t\t? (message, defaultValue, validate) => activePrompt.text(message, defaultValue, validate)\n\t\t\t\t: undefined,\n\t\t\tqueryPostType: readOptionalLooseStringFlag(flags, \"query-post-type\"),\n\t\t\tselectDataStorage: activePrompt\n\t\t\t\t? () => activePrompt.select(\"Select a data storage mode\", [...DATA_STORAGE_PROMPT_OPTIONS], 1)\n\t\t\t\t: undefined,\n\t\t\tselectExternalLayerId: shouldPromptForExternalLayerSelection && activePrompt\n\t\t\t\t? (options) =>\n\t\t\t\t\t\tactivePrompt.select(\n\t\t\t\t\t\t\t\"Select an external layer\",\n\t\t\t\t\t\t\ttoExternalLayerPromptOptions(options),\n\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t)\n\t\t\t\t: undefined,\n\t\t\tselectPackageManager: activePrompt\n\t\t\t\t? () => activePrompt.select(\"Select a package manager\", [...PACKAGE_MANAGER_PROMPT_OPTIONS], 1)\n\t\t\t\t: undefined,\n\t\t\tselectPersistencePolicy: activePrompt\n\t\t\t\t? () =>\n\t\t\t\t\t\tactivePrompt.select(\n\t\t\t\t\t\t\t\"Select a persistence policy\",\n\t\t\t\t\t\t\t[...PERSISTENCE_POLICY_PROMPT_OPTIONS],\n\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t)\n\t\t\t\t: undefined,\n\t\t\tselectTemplate: activePrompt\n\t\t\t\t? () => activePrompt.select(\"Select a template\", getTemplateSelectOptions(), 1)\n\t\t\t\t: undefined,\n\t\t\tselectWithMigrationUi: activePrompt\n\t\t\t\t? async () =>\n\t\t\t\t\t\t(await activePrompt.select(\"Enable migration UI support?\", [...BOOLEAN_PROMPT_OPTIONS], 2)) ===\n\t\t\t\t\t\t\"yes\"\n\t\t\t\t: undefined,\n\t\t\tselectWithTestPreset: activePrompt\n\t\t\t\t? async () =>\n\t\t\t\t\t\t(await activePrompt.select(\"Include the Playwright test preset?\", [...BOOLEAN_PROMPT_OPTIONS], 2)) ===\n\t\t\t\t\t\t\"yes\"\n\t\t\t\t: undefined,\n\t\t\tselectWithWpEnv: activePrompt\n\t\t\t\t? async () =>\n\t\t\t\t\t\t(await activePrompt.select(\"Include a local wp-env preset?\", [...BOOLEAN_PROMPT_OPTIONS], 2)) ===\n\t\t\t\t\t\t\"yes\"\n\t\t\t\t: undefined,\n\t\t\ttemplateId: readOptionalLooseStringFlag(flags, \"template\"),\n\t\t\ttextDomain: readOptionalLooseStringFlag(flags, \"text-domain\"),\n\t\t\tvariant: readOptionalLooseStringFlag(flags, \"variant\"),\n\t\t\twithMigrationUi: flags[\"with-migration-ui\"] as boolean | undefined,\n\t\t\twithTestPreset: flags[\"with-test-preset\"] as boolean | undefined,\n\t\t\twithWpEnv: flags[\"with-wp-env\"] as boolean | undefined,\n\t\t\tyes: effectiveYes,\n\t\t});\n\n\t\tconst payload = flow.dryRun && flow.plan\n\t\t\t? buildCreateDryRunPayload({\n\t\t\t\t\tpackageManager: flow.packageManager,\n\t\t\t\t\tplan: flow.plan,\n\t\t\t\t\tprojectDir: flow.projectDir,\n\t\t\t\t\tresult: flow.result,\n\t\t\t\t})\n\t\t\t: buildCreateCompletionPayload(flow);\n\t\treturn emitCompletion(payload, { emitOutput, printLine, warnLine });\n\t} catch (error) {\n\t\tif (!shouldWrapCliCommandError({ emitOutput })) {\n\t\t\tthrow error;\n\t\t}\n\t\tthrow await wrapCliCommandError(\"create\", error);\n\t} finally {\n\t\tif (activePrompt && activePrompt !== prompt) {\n\t\t\tactivePrompt.close();\n\t\t}\n\t}\n}\n\nexport async function executeAddCommand({\n\tcwd,\n\temitOutput = true,\n\tflags,\n\tinteractive,\n\tkind,\n\tname,\n\tprintLine = console.log as PrintLine,\n\tprompt,\n\twarnLine = console.warn as PrintLine,\n}: AddExecutionInput): Promise<AlternateBufferCompletionPayload | void> {\n\tlet activePrompt: ReadlinePrompt | undefined;\n\tconst dryRun = Boolean(flags[\"dry-run\"]);\n\n\ttry {\n\t\tconst addRuntime = await loadCliAddRuntime();\n\n\t\tif (!kind) {\n\t\t\tprintLine(addRuntime.formatAddHelpText());\n\t\t\tthrow new Error(\n\t\t\t\t\"`wp-typia add` requires <kind>. Usage: wp-typia add <block|variation|pattern|binding-source|rest-resource|editor-plugin|hooked-block> ...\",\n\t\t\t);\n\t\t}\n\n\t\tif (kind === \"variation\") {\n\t\t\tif (!name) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"`wp-typia add variation` requires <name>. Usage: wp-typia add variation <name> --block <block-slug>\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst blockSlug = readOptionalStringFlag(flags, \"block\");\n\t\t\tif (!blockSlug) {\n\t\t\t\tthrow new Error(\"`wp-typia add variation` requires --block <block-slug>.\");\n\t\t\t}\n\n\t\t\treturn executeWorkspaceAddWithOptionalDryRun<\n\t\t\t\tAwaited<ReturnType<typeof addRuntime.runAddVariationCommand>>\n\t\t\t>({\n\t\t\t\tbuildCompletion: (result) =>\n\t\t\t\t\tbuildAddCompletionPayload({\n\t\t\t\t\t\tkind: \"variation\",\n\t\t\t\t\t\tprojectDir: result.projectDir,\n\t\t\t\t\t\tvalues: {\n\t\t\t\t\t\t\tblockSlug: result.blockSlug,\n\t\t\t\t\t\t\tvariationSlug: result.variationSlug,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\tcwd,\n\t\t\t\tdryRun,\n\t\t\t\temitOutput,\n\t\t\t\texecute: (targetCwd) =>\n\t\t\t\t\taddRuntime.runAddVariationCommand({\n\t\t\t\t\t\tblockName: blockSlug,\n\t\t\t\t\t\tcwd: targetCwd,\n\t\t\t\t\t\tvariationName: name,\n\t\t\t\t\t}),\n\t\t\t\tprintLine,\n\t\t\t});\n\t\t}\n\n\t\tif (kind === \"pattern\") {\n\t\t\tif (!name) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"`wp-typia add pattern` requires <name>. Usage: wp-typia add pattern <name>.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn executeWorkspaceAddWithOptionalDryRun<\n\t\t\t\tAwaited<ReturnType<typeof addRuntime.runAddPatternCommand>>\n\t\t\t>({\n\t\t\t\tbuildCompletion: (result) =>\n\t\t\t\t\tbuildAddCompletionPayload({\n\t\t\t\t\t\tkind: \"pattern\",\n\t\t\t\t\t\tprojectDir: result.projectDir,\n\t\t\t\t\t\tvalues: {\n\t\t\t\t\t\t\tpatternSlug: result.patternSlug,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\tcwd,\n\t\t\t\tdryRun,\n\t\t\t\temitOutput,\n\t\t\t\texecute: (targetCwd) =>\n\t\t\t\t\taddRuntime.runAddPatternCommand({\n\t\t\t\t\t\tcwd: targetCwd,\n\t\t\t\t\t\tpatternName: name,\n\t\t\t\t\t}),\n\t\t\t\tprintLine,\n\t\t\t});\n\t\t}\n\n\t\tif (kind === \"binding-source\") {\n\t\t\tif (!name) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"`wp-typia add binding-source` requires <name>. Usage: wp-typia add binding-source <name>.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn executeWorkspaceAddWithOptionalDryRun<\n\t\t\t\tAwaited<ReturnType<typeof addRuntime.runAddBindingSourceCommand>>\n\t\t\t>({\n\t\t\t\tbuildCompletion: (result) =>\n\t\t\t\t\tbuildAddCompletionPayload({\n\t\t\t\t\t\tkind: \"binding-source\",\n\t\t\t\t\t\tprojectDir: result.projectDir,\n\t\t\t\t\t\tvalues: {\n\t\t\t\t\t\t\tbindingSourceSlug: result.bindingSourceSlug,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\tcwd,\n\t\t\t\tdryRun,\n\t\t\t\temitOutput,\n\t\t\t\texecute: (targetCwd) =>\n\t\t\t\t\taddRuntime.runAddBindingSourceCommand({\n\t\t\t\t\t\tbindingSourceName: name,\n\t\t\t\t\t\tcwd: targetCwd,\n\t\t\t\t\t}),\n\t\t\t\tprintLine,\n\t\t\t});\n\t\t}\n\n\t\tif (kind === \"rest-resource\") {\n\t\t\tif (!name) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"`wp-typia add rest-resource` requires <name>. Usage: wp-typia add rest-resource <name> [--namespace <vendor/v1>] [--methods <list,read,create>].\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst methods = readOptionalStringFlag(flags, \"methods\");\n\t\t\tconst namespace = readOptionalStringFlag(flags, \"namespace\");\n\t\t\treturn executeWorkspaceAddWithOptionalDryRun<\n\t\t\t\tAwaited<ReturnType<typeof addRuntime.runAddRestResourceCommand>>\n\t\t\t>({\n\t\t\t\tbuildCompletion: (result) =>\n\t\t\t\t\tbuildAddCompletionPayload({\n\t\t\t\t\t\tkind: \"rest-resource\",\n\t\t\t\t\t\tprojectDir: result.projectDir,\n\t\t\t\t\t\tvalues: {\n\t\t\t\t\t\t\tmethods: result.methods.join(\", \"),\n\t\t\t\t\t\t\tnamespace: result.namespace,\n\t\t\t\t\t\t\trestResourceSlug: result.restResourceSlug,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\tcwd,\n\t\t\t\tdryRun,\n\t\t\t\temitOutput,\n\t\t\t\texecute: (targetCwd) =>\n\t\t\t\t\taddRuntime.runAddRestResourceCommand({\n\t\t\t\t\t\tcwd: targetCwd,\n\t\t\t\t\t\tmethods,\n\t\t\t\t\t\tnamespace,\n\t\t\t\t\t\trestResourceName: name,\n\t\t\t\t\t}),\n\t\t\t\tprintLine,\n\t\t\t});\n\t\t}\n\n\t\tif (kind === \"editor-plugin\") {\n\t\t\tif (!name) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"`wp-typia add editor-plugin` requires <name>. Usage: wp-typia add editor-plugin <name> [--slot <PluginSidebar>].\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst slot = readOptionalStringFlag(flags, \"slot\");\n\t\t\treturn executeWorkspaceAddWithOptionalDryRun<\n\t\t\t\tAwaited<ReturnType<typeof addRuntime.runAddEditorPluginCommand>>\n\t\t\t>({\n\t\t\t\tbuildCompletion: (result) =>\n\t\t\t\t\tbuildAddCompletionPayload({\n\t\t\t\t\t\tkind: \"editor-plugin\",\n\t\t\t\t\t\tprojectDir: result.projectDir,\n\t\t\t\t\t\tvalues: {\n\t\t\t\t\t\t\teditorPluginSlug: result.editorPluginSlug,\n\t\t\t\t\t\t\tslot: result.slot,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\tcwd,\n\t\t\t\tdryRun,\n\t\t\t\temitOutput,\n\t\t\t\texecute: (targetCwd) =>\n\t\t\t\t\taddRuntime.runAddEditorPluginCommand({\n\t\t\t\t\t\tcwd: targetCwd,\n\t\t\t\t\t\teditorPluginName: name,\n\t\t\t\t\t\tslot,\n\t\t\t\t\t}),\n\t\t\t\tprintLine,\n\t\t\t});\n\t\t}\n\n\t\tif (kind === \"hooked-block\") {\n\t\t\tif (!name) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"`wp-typia add hooked-block` requires <block-slug>. Usage: wp-typia add hooked-block <block-slug> --anchor <anchor-block-name> --position <before|after|firstChild|lastChild>.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst anchorBlockName = readOptionalStringFlag(flags, \"anchor\");\n\t\t\tif (!anchorBlockName) {\n\t\t\t\tthrow new Error(\"`wp-typia add hooked-block` requires --anchor <anchor-block-name>.\");\n\t\t\t}\n\n\t\t\tconst position = readOptionalStringFlag(flags, \"position\");\n\t\t\tif (!position) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"`wp-typia add hooked-block` requires --position <before|after|firstChild|lastChild>.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn executeWorkspaceAddWithOptionalDryRun<\n\t\t\t\tAwaited<ReturnType<typeof addRuntime.runAddHookedBlockCommand>>\n\t\t\t>({\n\t\t\t\tbuildCompletion: (result) =>\n\t\t\t\t\tbuildAddCompletionPayload({\n\t\t\t\t\t\tkind: \"hooked-block\",\n\t\t\t\t\t\tprojectDir: result.projectDir,\n\t\t\t\t\t\tvalues: {\n\t\t\t\t\t\t\tanchorBlockName: result.anchorBlockName,\n\t\t\t\t\t\t\tblockSlug: result.blockSlug,\n\t\t\t\t\t\t\tposition: result.position,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\tcwd,\n\t\t\t\tdryRun,\n\t\t\t\temitOutput,\n\t\t\t\texecute: (targetCwd) =>\n\t\t\t\t\taddRuntime.runAddHookedBlockCommand({\n\t\t\t\t\t\tanchorBlockName,\n\t\t\t\t\t\tblockName: name,\n\t\t\t\t\t\tcwd: targetCwd,\n\t\t\t\t\t\tposition,\n\t\t\t\t\t}),\n\t\t\t\tprintLine,\n\t\t\t});\n\t\t}\n\n\t\tif (kind !== \"block\") {\n\t\t\tthrow new Error(\n\t\t\t\t`Unknown add kind \"${kind}\". Expected one of: block, variation, pattern, binding-source, rest-resource, editor-plugin, hooked-block.`,\n\t\t\t);\n\t\t}\n\n\t\tif (!name) {\n\t\t\tthrow new Error(\n\t\t\t\t\"`wp-typia add block` requires <name>. Usage: wp-typia add block <name> --template <basic|interactivity|persistence|compound>\",\n\t\t\t);\n\t\t}\n\n\t\tif (!flags.template) {\n\t\t\tthrow new Error(\n\t\t\t\t\"`wp-typia add block` requires --template <basic|interactivity|persistence|compound>.\",\n\t\t\t);\n\t\t}\n\n\t\tconst externalLayerId = readOptionalStringFlag(flags, \"external-layer-id\");\n\t\tconst externalLayerSource = readOptionalStringFlag(flags, \"external-layer-source\");\n\t\tconst shouldPromptForLayerSelection =\n\t\t\tBoolean(externalLayerSource) &&\n\t\t\t!Boolean(externalLayerId) &&\n\t\t\t(interactive ?? isInteractiveTerminal());\n\t\tconst promptRuntime = shouldPromptForLayerSelection\n\t\t\t? await loadCliPromptRuntime()\n\t\t\t: undefined;\n\t\tactivePrompt = shouldPromptForLayerSelection\n\t\t\t? (prompt ?? promptRuntime?.createReadlinePrompt())\n\t\t\t: undefined;\n\t\tconst selectPrompt = activePrompt;\n\n\t\tconst alternateRenderTargets = readOptionalStringFlag(\n\t\t\tflags,\n\t\t\t\"alternate-render-targets\",\n\t\t);\n\t\tconst dataStorageMode = readOptionalStringFlag(flags, \"data-storage\");\n\t\tconst innerBlocksPreset = readOptionalStringFlag(flags, \"inner-blocks-preset\");\n\t\tconst persistencePolicy = readOptionalStringFlag(flags, \"persistence-policy\");\n\t\tconst resolvedTemplateId = readOptionalStringFlag(flags, \"template\") as\n\t\t\t| \"basic\"\n\t\t\t| \"interactivity\"\n\t\t\t| \"persistence\"\n\t\t\t| \"compound\";\n\n\t\treturn executeWorkspaceAddWithOptionalDryRun<\n\t\t\tAwaited<ReturnType<typeof addRuntime.runAddBlockCommand>>\n\t\t>({\n\t\t\tbuildCompletion: (result) =>\n\t\t\t\tbuildAddCompletionPayload({\n\t\t\t\t\tkind: \"block\",\n\t\t\t\t\tprojectDir: result.projectDir,\n\t\t\t\t\tvalues: {\n\t\t\t\t\t\tblockSlugs: result.blockSlugs.join(\", \"),\n\t\t\t\t\t\ttemplateId: result.templateId,\n\t\t\t\t\t},\n\t\t\t\t\twarnings: result.warnings,\n\t\t\t\t}),\n\t\t\tcwd,\n\t\t\tdryRun,\n\t\t\temitOutput,\n\t\t\texecute: (targetCwd) =>\n\t\t\t\taddRuntime.runAddBlockCommand({\n\t\t\t\t\talternateRenderTargets,\n\t\t\t\t\tblockName: name,\n\t\t\t\t\tcwd: targetCwd,\n\t\t\t\t\tdataStorageMode,\n\t\t\t\t\texternalLayerId,\n\t\t\t\t\texternalLayerSource,\n\t\t\t\t\tinnerBlocksPreset,\n\t\t\t\t\tpersistencePolicy,\n\t\t\t\t\tselectExternalLayerId: selectPrompt\n\t\t\t\t\t\t? (options) =>\n\t\t\t\t\t\t\t\tselectPrompt.select(\n\t\t\t\t\t\t\t\t\t\"Select an external layer\",\n\t\t\t\t\t\t\t\t\ttoExternalLayerPromptOptions(options),\n\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\ttemplateId: resolvedTemplateId,\n\t\t\t\t}),\n\t\t\tprintLine,\n\t\t\twarnLine,\n\t\t});\n\t} catch (error) {\n\t\tif (!shouldWrapCliCommandError({ emitOutput })) {\n\t\t\tthrow error;\n\t\t}\n\t\tthrow await wrapCliCommandError(\"add\", error);\n\t} finally {\n\t\tif (activePrompt && activePrompt !== prompt) {\n\t\t\tactivePrompt.close();\n\t\t}\n\t}\n}\n\nexport async function executeTemplatesCommand(\n\t{ flags }: TemplatesExecutionInput,\n\tprintLine: PrintLine = console.log,\n): Promise<void> {\n\tconst {\n\t\tformatTemplateDetails,\n\t\tformatTemplateFeatures,\n\t\tformatTemplateSummary,\n\t\tgetTemplateById,\n\t\tlistTemplates,\n\t} = await loadCliTemplatesRuntime();\n\tconst subcommand = flags.subcommand ?? \"list\";\n\n\tif (subcommand === \"list\") {\n\t\tfor (const template of listTemplates()) {\n\t\t\tprintBlock(\n\t\t\t\t[formatTemplateSummary(template), formatTemplateFeatures(template)],\n\t\t\t\tprintLine,\n\t\t\t);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (subcommand === \"inspect\") {\n\t\tif (!flags.id) {\n\t\t\tthrow new Error(\"`wp-typia templates inspect` requires <template-id>.\");\n\t\t}\n\t\tconst template = getTemplateById(flags.id);\n\t\tif (!template) {\n\t\t\tthrow new Error(`Unknown template \"${flags.id}\".`);\n\t\t}\n\t\tprintBlock(\n\t\t\t[\n\t\t\t\tformatTemplateSummary(template),\n\t\t\t\tformatTemplateFeatures(template),\n\t\t\t\tformatTemplateDetails(template),\n\t\t\t],\n\t\t\tprintLine,\n\t\t);\n\t\treturn;\n\t}\n\n\tthrow new Error(`Unknown templates subcommand \"${subcommand}\". Expected list or inspect.`);\n}\n\nexport async function executeDoctorCommand(cwd: string): Promise<void> {\n\ttry {\n\t\tconst { runDoctor } = await loadCliDoctorRuntime();\n\t\tawait runDoctor(cwd);\n\t} catch (error) {\n\t\tthrow await wrapCliCommandError(\"doctor\", error);\n\t}\n}\n\nexport async function loadAddWorkspaceBlockOptions(cwd: string) {\n\tconst { tryResolveWorkspaceProject } = await loadWorkspaceProjectRuntime();\n\tconst workspace = tryResolveWorkspaceProject(cwd);\n\tif (!workspace) {\n\t\treturn [];\n\t}\n\n\tconst { getWorkspaceBlockSelectOptions } = await loadCliAddRuntime();\n\treturn getWorkspaceBlockSelectOptions(workspace.projectDir);\n}\n\nexport async function executeMigrateCommand({\n\tcommand,\n\tcwd,\n\tflags,\n\tprompt,\n\trenderLine,\n}: MigrateExecutionInput): Promise<AlternateBufferCompletionPayload | void> {\n\tconst { formatMigrationHelpText, parseMigrationArgs, runMigrationCommand } =\n\t\tawait loadMigrationsRuntime();\n\tif (!command) {\n\t\tconsole.log(formatMigrationHelpText());\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst argv = [command];\n\t\tpushFlag(argv, \"all\", flags.all);\n\t\tpushFlag(argv, \"force\", flags.force);\n\t\tpushFlag(\n\t\t\targv,\n\t\t\t\"current-migration-version\",\n\t\t\treadOptionalLooseStringFlag(flags, \"current-migration-version\"),\n\t\t);\n\t\tpushFlag(argv, \"migration-version\", readOptionalLooseStringFlag(flags, \"migration-version\"));\n\t\tpushFlag(\n\t\t\targv,\n\t\t\t\"from-migration-version\",\n\t\t\treadOptionalLooseStringFlag(flags, \"from-migration-version\"),\n\t\t);\n\t\tpushFlag(\n\t\t\targv,\n\t\t\t\"to-migration-version\",\n\t\t\treadOptionalLooseStringFlag(flags, \"to-migration-version\"),\n\t\t);\n\t\tpushFlag(argv, \"iterations\", readOptionalLooseStringFlag(flags, \"iterations\"));\n\t\tpushFlag(argv, \"seed\", readOptionalLooseStringFlag(flags, \"seed\"));\n\n\t\tconst parsed = parseMigrationArgs(argv);\n\t\tconst lines: string[] | null = renderLine ? [] : null;\n\t\tconst captureLine = (line: string) => {\n\t\t\tlines?.push(line);\n\t\t\tif (renderLine) {\n\t\t\t\trenderLine(line);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconsole.log(line);\n\t\t};\n\t\tconst result = await runMigrationCommand(parsed, cwd, {\n\t\t\tprompt,\n\t\t\trenderLine: captureLine,\n\t\t});\n\t\tif (renderLine) {\n\t\t\treturn result && typeof result === \"object\" && \"cancelled\" in result && result.cancelled === true\n\t\t\t\t\t? undefined\n\t\t\t\t\t: buildMigrationCompletionPayload({\n\t\t\t\t\t\tcommand: parsed.command ?? \"plan\",\n\t\t\t\t\t\tlines: lines ?? [],\n\t\t\t\t\t});\n\t\t}\n\n\t\tif (result && typeof result === \"object\" && \"cancelled\" in result && result.cancelled === true) {\n\t\t\treturn;\n\t\t}\n\t} catch (error) {\n\t\tif (!shouldWrapCliCommandError({ renderLine })) {\n\t\t\tthrow error;\n\t\t}\n\t\tthrow await wrapCliCommandError(\"migrate\", error);\n\t}\n}\n\nexport async function listTemplatesForRuntime() {\n\tconst { listTemplates } = await loadCliTemplatesRuntime();\n\treturn listTemplates();\n}\n",
|
|
12
|
+
"import { createElement, useEffect, useState, type ComponentType } from \"react\";\n\nimport {\n\tresolveLazyFlowComponent,\n\tuseAlternateBufferExitKeys,\n\tuseAlternateBufferLifecycle,\n} from \"./alternate-buffer-lifecycle\";\n\ntype LazyFlowProps<TProps> = {\n\tloader: () => Promise<{ default: ComponentType<TProps> }>;\n\tprops: TProps;\n};\n\nexport function LazyFlow<TProps>({ loader, props }: LazyFlowProps<TProps>) {\n\tconst [Component, setComponent] = useState<ComponentType<TProps> | null>(null);\n\tconst { handleFailure } = useAlternateBufferLifecycle(\"wp-typia TUI flow failed\", {\n\t\tenableExitKeys: false,\n\t});\n\n\tuseAlternateBufferExitKeys({\n\t\tenabled: Component === null,\n\t});\n\n\tuseEffect(() => {\n\t\tlet disposed = false;\n\n\t\tvoid resolveLazyFlowComponent({\n\t\t\tisDisposed: () => disposed,\n\t\t\tloader,\n\t\t\tonFailure: handleFailure,\n\t\t\tonLoaded: (component) => {\n\t\t\t\tsetComponent(() => component);\n\t\t\t},\n\t\t});\n\n\t\treturn () => {\n\t\t\tdisposed = true;\n\t\t};\n\t}, [handleFailure, loader]);\n\n\tif (!Component) {\n\t\treturn null;\n\t}\n\n\treturn createElement(Component as ComponentType<any>, props as any);\n}\n",
|
|
13
|
+
"import { useCallback, useState } from \"react\";\n\nimport { useRuntime } from \"@bunli/runtime/app\";\nimport { useKeyboard } from \"@bunli/tui\";\n\ntype AlternateBufferKeyEvent = {\n\tctrl?: boolean;\n\tsequence?: string;\n\tname?: string;\n};\n\nexport type AlternateBufferCompletionPayload = {\n\ttitle: string;\n\tpreambleLines?: string[];\n\tsummaryLines?: string[];\n\tnextSteps?: string[];\n\toptionalTitle?: string;\n\toptionalLines?: string[];\n\toptionalNote?: string;\n\twarningLines?: string[];\n};\n\nexport type AlternateBufferProgressPayload = {\n\tdescription?: string;\n\ttitle: string;\n};\n\ntype AlternateBufferFailureOptions = {\n\tcontext: string;\n\terror: unknown;\n\texit: () => void;\n\tlog?: (message: string) => void;\n};\n\ntype RunAlternateBufferActionOptions = {\n\taction: () => Promise<unknown>;\n\tcontext: string;\n\texit: () => void;\n\texitOnSuccess?: boolean;\n\tlog?: (message: string) => void;\n\tonSuccess?: (result: unknown) => void;\n};\n\ntype AlternateBufferLifecycleStatus = \"editing\" | \"submitting\" | \"completed\";\n\nexport function describeAlternateBufferFailure(context: string, error: unknown): string {\n\tconst message = error instanceof Error ? error.message : String(error);\n\treturn `${context}: ${message}`;\n}\n\nexport function isAlternateBufferExitKey(key: AlternateBufferKeyEvent): boolean {\n\treturn key.name === \"q\" || (key.ctrl === true && key.name === \"c\");\n}\n\nexport function isAlternateBufferCompletionKey(key: AlternateBufferKeyEvent): boolean {\n\treturn key.name === \"enter\" || key.sequence === \"\\r\" || key.sequence === \"\\n\";\n}\n\nexport function reportAlternateBufferFailure({\n\tcontext,\n\terror,\n\texit,\n\tlog = console.error,\n}: AlternateBufferFailureOptions): void {\n\tconst message = describeAlternateBufferFailure(context, error);\n\texit();\n\tlog(message);\n}\n\nexport async function runAlternateBufferAction({\n\taction,\n\tcontext,\n\texit,\n\texitOnSuccess = true,\n\tlog = console.error,\n\tonSuccess,\n}: RunAlternateBufferActionOptions): Promise<void> {\n\ttry {\n\t\tconst result = await action();\n\t\tonSuccess?.(result);\n\t\tif (exitOnSuccess) {\n\t\t\texit();\n\t\t}\n\t} catch (error) {\n\t\treportAlternateBufferFailure({ context, error, exit, log });\n\t}\n}\n\nexport async function resolveLazyFlowComponent<TProps>({\n\tloader,\n\tonLoaded,\n\tonFailure,\n\tisDisposed,\n}: {\n\tloader: () => Promise<{ default: React.ComponentType<TProps> }>;\n\tonLoaded: (component: React.ComponentType<TProps>) => void;\n\tonFailure: (error: unknown) => void;\n\tisDisposed: () => boolean;\n}): Promise<void> {\n\ttry {\n\t\tconst module = await loader();\n\t\tif (!isDisposed()) {\n\t\t\tonLoaded(module.default);\n\t\t}\n\t} catch (error) {\n\t\tif (!isDisposed()) {\n\t\t\tonFailure(error);\n\t\t}\n\t}\n}\n\nexport function useAlternateBufferExitKeys(options: {\n\tenabled?: boolean;\n\texit?: () => void;\n} = {}): void {\n\tconst runtime = useRuntime();\n\tconst exit = options.exit ?? (() => runtime.exit());\n\tconst enabled = options.enabled ?? true;\n\n\tuseKeyboard((key: AlternateBufferKeyEvent) => {\n\t\tif (!enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (isAlternateBufferExitKey(key)) {\n\t\t\texit();\n\t\t}\n\t});\n}\n\nexport function useAlternateBufferCompletionKeys(options: {\n\tenabled?: boolean;\n\texit?: () => void;\n} = {}): void {\n\tconst runtime = useRuntime();\n\tconst exit = options.exit ?? (() => runtime.exit());\n\tconst enabled = options.enabled ?? false;\n\n\tuseKeyboard((key: AlternateBufferKeyEvent) => {\n\t\tif (!enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (isAlternateBufferCompletionKey(key) || isAlternateBufferExitKey(key)) {\n\t\t\texit();\n\t\t}\n\t});\n}\n\nfunction isAlternateBufferCompletionPayload(\n\tvalue: unknown,\n): value is AlternateBufferCompletionPayload {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\treturn false;\n\t}\n\n\tconst candidate = value as { title?: unknown };\n\treturn typeof candidate.title === \"string\" && candidate.title.trim().length > 0;\n}\n\nexport function useAlternateBufferLifecycle(\n\tcontext: string,\n\toptions: {\n\t\tenableExitKeys?: boolean;\n\t} = {},\n): {\n\tcompletion: AlternateBufferCompletionPayload | null;\n\thandleCancel: () => void;\n\thandleFailure: (error: unknown) => void;\n\thandleSubmit: (action: () => Promise<AlternateBufferCompletionPayload | void>) => Promise<void>;\n\tprogress: AlternateBufferProgressPayload | null;\n\treportProgress: (payload: AlternateBufferProgressPayload) => void;\n\tstatus: AlternateBufferLifecycleStatus;\n} {\n\tconst runtime = useRuntime();\n\tconst [completion, setCompletion] = useState<AlternateBufferCompletionPayload | null>(null);\n\tconst [progress, setProgress] = useState<AlternateBufferProgressPayload | null>(null);\n\tconst [status, setStatus] = useState<AlternateBufferLifecycleStatus>(\"editing\");\n\tconst exit = useCallback(() => {\n\t\truntime.exit();\n\t}, [runtime]);\n\n\tuseAlternateBufferExitKeys({\n\t\tenabled: (options.enableExitKeys ?? true) && status !== \"completed\",\n\t\texit,\n\t});\n\n\tuseAlternateBufferCompletionKeys({\n\t\tenabled: status === \"completed\",\n\t\texit,\n\t});\n\n\tconst handleCancel = useCallback(() => {\n\t\tsetCompletion(null);\n\t\tsetProgress(null);\n\t\tsetStatus(\"editing\");\n\t\texit();\n\t}, [exit]);\n\n\tconst handleFailure = useCallback(\n\t\t(error: unknown) => {\n\t\t\tsetCompletion(null);\n\t\t\tsetProgress(null);\n\t\t\tsetStatus(\"editing\");\n\t\t\treportAlternateBufferFailure({\n\t\t\t\tcontext,\n\t\t\t\terror,\n\t\t\t\texit,\n\t\t\t});\n\t\t},\n\t\t[context, exit],\n\t);\n\n\tconst handleSubmit = useCallback(\n\t\tasync (action: () => Promise<AlternateBufferCompletionPayload | void>) => {\n\t\t\tsetCompletion(null);\n\t\t\tsetProgress(null);\n\t\t\tsetStatus(\"submitting\");\n\n\t\t\ttry {\n\t\t\t\tconst result = await action();\n\t\t\t\tif (isAlternateBufferCompletionPayload(result)) {\n\t\t\t\t\tsetCompletion(result);\n\t\t\t\t\tsetProgress(null);\n\t\t\t\t\tsetStatus(\"completed\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\texit();\n\t\t\t} catch (error) {\n\t\t\t\tsetCompletion(null);\n\t\t\t\tsetProgress(null);\n\t\t\t\tsetStatus(\"editing\");\n\t\t\t\treportAlternateBufferFailure({\n\t\t\t\t\tcontext,\n\t\t\t\t\terror,\n\t\t\t\t\texit,\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\t[context, exit],\n\t);\n\n\tconst reportProgress = useCallback((payload: AlternateBufferProgressPayload) => {\n\t\tsetProgress(payload);\n\t}, []);\n\n\treturn {\n\t\tcompletion,\n\t\thandleCancel,\n\t\thandleFailure,\n\t\thandleSubmit,\n\t\tprogress,\n\t\treportProgress,\n\t\tstatus,\n\t};\n}\n",
|
|
14
|
+
"export type FormatType = 'markdown' | 'code' | 'emoji' | 'template'\n\n// ANSI escape codes\nconst RESET = '\\x1b[0m'\nconst BOLD = '\\x1b[1m'\nconst DIM = '\\x1b[2m'\nconst ITALIC = '\\x1b[3m'\nconst UNDERLINE = '\\x1b[4m'\nconst STRIKETHROUGH = '\\x1b[9m'\n\nfunction bold(s: string): string {\n return `${BOLD}${s}${RESET}`\n}\n\nfunction dim(s: string): string {\n return `${DIM}${s}${RESET}`\n}\n\nfunction italic(s: string): string {\n return `${ITALIC}${s}${RESET}`\n}\n\nfunction underline(s: string): string {\n return `${UNDERLINE}${s}${RESET}`\n}\n\nfunction strikethrough(s: string): string {\n return `${STRIKETHROUGH}${s}${RESET}`\n}\n\nfunction colored(s: string, r: number, g: number, b: number): string {\n return `\\x1b[38;2;${r};${g};${b}m${s}${RESET}`\n}\n\n// --- formatMarkdown ---\n\nfunction applyInlineFormats(line: string): string {\n // Bold: **text** or __text__\n line = line.replace(/\\*\\*(.+?)\\*\\*/g, (_match, content) => bold(content))\n line = line.replace(/__(.+?)__/g, (_match, content) => bold(content))\n\n // Strikethrough: ~~text~~\n line = line.replace(/~~(.+?)~~/g, (_match, content) => strikethrough(content))\n\n // Inline code: `code`\n line = line.replace(/`([^`]+)`/g, (_match, content) => dim(content))\n\n // Links: [text](url)\n line = line.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_match, text, url) => underline(text) + dim(` (${url})`)\n )\n\n // Italic: *text* (not bold) or _text_ (not bold)\n line = line.replace(/(?<!\\*)\\*(?!\\*)(.+?)(?<!\\*)\\*(?!\\*)/g, (_match, content) => italic(content))\n line = line.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, (_match, content) => italic(content))\n\n return line\n}\n\n/**\n * Format markdown text for terminal display using ANSI codes.\n */\nexport function formatMarkdown(text: string): string {\n const lines = text.split('\\n')\n const result: string[] = []\n let inCodeBlock = false\n const codeBlockLines: string[] = []\n\n for (const line of lines) {\n // Code block fences\n if (/^```/.test(line)) {\n if (inCodeBlock) {\n // End code block — render collected lines\n for (const codeLine of codeBlockLines) {\n result.push(dim(` ${codeLine}`))\n }\n codeBlockLines.length = 0\n inCodeBlock = false\n } else {\n inCodeBlock = true\n }\n continue\n }\n\n if (inCodeBlock) {\n codeBlockLines.push(line)\n continue\n }\n\n // Headers\n const headerMatch = line.match(/^(#{1,6})\\s+(.+)$/)\n if (headerMatch) {\n const level = headerMatch[1].length\n const content = headerMatch[2]\n if (level === 1) {\n result.push(bold(colored(content, 255, 255, 255)))\n } else if (level === 2) {\n result.push(bold(content))\n } else {\n result.push(bold(dim(content)))\n }\n continue\n }\n\n // Horizontal rules\n if (/^[-*_]{3,}$/.test(line)) {\n result.push('\\u2500'.repeat(40))\n continue\n }\n\n // Unordered list items\n const ulMatch = line.match(/^(\\s*)[-*+]\\s+(.+)$/)\n if (ulMatch) {\n result.push(`${ulMatch[1]}\\u2022 ${applyInlineFormats(ulMatch[2])}`)\n continue\n }\n\n // Blockquotes\n const bqMatch = line.match(/^>\\s*(.+)$/)\n if (bqMatch) {\n result.push(`\\u2502 ${italic(bqMatch[1])}`)\n continue\n }\n\n // Regular line — apply inline transforms\n result.push(applyInlineFormats(line))\n }\n\n // If code block was never closed, still render what we collected\n if (inCodeBlock) {\n for (const codeLine of codeBlockLines) {\n result.push(dim(` ${codeLine}`))\n }\n }\n\n return result.join('\\n')\n}\n\n// --- formatCode ---\n\nconst KEYWORDS = new Set([\n 'const', 'let', 'var', 'function', 'class', 'return', 'if', 'else',\n 'for', 'while', 'import', 'export', 'from', 'async', 'await',\n 'try', 'catch', 'throw', 'new', 'typeof', 'interface', 'type',\n 'enum', 'extends', 'implements', 'public', 'private', 'protected',\n 'def', 'fn', 'pub', 'use', 'mod', 'struct', 'impl', 'trait',\n])\n\n/**\n * Format code with basic syntax highlighting for terminal display.\n */\nexport function formatCode(code: string, _language?: string): string {\n const lines = code.split('\\n')\n const result: string[] = []\n\n for (const line of lines) {\n // Full-line comments\n const trimmed = line.trimStart()\n if (trimmed.startsWith('//') || trimmed.startsWith('#')) {\n result.push(dim(line))\n continue\n }\n\n let formatted = ''\n let i = 0\n while (i < line.length) {\n const ch = line[i]\n\n // Strings\n if (ch === '\"' || ch === \"'\") {\n const quote = ch\n let str = quote\n i++\n while (i < line.length && line[i] !== quote) {\n if (line[i] === '\\\\' && i + 1 < line.length) {\n str += line[i] + line[i + 1]\n i += 2\n } else {\n str += line[i]\n i++\n }\n }\n if (i < line.length) {\n str += line[i]\n i++\n }\n formatted += colored(str, 80, 200, 120) // green\n continue\n }\n\n // Numbers\n if (/[0-9]/.test(ch) && (i === 0 || /[\\s(=,+\\-*/]/.test(line[i - 1]))) {\n let num = ''\n while (i < line.length && /[0-9._]/.test(line[i])) {\n num += line[i]\n i++\n }\n formatted += colored(num, 230, 200, 80) // yellow\n continue\n }\n\n // Words (potential keywords)\n if (/[a-zA-Z_]/.test(ch)) {\n let word = ''\n while (i < line.length && /[a-zA-Z0-9_]/.test(line[i])) {\n word += line[i]\n i++\n }\n if (KEYWORDS.has(word)) {\n formatted += colored(word, 100, 150, 255) // blue\n } else {\n formatted += word\n }\n continue\n }\n\n // Inline comment\n if (ch === '/' && i + 1 < line.length && line[i + 1] === '/') {\n formatted += dim(line.slice(i))\n i = line.length\n continue\n }\n\n formatted += ch\n i++\n }\n\n result.push(formatted)\n }\n\n return result.join('\\n')\n}\n\n// --- formatEmoji ---\n\nconst EMOJI_MAP: Record<string, string> = {\n ':smile:': '\\u{1F604}',\n ':laughing:': '\\u{1F606}',\n ':wink:': '\\u{1F609}',\n ':heart:': '\\u2764\\uFE0F',\n ':fire:': '\\u{1F525}',\n ':rocket:': '\\u{1F680}',\n ':check:': '\\u2705',\n ':x:': '\\u274C',\n ':warning:': '\\u26A0\\uFE0F',\n ':star:': '\\u2B50',\n ':thumbsup:': '\\u{1F44D}',\n ':thumbsdown:': '\\u{1F44E}',\n ':wave:': '\\u{1F44B}',\n ':clap:': '\\u{1F44F}',\n ':eyes:': '\\u{1F440}',\n ':tada:': '\\u{1F389}',\n ':bug:': '\\u{1F41B}',\n ':wrench:': '\\u{1F527}',\n ':lock:': '\\u{1F512}',\n ':key:': '\\u{1F511}',\n ':bulb:': '\\u{1F4A1}',\n ':memo:': '\\u{1F4DD}',\n ':link:': '\\u{1F517}',\n ':package:': '\\u{1F4E6}',\n ':sparkles:': '\\u2728',\n ':zap:': '\\u26A1',\n ':gear:': '\\u2699\\uFE0F',\n ':earth:': '\\u{1F30D}',\n ':clock:': '\\u{1F552}',\n ':question:': '\\u2753',\n ':exclamation:': '\\u2757',\n ':pin:': '\\u{1F4CC}',\n ':bell:': '\\u{1F514}',\n ':gem:': '\\u{1F48E}',\n ':shield:': '\\u{1F6E1}\\uFE0F',\n}\n\n/**\n * Replace emoji shortcodes with unicode emoji.\n */\nexport function formatEmoji(text: string): string {\n return text.replace(/:([a-z_]+):/g, (match) => EMOJI_MAP[match] ?? match)\n}\n\n// --- format dispatcher ---\n\n/**\n * Apply a named format type.\n */\nexport function format(text: string, type: FormatType): string {\n switch (type) {\n case 'markdown':\n return formatMarkdown(text)\n case 'code':\n return formatCode(text)\n case 'emoji':\n return formatEmoji(text)\n case 'template':\n return text\n }\n}\n",
|
|
15
|
+
"import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { FormContext, type FormFieldRegistration } from './form-context.js'\nimport { useScopedKeyboard } from '@bunli/runtime/app'\nimport { validateFormValues, type FormErrors } from './form-engine.js'\nimport { createKeyMatcher } from '@bunli/runtime/app'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport interface FormProps<TSchema extends StandardSchemaV1 = StandardSchemaV1> {\n title: string\n schema: TSchema\n onSubmit: (values: StandardSchemaV1.InferOutput<TSchema>) => void | Promise<void>\n onCancel?: () => void\n onReset?: () => void\n onValidationError?: (errors: FormErrors) => void\n onDirtyChange?: (isDirty: boolean, dirtyFields: string[]) => void\n onSubmitStateChange?: (state: { isSubmitting: boolean; isValidating: boolean }) => void\n initialValues?: Partial<StandardSchemaV1.InferOutput<TSchema>>\n validateOnChange?: boolean\n submitHint?: string\n resetHint?: string\n scopeId?: string\n children: React.ReactNode\n}\n\nconst formKeymap = createKeyMatcher({\n cancel: ['escape'],\n nextField: ['tab'],\n previousField: ['shift+tab'],\n submitShortcut: ['ctrl+s'],\n resetShortcut: ['ctrl+r'],\n nextError: ['f8'],\n previousError: ['shift+f8'],\n submit: ['enter']\n})\n\nfunction areValuesEqual(left: unknown, right: unknown): boolean {\n if (Object.is(left, right)) return true\n\n if (\n typeof left === 'object' &&\n left !== null &&\n typeof right === 'object' &&\n right !== null\n ) {\n try {\n return JSON.stringify(left) === JSON.stringify(right)\n } catch {\n return false\n }\n }\n\n return false\n}\n\nexport function Form<TSchema extends StandardSchemaV1>({\n title,\n schema,\n onSubmit,\n onCancel,\n onReset,\n onValidationError,\n onDirtyChange,\n onSubmitStateChange,\n initialValues,\n validateOnChange = true,\n submitHint,\n resetHint,\n scopeId,\n children\n}: FormProps<TSchema>) {\n const { tokens } = useTuiTheme()\n const keyboardScopeId = scopeId ?? `form:${title}`\n const initialValuesRef = useRef<Record<string, unknown>>({\n ...(initialValues as Record<string, unknown> | undefined)\n })\n\n const [values, setValues] = useState<Record<string, unknown>>(\n () => ({ ...initialValuesRef.current })\n )\n const valuesRef = useRef(values)\n const [errors, setErrors] = useState<FormErrors>({})\n const [touched, setTouched] = useState<Record<string, boolean>>({})\n const [fieldDefaults, setFieldDefaults] = useState<Record<string, unknown>>({})\n const [fieldOrder, setFieldOrder] = useState<string[]>([])\n const [fieldMeta, setFieldMeta] = useState<Record<string, FormFieldRegistration>>({})\n const [focusIndex, setFocusIndex] = useState(0)\n const [isSubmitting, setIsSubmitting] = useState(false)\n const [isValidating, setIsValidating] = useState(false)\n\n const activeFieldName = fieldOrder[focusIndex] ?? null\n\n useEffect(() => {\n setFocusIndex((prev) => {\n if (fieldOrder.length === 0) return 0\n return Math.min(prev, fieldOrder.length - 1)\n })\n }, [fieldOrder])\n\n useEffect(() => {\n valuesRef.current = values\n }, [values])\n\n const baselineValues = useMemo(\n () => ({ ...fieldDefaults, ...initialValuesRef.current }),\n [fieldDefaults]\n )\n\n const dirtyFields = useMemo(() => {\n const keys = new Set<string>([\n ...Object.keys(baselineValues),\n ...Object.keys(values)\n ])\n\n const next: Record<string, boolean> = {}\n for (const key of keys) {\n next[key] = !areValuesEqual(values[key], baselineValues[key])\n }\n return next\n }, [baselineValues, values])\n\n const isDirty = useMemo(\n () => Object.values(dirtyFields).some(Boolean),\n [dirtyFields]\n )\n\n useEffect(() => {\n onDirtyChange?.(\n isDirty,\n Object.entries(dirtyFields)\n .filter(([, dirty]) => dirty)\n .map(([name]) => name)\n )\n }, [dirtyFields, isDirty, onDirtyChange])\n\n useEffect(() => {\n onSubmitStateChange?.({ isSubmitting, isValidating })\n }, [isSubmitting, isValidating, onSubmitStateChange])\n\n const registerField = useCallback((field: FormFieldRegistration) => {\n setFieldMeta((prev) => {\n if (prev[field.name]) return prev\n return { ...prev, [field.name]: field }\n })\n\n setFieldOrder((prev) => {\n if (prev.includes(field.name)) return prev\n return [...prev, field.name]\n })\n\n if (field.defaultValue !== undefined) {\n setFieldDefaults((prev) => {\n if (prev[field.name] !== undefined) return prev\n return { ...prev, [field.name]: field.defaultValue }\n })\n\n setValues((prev) => {\n if (prev[field.name] !== undefined) return prev\n return { ...prev, [field.name]: field.defaultValue }\n })\n }\n }, [])\n\n const unregisterField = useCallback((name: string) => {\n setFieldMeta((prev) => {\n if (!prev[name]) return prev\n const next = { ...prev }\n delete next[name]\n return next\n })\n\n setFieldOrder((prev) => prev.filter((fieldName) => fieldName !== name))\n setTouched((prev) => {\n if (!prev[name]) return prev\n const next = { ...prev }\n delete next[name]\n return next\n })\n setErrors((prev) => {\n if (!prev[name]) return prev\n const next = { ...prev }\n delete next[name]\n return next\n })\n }, [])\n\n const runValidation = useCallback(\n async (nextValues: Record<string, unknown>, notify: boolean) => {\n setIsValidating(true)\n try {\n const result = await validateFormValues(schema, nextValues)\n if (result.ok) {\n setErrors({})\n return result\n }\n\n setErrors(result.errors)\n if (notify) {\n onValidationError?.(result.errors)\n }\n return result\n } finally {\n setIsValidating(false)\n }\n },\n [schema, onValidationError]\n )\n\n const setFieldValue = useCallback(\n (name: string, value: unknown) => {\n const nextValues = { ...valuesRef.current, [name]: value }\n valuesRef.current = nextValues\n setValues(nextValues)\n setTouched((prev) => ({ ...prev, [name]: true }))\n if (validateOnChange) {\n void runValidation(nextValues, false)\n }\n },\n [runValidation, validateOnChange]\n )\n\n const markTouched = useCallback((name: string) => {\n setTouched((prev) => ({ ...prev, [name]: true }))\n }, [])\n\n const focusField = useCallback((name: string) => {\n const idx = fieldOrder.indexOf(name)\n if (idx >= 0) {\n setFocusIndex(idx)\n }\n }, [fieldOrder])\n\n const getErrorFields = useCallback((): string[] => {\n return fieldOrder.filter((name) => Boolean(errors[name]))\n }, [errors, fieldOrder])\n\n const focusFirstErrorField = useCallback(\n (nextErrors: FormErrors) => {\n const firstInOrder = fieldOrder.find((name) => Boolean(nextErrors[name]))\n if (firstInOrder) {\n focusField(firstInOrder)\n return\n }\n\n const fallback = Object.keys(nextErrors).find((name) => name !== '_form')\n if (fallback) {\n focusField(fallback)\n }\n },\n [fieldOrder, focusField]\n )\n\n const submit = useCallback(() => {\n void (async () => {\n const result = await runValidation(values, true)\n if (!result.ok) {\n focusFirstErrorField(result.errors)\n return\n }\n\n setIsSubmitting(true)\n try {\n await onSubmit(result.value as StandardSchemaV1.InferOutput<TSchema>)\n } finally {\n setIsSubmitting(false)\n }\n })()\n }, [focusFirstErrorField, onSubmit, runValidation, values])\n\n const reset = useCallback(() => {\n const nextValues = { ...baselineValues }\n setValues(nextValues)\n setTouched({})\n setErrors({})\n setFocusIndex(0)\n onReset?.()\n }, [baselineValues, onReset])\n\n const jumpToNextError = useCallback(() => {\n const errorFields = getErrorFields()\n if (errorFields.length === 0) return\n\n if (!activeFieldName) {\n focusField(errorFields[0] ?? '')\n return\n }\n\n const currentIndex = errorFields.indexOf(activeFieldName)\n const next = errorFields[(currentIndex + 1 + errorFields.length) % errorFields.length]\n if (next) {\n focusField(next)\n }\n }, [activeFieldName, focusField, getErrorFields])\n\n const jumpToPreviousError = useCallback(() => {\n const errorFields = getErrorFields()\n if (errorFields.length === 0) return\n\n if (!activeFieldName) {\n focusField(errorFields[errorFields.length - 1] ?? '')\n return\n }\n\n const currentIndex = errorFields.indexOf(activeFieldName)\n const prev = errorFields[(currentIndex - 1 + errorFields.length) % errorFields.length]\n if (prev) {\n focusField(prev)\n }\n }, [activeFieldName, focusField, getErrorFields])\n\n const isActiveScope = useScopedKeyboard(\n keyboardScopeId,\n (key) => {\n if (formKeymap.match('cancel', key)) {\n onCancel?.()\n return true\n }\n\n if (formKeymap.match('nextField', key) || formKeymap.match('previousField', key)) {\n if (fieldOrder.length === 0) return\n setFocusIndex((prev) => {\n const delta = formKeymap.match('previousField', key) ? -1 : 1\n const next = prev + delta\n if (next < 0) return fieldOrder.length - 1\n if (next >= fieldOrder.length) return 0\n return next\n })\n return true\n }\n\n if (formKeymap.match('submitShortcut', key)) {\n submit()\n return true\n }\n\n if (formKeymap.match('resetShortcut', key)) {\n reset()\n return true\n }\n\n if (formKeymap.match('previousError', key)) {\n jumpToPreviousError()\n return true\n }\n\n if (formKeymap.match('nextError', key)) {\n jumpToNextError()\n return true\n }\n\n if (formKeymap.match('submit', key)) {\n if (activeFieldName) {\n const activeField = fieldMeta[activeFieldName]\n if (activeField?.submitOnEnter === false) return false\n }\n submit()\n return true\n }\n\n return false\n },\n { active: true, priority: 10 }\n )\n\n const footerMessage = useMemo(() => {\n if (isSubmitting) return 'Submitting...'\n if (isValidating) return 'Validating...'\n if (errors._form) return `Validation error: ${errors._form}`\n if (submitHint) return submitHint\n\n const dirtyLabel = isDirty ? 'dirty' : 'clean'\n return `Tab: next field | Enter: submit | Esc: cancel | Ctrl+S: submit | Ctrl+R: reset (${dirtyLabel}) | F8: next error`\n }, [errors._form, isDirty, isSubmitting, isValidating, submitHint])\n\n const contextValue = useMemo(\n () => ({\n values,\n errors,\n touched,\n dirtyFields,\n isDirty,\n isSubmitting,\n isValidating,\n keyboardScopeId,\n activeFieldName,\n registerField,\n unregisterField,\n setFieldValue,\n markTouched,\n focusField,\n submit,\n reset,\n getErrorFields,\n jumpToNextError,\n jumpToPreviousError\n }),\n [\n activeFieldName,\n dirtyFields,\n errors,\n focusField,\n getErrorFields,\n isDirty,\n isSubmitting,\n isValidating,\n jumpToNextError,\n jumpToPreviousError,\n keyboardScopeId,\n markTouched,\n registerField,\n reset,\n setFieldValue,\n submit,\n touched,\n unregisterField,\n values\n ]\n )\n\n return (\n <FormContext.Provider value={contextValue}>\n <box\n title={title}\n border\n padding={2}\n style={{\n flexDirection: 'column',\n borderColor: isActiveScope ? tokens.accent : tokens.border,\n backgroundColor: tokens.background\n }}\n >\n {children}\n <box style={{ flexDirection: 'row', gap: 2, marginTop: 2 }}>\n <text\n content={footerMessage}\n fg={errors._form ? tokens.textDanger : isDirty ? tokens.accent : tokens.textMuted}\n />\n {resetHint ? <text content={resetHint} fg={tokens.textMuted} /> : null}\n </box>\n </box>\n </FormContext.Provider>\n )\n}\n",
|
|
16
|
+
"import { createContext, useCallback, useContext, useEffect } from 'react'\nimport type { FormErrors } from './form-engine.js'\n\nexport interface FormFieldRegistration {\n name: string\n defaultValue?: unknown\n submitOnEnter?: boolean\n}\n\nexport interface FormContextValue {\n values: Record<string, unknown>\n errors: FormErrors\n touched: Record<string, boolean>\n dirtyFields: Record<string, boolean>\n isDirty: boolean\n isSubmitting: boolean\n isValidating: boolean\n keyboardScopeId: string\n activeFieldName: string | null\n registerField: (field: FormFieldRegistration) => void\n unregisterField: (name: string) => void\n setFieldValue: (name: string, value: unknown) => void\n markTouched: (name: string) => void\n focusField: (name: string) => void\n submit: () => void\n reset: () => void\n getErrorFields: () => string[]\n jumpToNextError: () => void\n jumpToPreviousError: () => void\n}\n\nexport const FormContext = createContext<FormContextValue | null>(null)\n\nexport function useFormContext(): FormContextValue {\n const context = useContext(FormContext)\n if (!context) {\n throw new Error('Interactive form fields must be rendered inside <Form>.')\n }\n return context\n}\n\nexport interface UseFormFieldOptions<T> {\n defaultValue?: T\n submitOnEnter?: boolean\n}\n\nexport interface UseFormFieldResult<T> {\n value: T\n error?: string\n touched: boolean\n focused: boolean\n setValue: (value: T) => void\n focus: () => void\n blur: () => void\n}\n\nexport function useFormField<T = unknown>(\n name: string,\n options: UseFormFieldOptions<T> = {}\n): UseFormFieldResult<T> {\n const context = useFormContext()\n const { registerField, unregisterField, setFieldValue, focusField, markTouched } = context\n\n useEffect(() => {\n registerField({\n name,\n defaultValue: options.defaultValue,\n submitOnEnter: options.submitOnEnter\n })\n\n return () => {\n unregisterField(name)\n }\n }, [name, options.defaultValue, options.submitOnEnter, registerField, unregisterField])\n\n const setValue = useCallback(\n (value: T) => {\n setFieldValue(name, value)\n },\n [name, setFieldValue]\n )\n\n const focus = useCallback(() => {\n focusField(name)\n }, [focusField, name])\n\n const blur = useCallback(() => {\n markTouched(name)\n }, [markTouched, name])\n\n const value = (context.values[name] ?? options.defaultValue ?? '') as T\n\n return {\n value,\n error: context.errors[name],\n touched: Boolean(context.touched[name]),\n focused: context.activeFieldName === name,\n setValue,\n focus,\n blur\n }\n}\n",
|
|
17
|
+
"import { useCallback } from 'react'\nimport { useFormField } from './form-context.js'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport interface FormFieldProps {\n label: string\n name: string\n placeholder?: string\n required?: boolean\n description?: string\n defaultValue?: string\n onChange?: (value: string) => void\n onSubmit?: (value: string) => void\n prompt?: string\n charLimit?: number\n showCharCount?: boolean\n width?: number\n}\n\nexport function FormField({\n label,\n name,\n placeholder,\n required,\n description,\n defaultValue = '',\n onChange,\n onSubmit,\n prompt,\n charLimit,\n showCharCount = false,\n width\n}: FormFieldProps) {\n const { tokens } = useTuiTheme()\n const field = useFormField<string>(name, {\n defaultValue,\n submitOnEnter: true\n })\n\n const handleInput = useCallback((newValue: string) => {\n if (charLimit && newValue.length > charLimit) {\n newValue = newValue.slice(0, charLimit)\n }\n field.setValue(newValue)\n onChange?.(newValue)\n }, [field, onChange, charLimit])\n\n const handleSubmit = useCallback(() => {\n const submittedValue = field.value ?? ''\n field.setValue(submittedValue)\n field.blur()\n onSubmit?.(submittedValue)\n }, [field, onSubmit])\n\n const currentLength = (field.value ?? '').length\n\n return (\n <box style={{ flexDirection: 'column', marginBottom: 1, gap: 1 }}>\n <text\n content={`${field.focused ? '>' : ' '} ${label}${required ? ' *' : ''}`}\n fg={field.focused ? tokens.accent : tokens.textPrimary}\n />\n {description ? <text content={description} fg={tokens.textMuted} /> : null}\n <box\n title={label}\n border\n height={3}\n width={width}\n style={{\n marginTop: 0.5,\n borderColor: field.error ? tokens.textDanger : field.focused ? tokens.accent : tokens.borderMuted\n }}\n >\n {prompt ? (\n <box style={{ flexDirection: 'row' }}>\n <text content={prompt} fg={tokens.accent} />\n <input\n value={field.value ?? ''}\n placeholder={placeholder}\n onInput={handleInput}\n onSubmit={handleSubmit}\n focused={field.focused}\n style={{ focusedBackgroundColor: tokens.backgroundMuted, flexGrow: 1 }}\n />\n </box>\n ) : (\n <input\n value={field.value ?? ''}\n placeholder={placeholder}\n onInput={handleInput}\n onSubmit={handleSubmit}\n focused={field.focused}\n style={{\n focusedBackgroundColor: tokens.backgroundMuted\n }}\n />\n )}\n </box>\n {showCharCount && (\n <text\n content={charLimit ? `${currentLength}/${charLimit}` : `${currentLength} chars`}\n fg={charLimit && currentLength >= charLimit ? tokens.textWarning : tokens.textMuted}\n />\n )}\n {field.error ? <text content={field.error} fg={tokens.textDanger} /> : null}\n </box>\n )\n}\n",
|
|
18
|
+
"import { useCallback, useMemo } from 'react'\nimport type { SelectOption } from '@opentui/core'\nimport { useFormField } from './form-context.js'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport interface SelectFieldProps {\n label: string\n name: string\n options: SelectOption[]\n required?: boolean\n description?: string\n defaultValue?: SelectOption['value']\n onChange?: (value: SelectOption['value']) => void\n}\n\nexport function SelectField({ \n label, \n name, \n options,\n required,\n description,\n defaultValue,\n onChange\n}: SelectFieldProps) {\n const { tokens } = useTuiTheme()\n const initialValue = defaultValue ?? options[0]?.value\n\n const field = useFormField<SelectOption['value']>(name, {\n defaultValue: initialValue,\n submitOnEnter: false\n })\n\n const selectedIndex = useMemo(() => {\n const index = options.findIndex((option) => option.value === field.value)\n if (index >= 0) return index\n return 0\n }, [field.value, options])\n\n const handleChange = useCallback((index: number, option: SelectOption | null) => {\n if (!option) return\n field.setValue(option.value)\n field.blur()\n onChange?.(option.value)\n }, [field, onChange])\n\n return (\n <box style={{ flexDirection: 'column', marginBottom: 1, gap: 1 }}>\n <text\n content={`${field.focused ? '>' : ' '} ${label}${required ? ' *' : ''}`}\n fg={field.focused ? tokens.accent : tokens.textPrimary}\n />\n {description ? <text content={description} fg={tokens.textMuted} /> : null}\n <box\n width='100%'\n border\n height={8}\n style={{\n marginTop: 0.5,\n borderColor: field.error ? tokens.textDanger : field.focused ? tokens.accent : tokens.borderMuted\n }}\n >\n <select\n options={options}\n selectedIndex={selectedIndex}\n onChange={handleChange}\n focused={field.focused}\n style={{\n flexGrow: 1\n }}\n />\n </box>\n {field.error ? <text content={field.error} fg={tokens.textDanger} /> : null}\n </box>\n )\n}\n",
|
|
19
|
+
"import { useId, useMemo, useState } from 'react'\nimport type { SelectOption } from '@opentui/core'\nimport { useScopedKeyboard } from '@bunli/runtime/app'\nimport { useFormField } from './form-context.js'\nimport { createKeyMatcher } from '@bunli/runtime/app'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport interface MultiSelectOption extends SelectOption {\n label?: string\n hint?: string\n disabled?: boolean\n}\n\nexport interface MultiSelectFieldProps {\n label: string\n name: string\n options: MultiSelectOption[]\n required?: boolean\n description?: string\n defaultValue?: Array<SelectOption['value']>\n scopeId?: string\n}\n\nfunction nextEnabledIndex(options: MultiSelectOption[], from: number, delta: number): number {\n if (options.length === 0) return 0\n for (let step = 0; step < options.length; step += 1) {\n const next = (from + delta * (step + 1) + options.length) % options.length\n if (!options[next]?.disabled) return next\n }\n return from\n}\n\nconst multiSelectKeymap = createKeyMatcher({\n up: ['up', 'k'],\n down: ['down', 'j'],\n toggle: ['space'],\n submit: ['enter']\n})\n\nexport function MultiSelectField({\n label,\n name,\n options,\n required,\n description,\n defaultValue = [],\n scopeId\n}: MultiSelectFieldProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const field = useFormField<Array<SelectOption['value']>>(name, {\n defaultValue,\n submitOnEnter: false\n })\n const keyboardScopeId = scopeId ?? `multiselect:${name}:${reactScopeId}`\n const [activeIndex, setActiveIndex] = useState(() => options.findIndex((option) => !option.disabled))\n const selectedSet = useMemo(() => new Set(field.value ?? []), [field.value])\n\n const commit = (next: Set<SelectOption['value']>) => {\n field.setValue(\n options\n .filter((option) => next.has(option.value))\n .map((option) => option.value)\n )\n field.blur()\n }\n\n useScopedKeyboard(\n keyboardScopeId,\n (key) => {\n if (!field.focused) return false\n\n if (multiSelectKeymap.match('up', key)) {\n setActiveIndex((prev) => nextEnabledIndex(options, prev, -1))\n return true\n }\n\n if (multiSelectKeymap.match('down', key)) {\n setActiveIndex((prev) => nextEnabledIndex(options, prev, 1))\n return true\n }\n\n if (multiSelectKeymap.match('toggle', key)) {\n const option = options[activeIndex]\n if (!option || option.disabled) return false\n const next = new Set(selectedSet)\n if (next.has(option.value)) {\n next.delete(option.value)\n } else {\n next.add(option.value)\n }\n commit(next)\n return true\n }\n\n if (multiSelectKeymap.match('submit', key)) {\n if (!required || selectedSet.size > 0) {\n field.blur()\n return true\n }\n }\n\n return false\n },\n { active: field.focused }\n )\n\n return (\n <box style={{ flexDirection: 'column', marginBottom: 1, gap: 1 }}>\n <text content={`${label}${required ? ' *' : ''}`} fg={tokens.textPrimary} />\n {description ? <text content={description} fg={tokens.textMuted} /> : null}\n <box border padding={1} style={{ flexDirection: 'column', gap: 1, borderColor: field.error ? tokens.textDanger : tokens.borderMuted }}>\n {options.map((option, index) => {\n const focused = field.focused && index === activeIndex\n const selected = selectedSet.has(option.value)\n const marker = selected ? '[x]' : '[ ]'\n const labelText = option.label ?? option.name\n const hintText = option.hint ?? option.description\n const disabled = option.disabled ? ' [disabled]' : ''\n return (\n <text\n key={`${name}-${option.value}`}\n content={`${focused ? '>' : ' '} ${marker} ${labelText}${hintText ? ` - ${hintText}` : ''}${disabled}`}\n fg={option.disabled ? tokens.textMuted : focused ? tokens.accent : tokens.textPrimary}\n />\n )\n })}\n </box>\n {field.error ? <text content={field.error} fg={tokens.textDanger} /> : null}\n </box>\n )\n}\n",
|
|
20
|
+
"import { useCallback, useEffect, useState } from 'react'\nimport { useFormField } from './form-context.js'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport interface NumberFieldProps {\n label: string\n name: string\n placeholder?: string\n required?: boolean\n description?: string\n defaultValue?: number\n onChange?: (value: number | undefined) => void\n}\n\nexport function NumberField({\n label,\n name,\n placeholder,\n required,\n description,\n defaultValue,\n onChange\n}: NumberFieldProps) {\n const { tokens } = useTuiTheme()\n const field = useFormField<number | undefined>(name, {\n defaultValue,\n submitOnEnter: true\n })\n const [draft, setDraft] = useState(() =>\n typeof field.value === 'number' && Number.isFinite(field.value) ? String(field.value) : ''\n )\n\n useEffect(() => {\n if (typeof field.value === 'number' && Number.isFinite(field.value)) {\n setDraft(String(field.value))\n return\n }\n setDraft('')\n }, [field.value])\n\n const handleInput = useCallback(\n (value: string) => {\n setDraft(value)\n const trimmed = value.trim()\n if (!trimmed) {\n field.setValue(undefined)\n onChange?.(undefined)\n return\n }\n\n const parsed = Number(trimmed)\n if (!Number.isNaN(parsed)) {\n field.setValue(parsed)\n onChange?.(parsed)\n }\n },\n [field, onChange]\n )\n\n return (\n <box style={{ flexDirection: 'column', marginBottom: 1, gap: 1 }}>\n <text content={`${label}${required ? ' *' : ''}`} fg={tokens.textPrimary} />\n {description ? <text content={description} fg={tokens.textMuted} /> : null}\n <box border height={3} style={{ borderColor: field.error ? tokens.textDanger : tokens.borderMuted }}>\n <input\n value={draft}\n placeholder={placeholder}\n onInput={handleInput}\n focused={field.focused}\n style={{ focusedBackgroundColor: tokens.backgroundMuted }}\n />\n </box>\n {field.error ? <text content={field.error} fg={tokens.textDanger} /> : null}\n </box>\n )\n}\n",
|
|
21
|
+
"import { useCallback, useMemo, useState } from 'react'\nimport { useFormContext, useFormField } from './form-context.js'\nimport { useScopedKeyboard } from '@bunli/runtime/app'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport interface PasswordFieldProps {\n label: string\n name: string\n placeholder?: string\n required?: boolean\n description?: string\n defaultValue?: string\n onChange?: (value: string) => void\n}\n\nfunction mask(value: string): string {\n return '*'.repeat(value.length)\n}\n\nfunction resolveTextInput(sequence: string): string {\n const normalized = sequence\n .replace(/\\u001b\\[200~/g, '')\n .replace(/\\u001b\\[201~/g, '')\n .replace(/\\r\\n/g, '')\n .replace(/[\\r\\n]/g, '')\n\n let output = ''\n for (const char of normalized) {\n const codePoint = char.codePointAt(0)\n if (typeof codePoint !== 'number') continue\n if (codePoint < 0x20 || codePoint === 0x7f) continue\n output += char\n }\n return output\n}\n\nexport function PasswordField({\n label,\n name,\n placeholder,\n required,\n description,\n defaultValue = '',\n onChange\n}: PasswordFieldProps) {\n const { tokens } = useTuiTheme()\n const { keyboardScopeId } = useFormContext()\n const field = useFormField<string>(name, {\n defaultValue,\n submitOnEnter: true\n })\n const [revealed, setRevealed] = useState(false)\n\n const handleInput = useCallback((value: string) => {\n field.setValue(value)\n onChange?.(value)\n }, [field, onChange])\n\n const value = field.value ?? ''\n const visibleValue = revealed ? value : mask(value)\n const display = useMemo(() => {\n const base =\n value.length > 0\n ? visibleValue\n : (placeholder ?? '')\n if (field.focused) return `${base}▌`\n return base\n }, [field.focused, placeholder, value.length, visibleValue])\n\n useScopedKeyboard(\n keyboardScopeId,\n (key) => {\n if (!field.focused) return false\n\n if (key.ctrl && key.name === 'r') {\n setRevealed((prev) => !prev)\n return true\n }\n\n if (key.name === 'backspace' || key.name === 'delete') {\n if (value.length === 0) return true\n handleInput(value.slice(0, -1))\n return true\n }\n\n if (key.ctrl || key.meta || key.option) {\n return false\n }\n\n const typed = resolveTextInput(key.sequence ?? '')\n if (typed.length > 0) {\n handleInput(`${value}${typed}`)\n return true\n }\n\n return false\n },\n { active: field.focused, priority: 10 }\n )\n\n return (\n <box style={{ flexDirection: 'column', marginBottom: 1, gap: 1 }}>\n <text\n content={`${field.focused ? '>' : ' '} ${label}${required ? ' *' : ''}`}\n fg={field.focused ? tokens.accent : tokens.textPrimary}\n />\n {description ? <text content={description} fg={tokens.textMuted} /> : null}\n <box\n border\n height={3}\n style={{ borderColor: field.error ? tokens.textDanger : field.focused ? tokens.accent : tokens.borderMuted }}\n >\n <box style={{ backgroundColor: field.focused ? tokens.backgroundMuted : tokens.background }}>\n <text\n content={display}\n fg={value.length === 0 ? tokens.textMuted : tokens.textPrimary}\n />\n </box>\n </box>\n <text content={`Value: ${mask(value)} ${revealed ? '(revealed)' : '(hidden, Ctrl+R reveal)'}`} fg={tokens.textMuted} />\n {field.error ? <text content={field.error} fg={tokens.textDanger} /> : null}\n </box>\n )\n}\n",
|
|
22
|
+
"import { useCallback, useEffect, useRef, useState } from 'react'\nimport type { TextareaRenderable } from '@opentui/core'\nimport { useFormField } from './form-context.js'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport interface TextareaFieldProps {\n label: string\n name: string\n placeholder?: string\n required?: boolean\n description?: string\n defaultValue?: string\n showLineNumbers?: boolean\n maxLines?: number\n charLimit?: number\n showCharCount?: boolean\n height?: number\n}\n\nexport function TextareaField({\n label,\n name,\n placeholder,\n required,\n description,\n defaultValue = '',\n showLineNumbers = false,\n maxLines,\n charLimit,\n showCharCount = false,\n height\n}: TextareaFieldProps) {\n const { tokens } = useTuiTheme()\n const field = useFormField<string>(name, {\n defaultValue,\n submitOnEnter: false\n })\n const ref = useRef<TextareaRenderable | null>(null)\n const [charCount, setCharCount] = useState(defaultValue.length)\n const [lineCount, setLineCount] = useState(1)\n\n const syncFromBuffer = useCallback(() => {\n let value = ref.current?.plainText ?? ''\n\n if (maxLines) {\n const lines = value.split('\\n')\n if (lines.length > maxLines) {\n value = lines.slice(0, maxLines).join('\\n')\n ref.current?.setText(value)\n }\n }\n\n if (charLimit && value.length > charLimit) {\n value = value.slice(0, charLimit)\n ref.current?.setText(value)\n }\n\n field.setValue(value)\n setCharCount(value.length)\n setLineCount(value.split('\\n').length)\n }, [field, maxLines, charLimit])\n\n useEffect(() => {\n const nextValue = field.value ?? ''\n const currentValue = ref.current?.plainText ?? ''\n if (currentValue !== nextValue) {\n ref.current?.setText(nextValue)\n }\n }, [field.value])\n\n return (\n <box style={{ flexDirection: 'column', marginBottom: 1, gap: 1 }}>\n <text content={`${label}${required ? ' *' : ''}`} fg={tokens.textPrimary} />\n {description ? <text content={description} fg={tokens.textMuted} /> : null}\n <box border height={height ?? 7} style={{ borderColor: field.error ? tokens.textDanger : tokens.borderMuted }}>\n <textarea\n ref={ref}\n initialValue={field.value ?? defaultValue}\n placeholder={placeholder}\n focused={field.focused}\n onContentChange={syncFromBuffer}\n onSubmit={() => {\n syncFromBuffer()\n field.blur()\n }}\n style={{\n focusedBackgroundColor: tokens.backgroundMuted\n }}\n />\n </box>\n {(showLineNumbers || showCharCount) && (\n <box style={{ flexDirection: 'row', gap: 2 }}>\n {showLineNumbers && (\n <text content={`Lines: ${lineCount}${maxLines ? `/${maxLines}` : ''}`} fg={tokens.textMuted} />\n )}\n {showCharCount && (\n <text\n content={charLimit ? `${charCount}/${charLimit}` : `${charCount} chars`}\n fg={charLimit && charCount >= charLimit ? tokens.textWarning : tokens.textMuted}\n />\n )}\n </box>\n )}\n {field.error ? <text content={field.error} fg={tokens.textDanger} /> : null}\n </box>\n )\n}\n",
|
|
23
|
+
"import { useId } from 'react'\nimport { useScopedKeyboard } from '@bunli/runtime/app'\nimport { useFormField } from './form-context.js'\nimport { createKeyMatcher } from '@bunli/runtime/app'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport interface CheckboxFieldProps {\n label: string\n name: string\n description?: string\n defaultValue?: boolean\n scopeId?: string\n}\n\nconst checkboxKeymap = createKeyMatcher({\n toggle: ['space', 'enter']\n})\n\nexport function CheckboxField({ label, name, description, defaultValue = false, scopeId }: CheckboxFieldProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const field = useFormField<boolean>(name, {\n defaultValue,\n submitOnEnter: false\n })\n const keyboardScopeId = scopeId ?? `checkbox:${name}:${reactScopeId}`\n\n const toggle = () => {\n field.setValue(!field.value)\n field.blur()\n }\n\n useScopedKeyboard(\n keyboardScopeId,\n (key) => {\n if (!field.focused) return false\n if (checkboxKeymap.match('toggle', key)) {\n toggle()\n return true\n }\n return false\n },\n { active: field.focused }\n )\n\n return (\n <box style={{ flexDirection: 'column', marginBottom: 1, gap: 1 }}>\n <text\n content={`${field.focused ? '>' : ' '} ${field.value ? '[x]' : '[ ]'} ${label}`}\n fg={field.focused ? tokens.accent : tokens.textPrimary}\n />\n {description ? <text content={description} fg={tokens.textMuted} /> : null}\n {field.error ? <text content={field.error} fg={tokens.textDanger} /> : null}\n </box>\n )\n}\n",
|
|
24
|
+
"import { useEffect } from 'react'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { SelectOption } from '@opentui/core'\nimport { Form, type FormProps } from './form.js'\nimport { FormField } from './form-field.js'\nimport { SelectField } from './select-field.js'\nimport { NumberField } from './number-field.js'\nimport { TextareaField } from './textarea-field.js'\nimport { PasswordField } from './password-field.js'\nimport { CheckboxField } from './checkbox-field.js'\nimport { MultiSelectField } from './multi-select-field.js'\nimport { useFormContext } from './form-context.js'\n\ntype FormOutput<TSchema extends StandardSchemaV1> = StandardSchemaV1.InferOutput<TSchema>\ntype SchemaFieldName<TSchema extends StandardSchemaV1> = keyof FormOutput<TSchema> & string\n\ninterface BaseSchemaField<TSchema extends StandardSchemaV1> {\n name: SchemaFieldName<TSchema>\n label: string\n required?: boolean\n description?: string\n visibleWhen?: (values: Partial<FormOutput<TSchema>>) => boolean\n deriveDefault?: (values: Partial<FormOutput<TSchema>>) => unknown\n}\n\nexport interface TextSchemaField<TSchema extends StandardSchemaV1> extends BaseSchemaField<TSchema> {\n kind: 'text'\n placeholder?: string\n defaultValue?: string\n}\n\nexport interface SelectSchemaField<TSchema extends StandardSchemaV1> extends BaseSchemaField<TSchema> {\n kind: 'select'\n options: SelectOption[]\n defaultValue?: SelectOption['value']\n}\n\nexport interface MultiSelectSchemaField<TSchema extends StandardSchemaV1> extends BaseSchemaField<TSchema> {\n kind: 'multiselect'\n options: SelectOption[]\n defaultValue?: Array<SelectOption['value']>\n}\n\nexport interface NumberSchemaField<TSchema extends StandardSchemaV1> extends BaseSchemaField<TSchema> {\n kind: 'number'\n placeholder?: string\n defaultValue?: number\n}\n\nexport interface PasswordSchemaField<TSchema extends StandardSchemaV1> extends BaseSchemaField<TSchema> {\n kind: 'password'\n placeholder?: string\n defaultValue?: string\n}\n\nexport interface TextareaSchemaField<TSchema extends StandardSchemaV1> extends BaseSchemaField<TSchema> {\n kind: 'textarea'\n placeholder?: string\n defaultValue?: string\n}\n\nexport interface CheckboxSchemaField<TSchema extends StandardSchemaV1> extends BaseSchemaField<TSchema> {\n kind: 'checkbox'\n defaultValue?: boolean\n}\n\nexport type SchemaField<TSchema extends StandardSchemaV1> =\n | TextSchemaField<TSchema>\n | SelectSchemaField<TSchema>\n | MultiSelectSchemaField<TSchema>\n | NumberSchemaField<TSchema>\n | PasswordSchemaField<TSchema>\n | TextareaSchemaField<TSchema>\n | CheckboxSchemaField<TSchema>\n\nexport interface SchemaFormProps<TSchema extends StandardSchemaV1>\n extends Omit<FormProps<TSchema>, 'children'> {\n fields: SchemaField<TSchema>[]\n}\n\nfunction SchemaFieldsRenderer<TSchema extends StandardSchemaV1>({\n fields\n}: {\n fields: SchemaField<TSchema>[]\n}) {\n const context = useFormContext()\n const values = context.values as Partial<FormOutput<TSchema>>\n\n useEffect(() => {\n for (const field of fields) {\n if (!field.deriveDefault) continue\n if (context.values[field.name] !== undefined) continue\n const derived = field.deriveDefault(values)\n if (derived !== undefined) {\n context.setFieldValue(field.name, derived)\n }\n }\n }, [context, fields, values])\n\n return (\n <>\n {fields.map((field) => {\n const visible = field.visibleWhen ? field.visibleWhen(values) : true\n if (!visible) return null\n\n if (field.kind === 'select') {\n return (\n <SelectField\n key={field.name}\n name={field.name}\n label={field.label}\n options={field.options}\n defaultValue={field.defaultValue}\n required={field.required}\n description={field.description}\n />\n )\n }\n\n if (field.kind === 'multiselect') {\n return (\n <MultiSelectField\n key={field.name}\n name={field.name}\n label={field.label}\n options={field.options}\n defaultValue={field.defaultValue}\n required={field.required}\n description={field.description}\n />\n )\n }\n\n if (field.kind === 'number') {\n return (\n <NumberField\n key={field.name}\n name={field.name}\n label={field.label}\n placeholder={field.placeholder}\n defaultValue={field.defaultValue}\n required={field.required}\n description={field.description}\n />\n )\n }\n\n if (field.kind === 'password') {\n return (\n <PasswordField\n key={field.name}\n name={field.name}\n label={field.label}\n placeholder={field.placeholder}\n defaultValue={field.defaultValue}\n required={field.required}\n description={field.description}\n />\n )\n }\n\n if (field.kind === 'textarea') {\n return (\n <TextareaField\n key={field.name}\n name={field.name}\n label={field.label}\n placeholder={field.placeholder}\n defaultValue={field.defaultValue}\n required={field.required}\n description={field.description}\n />\n )\n }\n\n if (field.kind === 'checkbox') {\n return (\n <CheckboxField\n key={field.name}\n name={field.name}\n label={field.label}\n defaultValue={field.defaultValue}\n description={field.description}\n />\n )\n }\n\n return (\n <FormField\n key={field.name}\n name={field.name}\n label={field.label}\n placeholder={field.placeholder}\n defaultValue={field.defaultValue}\n required={field.required}\n description={field.description}\n />\n )\n })}\n </>\n )\n}\n\nexport function SchemaForm<TSchema extends StandardSchemaV1>({\n fields,\n ...formProps\n}: SchemaFormProps<TSchema>) {\n return (\n <Form {...formProps}>\n <SchemaFieldsRenderer fields={fields} />\n </Form>\n )\n}\n",
|
|
25
|
+
"import { useMemo } from 'react'\nimport { useTuiTheme } from '@bunli/runtime/app'\nimport { displayWidth, formatFixedWidth, type TextOverflowMode } from '@bunli/runtime/app'\n\nexport interface KeyValueItem {\n key: string\n value: string | number | boolean | null | undefined\n}\n\nexport interface KeyValueListProps {\n items: KeyValueItem[]\n minKeyWidth?: number\n maxLineWidth?: number\n fillWidth?: boolean\n overflow?: TextOverflowMode\n}\n\nexport function KeyValueList({\n items,\n minKeyWidth = 12,\n maxLineWidth,\n fillWidth = false,\n overflow = 'ellipsis'\n}: KeyValueListProps) {\n const { tokens } = useTuiTheme()\n\n const keyWidth = useMemo(\n () => Math.max(minKeyWidth, ...items.map((item) => displayWidth(item.key)), 0),\n [items, minKeyWidth]\n )\n const lineWidth = useMemo(() => {\n const contentLineWidth = Math.max(\n keyWidth + 3,\n ...items.map((item) => displayWidth(`${formatFixedWidth(item.key, keyWidth)} : ${String(item.value ?? '')}`))\n )\n const boundedLineWidth = typeof maxLineWidth === 'number'\n ? Math.max(keyWidth + 3, Math.min(maxLineWidth, contentLineWidth))\n : contentLineWidth\n\n if (!fillWidth || typeof maxLineWidth !== 'number') {\n return boundedLineWidth\n }\n\n return Math.max(boundedLineWidth, maxLineWidth)\n }, [fillWidth, items, keyWidth, maxLineWidth])\n\n return (\n <box style={{ flexDirection: 'column', gap: 1 }}>\n {items.map((item, index) => (\n <text\n key={`kv-${index}-${item.key}`}\n content={formatFixedWidth(\n `${formatFixedWidth(item.key, keyWidth)} : ${String(item.value ?? '')}`,\n lineWidth,\n { overflow }\n )}\n fg={tokens.textPrimary}\n />\n ))}\n </box>\n )\n}\n",
|
|
26
|
+
"import { Children } from 'react'\nimport type { ReactNode } from 'react'\n\nexport interface GridProps {\n children: ReactNode\n columns?: number\n gap?: number\n}\n\nexport function Grid({ children, columns = 2, gap = 1 }: GridProps) {\n const items = Children.toArray(children)\n const normalizedColumns = Math.max(1, columns)\n const rows: ReactNode[][] = []\n\n for (let index = 0; index < items.length; index += normalizedColumns) {\n rows.push(items.slice(index, index + normalizedColumns))\n }\n\n return (\n <box style={{ flexDirection: 'column', gap }}>\n {rows.map((row, rowIndex) => (\n <box key={`grid-row-${rowIndex}`} style={{ flexDirection: 'row', gap }}>\n {row.map((item, itemIndex) => (\n <box key={`grid-item-${rowIndex}-${itemIndex}`}>\n {item}\n </box>\n ))}\n </box>\n ))}\n </box>\n )\n}\n",
|
|
27
|
+
"import { useEffect, useId, useRef, type ReactNode } from 'react'\nimport type { BoxRenderable, Renderable } from '@opentui/core'\nimport { useRenderer } from '@opentui/react'\nimport { createKeyMatcher } from '@bunli/runtime/app'\nimport { useScopedKeyboard } from '@bunli/runtime/app'\nimport { OverlayPortal } from '@bunli/runtime/app'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport interface ModalProps {\n isOpen: boolean\n title: string\n children: ReactNode\n onClose?: () => void\n closeHint?: string\n scopeId?: string\n zIndex?: number\n}\n\nconst modalKeymap = createKeyMatcher({\n close: ['escape', 'ctrl+c'],\n trap: ['tab', 'shift+tab']\n})\n\nexport function Modal({\n isOpen,\n title,\n children,\n onClose,\n closeHint = 'Esc to close',\n scopeId,\n zIndex = 1000\n}: ModalProps) {\n const { tokens } = useTuiTheme()\n const renderer = useRenderer()\n const reactScopeId = useId()\n const keyboardScopeId = scopeId ?? `modal:${title}:${reactScopeId}`\n const modalRef = useRef<BoxRenderable | null>(null)\n const previousFocusedRef = useRef<Renderable | null>(null)\n const wasOpenRef = useRef(false)\n\n useEffect(() => {\n if (isOpen && !wasOpenRef.current) {\n previousFocusedRef.current = renderer.currentFocusedRenderable\n\n const focusTarget = modalRef.current\n if (focusTarget) {\n renderer.focusRenderable(focusTarget)\n }\n }\n\n if (!isOpen && wasOpenRef.current) {\n const previous = previousFocusedRef.current\n if (previous) {\n try {\n renderer.focusRenderable(previous)\n } catch {\n // Ignore restore failures if previous focus target was destroyed.\n }\n }\n }\n\n wasOpenRef.current = isOpen\n }, [isOpen, renderer])\n\n useScopedKeyboard(\n keyboardScopeId,\n (key) => {\n if (!isOpen) return false\n\n if (modalKeymap.match('close', key)) {\n onClose?.()\n return true\n }\n\n if (modalKeymap.match('trap', key)) {\n const focusTarget = modalRef.current\n if (focusTarget) {\n renderer.focusRenderable(focusTarget)\n }\n return true\n }\n\n return false\n },\n { active: isOpen, priority: 100 }\n )\n\n return (\n <OverlayPortal active={isOpen} priority={zIndex}>\n <box\n position='absolute'\n top={1}\n left={2}\n right={2}\n zIndex={zIndex}\n border\n padding={2}\n title={title}\n focusable\n ref={modalRef}\n style={{\n flexDirection: 'column',\n gap: 1,\n borderColor: tokens.accent,\n backgroundColor: tokens.backgroundMuted\n }}\n >\n {children}\n <text content={closeHint} fg={tokens.textMuted} />\n </box>\n </OverlayPortal>\n )\n}\n",
|
|
28
|
+
"import { useId, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { useScopedKeyboard } from '@bunli/runtime/app'\nimport { createKeyMatcher } from '@bunli/runtime/app'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport interface TabItem {\n key: string\n label: string\n content: ReactNode\n}\n\nexport interface TabsProps {\n tabs: TabItem[]\n initialKey?: string\n activeKey?: string\n onChange?: (key: string) => void\n scopeId?: string\n keyboardEnabled?: boolean\n}\n\nconst tabsKeymap = createKeyMatcher({\n previous: ['left', 'h'],\n next: ['right', 'l']\n})\n\nexport function Tabs({\n tabs,\n initialKey,\n activeKey,\n onChange,\n scopeId,\n keyboardEnabled = true\n}: TabsProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const keyboardScopeId = scopeId ?? `tabs:${reactScopeId}`\n const [internalKey, setInternalKey] = useState<string>(() => initialKey ?? tabs[0]?.key ?? '')\n const currentKey = activeKey ?? internalKey\n const currentIndex = Math.max(0, tabs.findIndex((tab) => tab.key === currentKey))\n\n const selectIndex = (index: number) => {\n const tab = tabs[index]\n if (!tab) return\n if (activeKey === undefined) {\n setInternalKey(tab.key)\n }\n onChange?.(tab.key)\n }\n\n useScopedKeyboard(\n keyboardScopeId,\n (key) => {\n if (tabs.length === 0) return false\n if (tabsKeymap.match('previous', key)) {\n const next = (currentIndex - 1 + tabs.length) % tabs.length\n selectIndex(next)\n return true\n }\n if (tabsKeymap.match('next', key)) {\n const next = (currentIndex + 1) % tabs.length\n selectIndex(next)\n return true\n }\n return false\n },\n { active: keyboardEnabled }\n )\n\n const activeTab = useMemo(() => tabs[currentIndex] ?? null, [tabs, currentIndex])\n\n return (\n <box style={{ flexDirection: 'column', gap: 1 }}>\n <box style={{ flexDirection: 'row', gap: 2 }}>\n {tabs.map((tab, index) => {\n const isActive = index === currentIndex\n const label = isActive ? `[${tab.label}]` : tab.label\n return (\n <text\n key={tab.key}\n content={label}\n fg={isActive ? tokens.accent : tokens.textMuted}\n />\n )\n })}\n </box>\n <box border padding={1} style={{ borderColor: keyboardEnabled ? tokens.accent : tokens.border }}>\n {activeTab?.content ?? <text content=\"\" />}\n </box>\n </box>\n )\n}\n",
|
|
29
|
+
"import { useId, useState } from 'react'\nimport type { KeyEvent } from '@opentui/core'\nimport { useScopedKeyboard, createKeyMatcher, useTuiTheme } from '@bunli/runtime/app'\n\nexport interface ConfirmProps {\n message: string\n defaultValue?: boolean\n affirmativeLabel?: string\n negativeLabel?: string\n onConfirm?: (value: boolean) => void\n onAbort?: () => void\n scopeId?: string\n keyboardEnabled?: boolean\n}\n\nconst confirmKeymap = createKeyMatcher({\n toggle: ['left', 'right', 'h', 'l', 'tab'],\n affirm: ['y'],\n negate: ['n', 'q'],\n submit: ['enter'],\n abort: ['escape']\n})\n\nexport function Confirm({\n message,\n defaultValue = false,\n affirmativeLabel = 'Yes',\n negativeLabel = 'No',\n onConfirm,\n onAbort,\n scopeId,\n keyboardEnabled = true\n}: ConfirmProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const keyboardScopeId = scopeId ?? `confirm:${reactScopeId}`\n const [selected, setSelected] = useState(defaultValue)\n\n useScopedKeyboard(\n keyboardScopeId,\n (key: KeyEvent) => {\n if (confirmKeymap.match('toggle', key)) {\n setSelected((prev) => !prev)\n return true\n }\n\n if (confirmKeymap.match('affirm', key)) {\n setSelected(true)\n return true\n }\n\n if (confirmKeymap.match('negate', key)) {\n setSelected(false)\n return true\n }\n\n if (confirmKeymap.match('submit', key)) {\n setSelected((current) => {\n onConfirm?.(current)\n return current\n })\n return true\n }\n\n if (confirmKeymap.match('abort', key)) {\n onAbort?.()\n return true\n }\n\n return false\n },\n { active: keyboardEnabled }\n )\n\n return (\n <box style={{ flexDirection: 'row', gap: 1 }}>\n <text content={message} fg={tokens.textPrimary} />\n <text content={selected ? `[${affirmativeLabel}]` : ` ${affirmativeLabel} `} fg={selected ? tokens.accent : tokens.textMuted} />\n <text content=\"/\" fg={tokens.textMuted} />\n <text content={!selected ? `[${negativeLabel}]` : ` ${negativeLabel} `} fg={!selected ? tokens.accent : tokens.textMuted} />\n </box>\n )\n}\n",
|
|
30
|
+
"import { useCallback, useEffect, useId, useMemo, useState } from 'react'\nimport type { KeyEvent } from '@opentui/core'\nimport { useScopedKeyboard } from '@bunli/runtime/app'\nimport { createKeyMatcher } from '@bunli/runtime/app'\nimport { useTuiTheme } from '@bunli/runtime/app'\nimport { displayWidth, formatFixedWidth, type TextOverflowMode } from '@bunli/runtime/app'\n\nexport interface MenuItem {\n key: string\n label: string\n description?: string\n disabled?: boolean\n}\n\nexport interface MenuProps {\n title?: string\n items: MenuItem[]\n onSelect?: (key: string) => void\n initialIndex?: number\n scopeId?: string\n keyboardEnabled?: boolean\n maxLineWidth?: number\n overflow?: TextOverflowMode\n boxed?: boolean\n onKeyPress?: (key: KeyEvent, context: {\n index: number\n items: MenuItem[]\n setIndex: (nextIndex: number) => void\n select: (itemKey: string) => void\n }) => boolean\n}\n\nconst menuKeymap = createKeyMatcher({\n up: ['up', 'k'],\n down: ['down', 'j'],\n select: ['enter']\n})\n\nfunction isUpKey(key: KeyEvent) {\n const name = key.name?.toLowerCase()\n return menuKeymap.match('up', key) || name === 'arrowup' || key.sequence === '\\u001b[A'\n}\n\nfunction isDownKey(key: KeyEvent) {\n const name = key.name?.toLowerCase()\n return menuKeymap.match('down', key) || name === 'arrowdown' || key.sequence === '\\u001b[B'\n}\n\nfunction isSelectKey(key: KeyEvent) {\n const name = key.name?.toLowerCase()\n return (\n menuKeymap.match('select', key) ||\n name === 'return' ||\n key.sequence === '\\r' ||\n key.sequence === '\\n'\n )\n}\n\nexport function Menu({\n title,\n items,\n onSelect,\n initialIndex = 0,\n scopeId,\n keyboardEnabled = true,\n maxLineWidth,\n overflow = 'ellipsis',\n boxed = true,\n onKeyPress\n}: MenuProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const keyboardScopeId = scopeId ?? `menu:${reactScopeId}`\n const [index, setIndex] = useState(initialIndex)\n const lineWidth = useMemo(() => {\n const contentLineWidth = Math.max(\n 8,\n ...items.map((entry) => {\n const entryDisabled = entry.disabled ? ' [disabled]' : ''\n return displayWidth(`> ${entry.label}${entryDisabled}${entry.description ? ` - ${entry.description}` : ''}`)\n })\n )\n\n if (typeof maxLineWidth === 'number') {\n return Math.max(8, Math.min(maxLineWidth, contentLineWidth))\n }\n\n return contentLineWidth\n }, [items, maxLineWidth])\n const clearLineWidth = useMemo(() => {\n if (boxed) return lineWidth\n if (typeof maxLineWidth === 'number') return lineWidth\n const terminalWidth = process.stdout.columns ?? 80\n return Math.max(lineWidth, terminalWidth - 2)\n }, [boxed, lineWidth, maxLineWidth])\n\n useEffect(() => {\n setIndex((prev) => {\n if (items.length === 0) return 0\n\n const bounded = ((prev % items.length) + items.length) % items.length\n if (!items[bounded]?.disabled) {\n return bounded\n }\n\n for (let offset = 1; offset < items.length; offset += 1) {\n const next = (bounded + offset) % items.length\n if (!items[next]?.disabled) {\n return next\n }\n }\n return bounded\n })\n }, [items])\n\n const move = useCallback((delta: number) => {\n if (items.length === 0) return\n\n setIndex((prev) => {\n for (let step = 0; step < items.length; step += 1) {\n const next = (prev + delta * (step + 1) + items.length) % items.length\n if (!items[next]?.disabled) {\n return next\n }\n }\n\n return prev\n })\n }, [items])\n\n useScopedKeyboard(\n keyboardScopeId,\n (key) => {\n if (\n onKeyPress?.(key, {\n index,\n items,\n setIndex: (nextIndex) => setIndex(nextIndex),\n select: (itemKey) => onSelect?.(itemKey)\n })\n ) {\n return true\n }\n\n if (isUpKey(key)) {\n move(-1)\n return true\n }\n\n if (isDownKey(key)) {\n move(1)\n return true\n }\n\n if (isSelectKey(key)) {\n const item = items[index]\n if (!item || item.disabled) return false\n onSelect?.(item.key)\n return true\n }\n\n return false\n },\n { active: keyboardEnabled }\n )\n\n const rows = (\n <>\n {title\n ? <text content={formatFixedWidth(title, clearLineWidth, { overflow: 'clip' })} fg={tokens.textPrimary} />\n : null}\n {items.map((item, itemIndex) => {\n const active = itemIndex === index\n const prefix = active ? '>\\u00a0' : '\\u00a0\\u00a0'\n const disabled = item.disabled ? ' [disabled]' : ''\n const rawLine = `${prefix}${item.label}${disabled}${item.description ? ` - ${item.description}` : ''}`\n\n return (\n <text\n key={item.key}\n content={formatFixedWidth(rawLine, clearLineWidth, { overflow })}\n fg={item.disabled ? tokens.textMuted : active ? tokens.accent : tokens.textPrimary}\n />\n )\n })}\n </>\n )\n\n if (!boxed) {\n return <box style={{ flexDirection: 'column', gap: 0 }}>{rows}</box>\n }\n\n return (\n <box\n border\n padding={1}\n style={{\n flexDirection: 'column',\n gap: 1,\n borderColor: keyboardEnabled ? tokens.accent : tokens.border\n }}\n >\n {rows}\n </box>\n )\n}\n",
|
|
31
|
+
"import { useEffect, useId, useMemo, useState } from 'react'\nimport type { KeyEvent } from '@opentui/core'\nimport { createKeyMatcher, displayWidth, formatFixedWidth, useScopedKeyboard, useTuiTheme } from '@bunli/runtime/app'\nimport type { TextOverflowMode } from '@bunli/runtime/app'\n\nexport interface NavListItem {\n key: string\n label: string\n description?: string\n section?: string\n meta?: string\n disabled?: boolean\n}\n\nexport interface NavListProps {\n id?: string\n title?: string\n items: NavListItem[]\n value?: string\n defaultValue?: string\n onChange?: (key: string) => void\n onSelect?: (key: string) => void\n onFocusRequest?: () => void\n scopeId?: string\n keyboardEnabled?: boolean\n pointerEnabled?: boolean\n selectOnMove?: boolean\n maxLineWidth?: number\n overflow?: TextOverflowMode\n boxed?: boolean\n compact?: boolean\n wrapLabels?: boolean\n maxLabelLines?: number\n}\n\nconst navKeymap = createKeyMatcher({\n up: ['up', 'k'],\n down: ['down', 'j'],\n select: ['enter']\n})\n\nexport function moveSelectableNavIndex(items: NavListItem[], currentIndex: number, delta: number) {\n if (items.length === 0) return -1\n\n for (let step = 0; step < items.length; step += 1) {\n const nextIndex = (currentIndex + delta * (step + 1) + items.length) % items.length\n if (!items[nextIndex]?.disabled) {\n return nextIndex\n }\n }\n\n return currentIndex\n}\n\nexport function findFirstSelectableKey(items: NavListItem[]) {\n return items.find((item) => !item.disabled)?.key ?? items[0]?.key ?? ''\n}\n\nexport function resolveNavListModeWidth(maxLineWidth: number | undefined) {\n const terminalWidth = process.stdout.columns ?? 80\n return Math.max(18, maxLineWidth ?? (terminalWidth - 4))\n}\n\nfunction chunkWord(word: string, maxWidth: number): string[] {\n if (maxWidth <= 1 || displayWidth(word) <= maxWidth) {\n return [word]\n }\n\n const chunks: string[] = []\n let current = ''\n\n for (const char of word) {\n const next = `${current}${char}`\n if (current.length > 0 && displayWidth(next) > maxWidth) {\n chunks.push(current)\n current = char\n continue\n }\n current = next\n }\n\n if (current.length > 0) {\n chunks.push(current)\n }\n\n return chunks\n}\n\nfunction wrapNavLabelLines(\n label: string,\n lineWidth: number,\n prefix: string,\n maxLines: number,\n overflow: TextOverflowMode\n): string[] {\n if (maxLines <= 1) {\n return [formatFixedWidth(`${prefix}${label}`, lineWidth, { overflow })]\n }\n\n const restPrefix = '\\u00a0\\u00a0'\n const firstWidth = Math.max(1, lineWidth - displayWidth(prefix))\n const restWidth = Math.max(1, lineWidth - displayWidth(restPrefix))\n const rawWords = label.trim().split(/\\s+/).filter(Boolean)\n const words = rawWords.flatMap((word) => chunkWord(word, Math.max(firstWidth, restWidth)))\n\n if (words.length === 0) {\n return [formatFixedWidth(prefix, lineWidth, { overflow: 'clip' })]\n }\n\n const lines: string[] = []\n let current = ''\n let currentWidth = firstWidth\n\n for (const [wordIndex, word] of words.entries()) {\n const candidate = current.length > 0 ? `${current} ${word}` : word\n if (current.length > 0 && displayWidth(candidate) > currentWidth) {\n lines.push(current)\n if (lines.length === maxLines - 1) {\n const remaining = [word, ...words.slice(wordIndex + 1)].join(' ')\n lines.push(remaining)\n return lines.map((line, index) =>\n formatFixedWidth(`${index === 0 ? prefix : restPrefix}${line}`, lineWidth, { overflow })\n )\n }\n current = word\n currentWidth = restWidth\n continue\n }\n current = candidate\n }\n\n if (current.length > 0) {\n lines.push(current)\n }\n\n return lines.slice(0, maxLines).map((line, index) =>\n formatFixedWidth(`${index === 0 ? prefix : restPrefix}${line}`, lineWidth, { overflow })\n )\n}\n\nexport function NavList({\n id,\n title,\n items,\n value,\n defaultValue,\n onChange,\n onSelect,\n onFocusRequest,\n scopeId,\n keyboardEnabled = true,\n pointerEnabled = true,\n selectOnMove = true,\n maxLineWidth,\n overflow = 'ellipsis',\n boxed = false,\n compact = false,\n wrapLabels = false,\n maxLabelLines = 2\n}: NavListProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const keyboardScopeId = scopeId ?? `nav-list:${reactScopeId}`\n const [internalValue, setInternalValue] = useState(() => defaultValue ?? findFirstSelectableKey(items))\n const [hoveredKey, setHoveredKey] = useState<string | null>(null)\n const currentValue = value ?? internalValue\n\n useEffect(() => {\n if (items.length === 0) {\n if (value === undefined) {\n setInternalValue('')\n }\n return\n }\n\n const match = items.find((item) => item.key === currentValue && !item.disabled)\n if (match) return\n\n if (value === undefined) {\n setInternalValue(findFirstSelectableKey(items))\n }\n }, [currentValue, items, value])\n\n const selectedIndex = useMemo(() => {\n const matchedIndex = items.findIndex((item) => item.key === currentValue)\n if (matchedIndex >= 0) return matchedIndex\n return Math.max(0, items.findIndex((item) => !item.disabled))\n }, [currentValue, items])\n\n const lineWidth = useMemo(() => resolveNavListModeWidth(maxLineWidth), [maxLineWidth])\n\n const selectKey = (nextKey: string, options: { confirm?: boolean } = {}) => {\n if (!nextKey) return\n const item = items.find((candidate) => candidate.key === nextKey)\n if (!item || item.disabled) return\n\n if (value === undefined) {\n setInternalValue(nextKey)\n }\n\n onChange?.(nextKey)\n if (options.confirm) {\n onSelect?.(nextKey)\n }\n }\n\n useScopedKeyboard(\n keyboardScopeId,\n (key: KeyEvent) => {\n if (items.length === 0) return false\n\n if (navKeymap.match('up', key)) {\n const nextIndex = moveSelectableNavIndex(items, selectedIndex, -1)\n const nextKey = items[nextIndex]?.key\n if (nextKey) {\n if (selectOnMove) {\n selectKey(nextKey)\n } else if (value === undefined) {\n setInternalValue(nextKey)\n }\n }\n return true\n }\n\n if (navKeymap.match('down', key)) {\n const nextIndex = moveSelectableNavIndex(items, selectedIndex, 1)\n const nextKey = items[nextIndex]?.key\n if (nextKey) {\n if (selectOnMove) {\n selectKey(nextKey)\n } else if (value === undefined) {\n setInternalValue(nextKey)\n }\n }\n return true\n }\n\n if (navKeymap.match('select', key)) {\n const selected = items[selectedIndex]\n if (!selected || selected.disabled) return false\n selectKey(selected.key, { confirm: true })\n return true\n }\n\n return false\n },\n { active: keyboardEnabled }\n )\n\n let previousSection: string | undefined\n\n const content = (\n <>\n {title ? <text content={formatFixedWidth(title, lineWidth, { overflow: 'clip' })} fg={tokens.textMuted} /> : null}\n {items.map((item) => {\n const selected = item.key === currentValue\n const hovered = item.key === hoveredKey\n const sectionLabel = item.section && item.section !== previousSection ? item.section : undefined\n previousSection = item.section\n\n const prefix = selected ? '>\\u00a0' : '\\u00a0\\u00a0'\n const metaWidth = item.meta ? displayWidth(item.meta) + 1 : 0\n const labelWidth = Math.max(8, lineWidth - 2 - metaWidth)\n const labelLines = wrapLabels && !item.meta\n ? wrapNavLabelLines(item.label, labelWidth, prefix, maxLabelLines, overflow)\n : [formatFixedWidth(`${prefix}${item.label}`, labelWidth, { overflow })]\n\n return (\n <box key={item.key} style={{ flexDirection: 'column', gap: compact ? 0 : 1 }}>\n {sectionLabel ? <text content={sectionLabel} fg={tokens.textMuted} /> : null}\n <box\n id={id ? `${id}--row-${item.key}` : undefined}\n width='100%'\n paddingLeft={boxed ? 0 : 1}\n paddingRight={boxed ? 0 : 1}\n style={{\n flexDirection: 'column',\n gap: compact ? 0 : 1,\n backgroundColor: selected || hovered ? tokens.backgroundMuted : undefined\n }}\n onMouseDown={pointerEnabled ? () => {\n onFocusRequest?.()\n selectKey(item.key, { confirm: true })\n } : undefined}\n onMouseOver={pointerEnabled ? () => {\n setHoveredKey(item.key)\n } : undefined}\n onMouseOut={pointerEnabled ? () => {\n setHoveredKey((current) => current === item.key ? null : current)\n } : undefined}\n >\n <box style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n {labelLines.map((line, lineIndex) => (\n <text\n key={`${item.key}-label-${lineIndex}`}\n content={line}\n fg={item.disabled ? tokens.textMuted : selected ? tokens.accent : hovered ? tokens.textPrimary : tokens.textPrimary}\n />\n ))}\n </box>\n {item.meta\n ? <text content={item.meta} fg={selected ? tokens.accent : tokens.textMuted} />\n : null}\n </box>\n {!compact && item.description\n ? <text content={formatFixedWidth(item.description, Math.max(14, lineWidth - 2), { overflow })} fg={tokens.textMuted} />\n : null}\n </box>\n </box>\n )\n })}\n </>\n )\n\n if (!boxed) {\n return <box id={id} width='100%' style={{ flexDirection: 'column', gap: compact ? 0 : 1 }}>{content}</box>\n }\n\n return (\n <box\n id={id}\n width='100%'\n border\n padding={1}\n style={{\n flexDirection: 'column',\n gap: compact ? 0 : 1,\n borderColor: keyboardEnabled ? tokens.accent : tokens.border\n }}\n >\n {content}\n </box>\n )\n}\n",
|
|
32
|
+
"import { useEffect, useId, useMemo, useState } from 'react'\nimport { useScopedKeyboard } from '@bunli/runtime/app'\nimport { createKeyMatcher } from '@bunli/runtime/app'\nimport { useTuiTheme } from '@bunli/runtime/app'\nimport { displayWidth, formatFixedWidth, type TextOverflowMode } from '@bunli/runtime/app'\n\nexport interface CommandPaletteItem {\n key: string\n label: string\n hint?: string\n}\n\nexport interface CommandPaletteProps {\n items: CommandPaletteItem[]\n placeholder?: string\n onSelect?: (key: string) => void\n scopeId?: string\n keyboardEnabled?: boolean\n inputFocused?: boolean\n maxLineWidth?: number\n overflow?: TextOverflowMode\n}\n\nconst paletteKeymap = createKeyMatcher({\n up: ['up', 'k'],\n down: ['down', 'j'],\n select: ['enter']\n})\n\nexport function CommandPalette({\n items,\n placeholder = 'Type to filter commands...',\n onSelect,\n scopeId,\n keyboardEnabled = true,\n inputFocused = true,\n maxLineWidth,\n overflow = 'ellipsis'\n}: CommandPaletteProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const keyboardScopeId = scopeId ?? `command-palette:${reactScopeId}`\n const [query, setQuery] = useState('')\n const [selectedIndex, setSelectedIndex] = useState(0)\n\n const filtered = useMemo(\n () =>\n items.filter((item) => item.label.toLowerCase().includes(query.toLowerCase())),\n [items, query]\n )\n\n useEffect(() => {\n setSelectedIndex((prev) => {\n if (filtered.length === 0) return 0\n return Math.min(prev, filtered.length - 1)\n })\n }, [filtered.length])\n const lineWidth = useMemo(() => {\n const contentLineWidth = Math.max(\n 8,\n ...items.map((item) => displayWidth(`> ${item.label}${item.hint ? ` - ${item.hint}` : ''}`))\n )\n if (typeof maxLineWidth === 'number') {\n return Math.max(8, Math.min(maxLineWidth, contentLineWidth))\n }\n return contentLineWidth\n }, [items, maxLineWidth])\n\n useScopedKeyboard(\n keyboardScopeId,\n (key) => {\n if (filtered.length === 0) return false\n\n if (paletteKeymap.match('up', key)) {\n setSelectedIndex((prev) => (prev - 1 + filtered.length) % filtered.length)\n return true\n }\n\n if (paletteKeymap.match('down', key)) {\n setSelectedIndex((prev) => (prev + 1) % filtered.length)\n return true\n }\n\n if (paletteKeymap.match('select', key)) {\n const item = filtered[selectedIndex]\n if (!item) return false\n onSelect?.(item.key)\n return true\n }\n\n return false\n },\n { active: keyboardEnabled }\n )\n\n return (\n <box\n border\n padding={1}\n style={{\n flexDirection: 'column',\n gap: 1,\n borderColor: keyboardEnabled ? tokens.accent : tokens.border\n }}\n >\n <input\n value={query}\n placeholder={placeholder}\n onInput={setQuery}\n focused={inputFocused}\n style={{ focusedBackgroundColor: tokens.backgroundMuted }}\n />\n <box style={{ flexDirection: 'column', gap: 1 }}>\n {filtered.length === 0 ? (\n <text content=\"No commands found\" fg={tokens.textMuted} />\n ) : (\n Array.from({ length: filtered.length }, (_, rowIndex) => {\n const item = filtered[rowIndex]\n if (!item) {\n return <text key={`palette-empty-${rowIndex}`} content={formatFixedWidth('', lineWidth, { overflow })} fg={tokens.textPrimary} />\n }\n\n const active = rowIndex === selectedIndex\n const rawLine = `${active ? '>' : ' '} ${item.label}${item.hint ? ` - ${item.hint}` : ''}`\n return (\n <text\n key={item.key}\n content={formatFixedWidth(rawLine, lineWidth, { overflow })}\n fg={active ? tokens.accent : tokens.textPrimary}\n />\n )\n })\n )}\n </box>\n </box>\n )\n}\n",
|
|
33
|
+
"import { useEffect, useId, useMemo, useState } from 'react'\nimport type { KeyEvent } from '@opentui/core'\nimport { useScopedKeyboard, createKeyMatcher, useTuiTheme } from '@bunli/runtime/app'\nimport Fuse, { type FuseResultMatch } from 'fuse.js'\n\nexport interface FilterOption {\n label: string\n value: string\n description?: string\n}\n\nexport interface FilterProps {\n options: FilterOption[]\n placeholder?: string\n prompt?: string\n mode?: 'single' | 'multiple'\n limit?: number\n fuzzy?: boolean\n reverse?: boolean\n selectIfOne?: boolean\n height?: number\n onSelect?: (selected: FilterOption[]) => void\n onAbort?: () => void\n scopeId?: string\n keyboardEnabled?: boolean\n}\n\ninterface FilterResult {\n item: FilterOption\n matches?: ReadonlyArray<FuseResultMatch>\n refIndex: number\n}\n\nconst filterKeymap = createKeyMatcher({\n up: ['up'],\n down: ['down'],\n toggle: ['tab'],\n selectAll: ['ctrl+a'],\n submit: ['enter'],\n abort: ['escape']\n})\n\nexport function Filter({\n options,\n placeholder = 'Type to filter...',\n prompt = '> ',\n mode = 'single',\n limit = 0,\n fuzzy = true,\n reverse = false,\n selectIfOne = false,\n height = 10,\n onSelect,\n onAbort,\n scopeId,\n keyboardEnabled = true\n}: FilterProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const keyboardScopeId = scopeId ?? `filter:${reactScopeId}`\n const [query, setQuery] = useState('')\n const [cursorIndex, setCursorIndex] = useState(0)\n const [selectedIndices, setSelectedIndices] = useState<Set<number>>(new Set())\n\n const fuse = useMemo(\n () =>\n new Fuse(options, {\n keys: ['label'],\n includeMatches: true,\n threshold: 0.3,\n ignoreLocation: true\n }),\n [options]\n )\n\n const filtered: FilterResult[] = useMemo(() => {\n if (!query) {\n return options.map((item, index) => ({\n item,\n matches: [] as FuseResultMatch[],\n refIndex: index\n }))\n }\n if (fuzzy) {\n return fuse.search(query).map((result) => ({\n item: result.item,\n matches: result.matches ?? [],\n refIndex: result.refIndex\n }))\n }\n return options\n .map((item, index) => ({ item, refIndex: index }))\n .filter(({ item }) => item.label.toLowerCase().includes(query.toLowerCase()))\n }, [options, query, fuzzy, fuse])\n\n const orderedFiltered = useMemo(() => {\n if (reverse) return [...filtered].reverse()\n return filtered\n }, [filtered, reverse])\n\n // Bound cursor when filtered list changes\n useEffect(() => {\n setCursorIndex((prev) => {\n if (orderedFiltered.length === 0) return 0\n return Math.min(prev, orderedFiltered.length - 1)\n })\n }, [orderedFiltered.length])\n\n // Auto-select if only one match\n useEffect(() => {\n if (selectIfOne && orderedFiltered.length === 1 && query.length > 0) {\n onSelect?.([orderedFiltered[0]!.item])\n }\n }, [selectIfOne, orderedFiltered, query, onSelect])\n\n useScopedKeyboard(\n keyboardScopeId,\n (key: KeyEvent) => {\n if (filterKeymap.match('abort', key)) {\n onAbort?.()\n return true\n }\n\n if (filterKeymap.match('submit', key)) {\n if (mode === 'multiple') {\n const selected = Array.from(selectedIndices)\n .map((refIndex) => options[refIndex])\n .filter((opt): opt is FilterOption => opt !== undefined)\n onSelect?.(selected)\n } else {\n const result = orderedFiltered[cursorIndex]\n if (result) {\n onSelect?.([result.item])\n }\n }\n return true\n }\n\n if (filterKeymap.match('up', key)) {\n setCursorIndex((prev) => {\n if (orderedFiltered.length === 0) return 0\n return (prev - 1 + orderedFiltered.length) % orderedFiltered.length\n })\n return true\n }\n\n if (filterKeymap.match('down', key)) {\n setCursorIndex((prev) => {\n if (orderedFiltered.length === 0) return 0\n return (prev + 1) % orderedFiltered.length\n })\n return true\n }\n\n if (filterKeymap.match('toggle', key) && mode === 'multiple') {\n const result = orderedFiltered[cursorIndex]\n if (!result) return false\n setSelectedIndices((prev) => {\n const next = new Set(prev)\n if (next.has(result.refIndex)) {\n next.delete(result.refIndex)\n } else {\n if (limit > 0 && next.size >= limit) return prev\n next.add(result.refIndex)\n }\n return next\n })\n return true\n }\n\n if (filterKeymap.match('selectAll', key) && mode === 'multiple') {\n setSelectedIndices((prev) => {\n if (prev.size === orderedFiltered.length) {\n return new Set()\n }\n const allIndices = orderedFiltered.map((r) => r.refIndex)\n if (limit > 0) {\n return new Set(allIndices.slice(0, limit))\n }\n return new Set(allIndices)\n })\n return true\n }\n\n return false\n },\n { active: keyboardEnabled }\n )\n\n // Pagination\n const pageOffset = Math.max(0, Math.min(cursorIndex - Math.floor(height / 2), orderedFiltered.length - height))\n const visibleStart = Math.max(0, pageOffset)\n const visibleResults = orderedFiltered.slice(visibleStart, visibleStart + height)\n\n return (\n <box style={{ flexDirection: 'column', gap: 0 }}>\n {/* Search input */}\n <box style={{ flexDirection: 'row' }}>\n <text content={prompt} fg={tokens.accent} />\n <input\n value={query}\n placeholder={placeholder}\n onInput={setQuery}\n focused={keyboardEnabled}\n style={{ focusedBackgroundColor: tokens.backgroundMuted }}\n />\n </box>\n\n {/* Filtered results */}\n <box style={{ flexDirection: 'column' }}>\n {visibleResults.map((result, visIndex) => {\n const absoluteIndex = visIndex + visibleStart\n const isCursor = absoluteIndex === cursorIndex\n const isSelected = selectedIndices.has(result.refIndex)\n const prefix = isCursor ? '> ' : ' '\n const marker = mode === 'multiple' ? (isSelected ? '[x] ' : '[ ] ') : ''\n const desc = result.item.description ? ` - ${result.item.description}` : ''\n\n return (\n <text\n key={result.item.value}\n content={`${prefix}${marker}${result.item.label}${desc}`}\n fg={isCursor ? tokens.accent : tokens.textPrimary}\n />\n )\n })}\n </box>\n\n {/* Count indicator */}\n <text content={`${filtered.length}/${options.length}`} fg={tokens.textMuted} />\n </box>\n )\n}\n",
|
|
34
|
+
"import { useEffect, useId, useMemo, useState } from 'react'\nimport { useScopedKeyboard } from '@bunli/runtime/app'\nimport { createKeyMatcher } from '@bunli/runtime/app'\nimport { useTuiTheme } from '@bunli/runtime/app'\nimport { displayWidth, formatFixedWidth, type TextOverflowMode } from '@bunli/runtime/app'\n\nexport interface DataTableColumn {\n key: string\n label: string\n}\n\nexport interface DataTableProps {\n columns: DataTableColumn[]\n rows: Array<Record<string, string | number | boolean | null | undefined>>\n onRowSelect?: (row: Record<string, string | number | boolean | null | undefined>) => void\n scopeId?: string\n keyboardEnabled?: boolean\n maxLineWidth?: number\n fillWidth?: boolean\n overflow?: TextOverflowMode\n}\n\nconst dataTableKeymap = createKeyMatcher({\n sortPrevious: ['left', 'h'],\n sortNext: ['right', 'l'],\n rowPrevious: ['up', 'k'],\n rowNext: ['down', 'j'],\n select: ['enter']\n})\n\nexport function DataTable({\n columns,\n rows,\n onRowSelect,\n scopeId,\n keyboardEnabled = true,\n maxLineWidth,\n fillWidth = false,\n overflow = 'ellipsis'\n}: DataTableProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const keyboardScopeId = scopeId ?? `datatable:${reactScopeId}`\n const [sortIndex, setSortIndex] = useState(0)\n const [selectedRowIndex, setSelectedRowIndex] = useState(0)\n\n const sortColumn = columns[sortIndex]?.key\n\n const sortedRows = useMemo(() => {\n if (!sortColumn) return rows\n const copy = [...rows]\n copy.sort((a, b) => String(a[sortColumn] ?? '').localeCompare(String(b[sortColumn] ?? '')))\n return copy\n }, [rows, sortColumn])\n\n useEffect(() => {\n setSortIndex((prev) => {\n if (columns.length === 0) return 0\n return Math.min(prev, columns.length - 1)\n })\n }, [columns.length])\n\n useEffect(() => {\n setSelectedRowIndex((prev) => {\n if (sortedRows.length === 0) return 0\n return Math.min(prev, sortedRows.length - 1)\n })\n }, [sortedRows.length])\n\n useScopedKeyboard(\n keyboardScopeId,\n (key) => {\n if (dataTableKeymap.match('sortPrevious', key)) {\n if (columns.length === 0) return false\n setSortIndex((prev) => (prev - 1 + columns.length) % columns.length)\n return true\n }\n\n if (dataTableKeymap.match('sortNext', key)) {\n if (columns.length === 0) return false\n setSortIndex((prev) => (prev + 1) % columns.length)\n return true\n }\n\n if (dataTableKeymap.match('rowPrevious', key)) {\n if (sortedRows.length === 0) return false\n setSelectedRowIndex((prev) => Math.max(0, prev - 1))\n return true\n }\n\n if (dataTableKeymap.match('rowNext', key)) {\n if (sortedRows.length === 0) return false\n setSelectedRowIndex((prev) => Math.min(sortedRows.length - 1, prev + 1))\n return true\n }\n\n if (dataTableKeymap.match('select', key)) {\n const row = sortedRows[selectedRowIndex]\n if (!row) return false\n onRowSelect?.(row)\n return true\n }\n\n return false\n },\n { active: keyboardEnabled }\n )\n\n const widths = columns.map((column) =>\n Math.max(\n displayWidth(column.label),\n ...sortedRows.map((row) => displayWidth(String(row[column.key] ?? ''))),\n 1\n )\n )\n\n const rawHeader = columns\n .map((column, index) => {\n const decorated = index === sortIndex ? `${column.label}*` : column.label\n return formatFixedWidth(decorated, widths[index] ?? displayWidth(column.label), { overflow })\n })\n .join(' | ')\n const rawSeparator = widths.map((width) => '-'.repeat(width)).join('-+-')\n const rawRowLines = sortedRows.map((row) => columns\n .map((column, index) => formatFixedWidth(String(row[column.key] ?? ''), widths[index] ?? 1, { overflow }))\n .join(' | '))\n const contentWidth = Math.max(\n displayWidth(rawHeader),\n displayWidth(rawSeparator),\n ...rawRowLines.map((line) => displayWidth(line)),\n 1\n )\n const finalLineWidth = Math.max(\n 8,\n fillWidth && typeof maxLineWidth === 'number'\n ? maxLineWidth\n : Math.min(maxLineWidth ?? Number.POSITIVE_INFINITY, contentWidth)\n )\n const header = formatFixedWidth(rawHeader, finalLineWidth, { overflow })\n const separator = formatFixedWidth(rawSeparator, finalLineWidth, { overflow })\n\n const footer = formatFixedWidth('Arrows: navigate/sort | Enter: select row', finalLineWidth, { overflow })\n\n return (\n <box\n border\n padding={1}\n style={{\n flexDirection: 'column',\n gap: 1,\n borderColor: keyboardEnabled ? tokens.accent : tokens.border\n }}\n >\n <text content={header} fg={tokens.textPrimary} />\n <text content={separator} fg={tokens.borderMuted} />\n {sortedRows.length === 0 ? (\n <text content={formatFixedWidth('No rows', finalLineWidth, { overflow })} fg={tokens.textMuted} />\n ) : (\n sortedRows.map((row, rowIndex) => {\n const line = formatFixedWidth(rawRowLines[rowIndex] ?? '', finalLineWidth, { overflow })\n const active = rowIndex === selectedRowIndex\n return (\n <text\n key={`datatable-row-${rowIndex}`}\n content={formatFixedWidth(`${active ? '>' : ' '} ${line}`, finalLineWidth + 2, { overflow })}\n fg={active ? tokens.accent : tokens.textPrimary}\n />\n )\n })\n )}\n <text content={footer} fg={tokens.textMuted} />\n </box>\n )\n}\n",
|
|
35
|
+
"import { useEffect, useMemo, useState, type ReactNode } from 'react'\nimport { useTuiTheme } from '@bunli/runtime/app'\nimport { useTerminalDimensions } from '@opentui/react'\n\nexport type SidebarLayoutMode = 'auto' | 'wide' | 'medium' | 'narrow'\nexport type SidebarLayoutResolvedMode = Exclude<SidebarLayoutMode, 'auto'>\nexport type SidebarLayoutPane = 'sidebar' | 'content' | 'inspector'\n\nexport interface SidebarLayoutPaneLabels {\n sidebar: string\n content: string\n inspector?: string\n}\n\nexport interface SidebarLayoutProps {\n header?: ReactNode\n status?: ReactNode\n sidebar: ReactNode\n content: ReactNode\n inspector?: ReactNode\n mode?: SidebarLayoutMode\n activePane?: SidebarLayoutPane\n defaultActivePane?: SidebarLayoutPane\n onActivePaneChange?: (pane: SidebarLayoutPane) => void\n paneLabels?: SidebarLayoutPaneLabels\n sidebarWidth?: number\n inspectorWidth?: number\n wideMinWidth?: number\n mediumMinWidth?: number\n gap?: number\n height?: number | `${number}%` | 'auto'\n}\n\nexport function resolveSidebarLayoutMode(\n terminalWidth: number,\n mode: SidebarLayoutMode,\n breakpoints: { mediumMinWidth: number; wideMinWidth: number }\n): SidebarLayoutResolvedMode {\n if (mode !== 'auto') return mode\n if (terminalWidth >= breakpoints.wideMinWidth) return 'wide'\n if (terminalWidth >= breakpoints.mediumMinWidth) return 'medium'\n return 'narrow'\n}\n\nfunction buildPaneOrder(inspector?: ReactNode): SidebarLayoutPane[] {\n return inspector ? ['sidebar', 'content', 'inspector'] : ['sidebar', 'content']\n}\n\nexport function SidebarLayout({\n header,\n status,\n sidebar,\n content,\n inspector,\n mode = 'auto',\n activePane,\n defaultActivePane = 'content',\n onActivePaneChange,\n paneLabels,\n sidebarWidth = 28,\n inspectorWidth = 38,\n wideMinWidth = 132,\n mediumMinWidth = 100,\n gap = 2,\n height = '100%'\n}: SidebarLayoutProps) {\n const { width: terminalWidth = 140 } = useTerminalDimensions()\n const { tokens } = useTuiTheme()\n const resolvedMode = resolveSidebarLayoutMode(terminalWidth, mode, {\n mediumMinWidth,\n wideMinWidth\n })\n const paneOrder = useMemo(() => buildPaneOrder(inspector), [inspector])\n const [internalActivePane, setInternalActivePane] = useState<SidebarLayoutPane>(defaultActivePane)\n const currentActivePane = paneOrder.includes(activePane ?? internalActivePane)\n ? (activePane ?? internalActivePane)\n : paneOrder[0] ?? 'content'\n\n useEffect(() => {\n if (!paneOrder.includes(currentActivePane)) {\n const nextPane = paneOrder[0] ?? 'content'\n if (activePane === undefined) {\n setInternalActivePane(nextPane)\n }\n onActivePaneChange?.(nextPane)\n }\n }, [activePane, currentActivePane, onActivePaneChange, paneOrder])\n\n const setPane = (pane: SidebarLayoutPane) => {\n if (!paneOrder.includes(pane)) return\n if (activePane === undefined) {\n setInternalActivePane(pane)\n }\n onActivePaneChange?.(pane)\n }\n\n const selectorRow = (panes: SidebarLayoutPane[], selectedPane: SidebarLayoutPane = currentActivePane) => (\n <box\n paddingLeft={1}\n paddingRight={1}\n style={{ flexDirection: 'row', gap: 2, justifyContent: 'space-between' }}\n >\n {panes.map((pane) => {\n const label = pane === 'sidebar'\n ? paneLabels?.sidebar ?? 'Browse'\n : pane === 'content'\n ? paneLabels?.content ?? 'Preview'\n : paneLabels?.inspector ?? 'Info'\n const selected = pane === selectedPane\n\n return (\n <text\n key={pane}\n content={selected ? `[${label}]` : label}\n fg={selected ? tokens.accent : tokens.textMuted}\n onMouseDown={() => setPane(pane)}\n />\n )\n })}\n </box>\n )\n\n const renderNarrowSelector = resolvedMode === 'narrow'\n ? (\n selectorRow(paneOrder)\n )\n : null\n\n const mainBody = (() => {\n if (resolvedMode === 'wide') {\n return (\n <box style={{ flexDirection: 'row', gap, flexGrow: 1, height: '100%' }}>\n <box width={sidebarWidth} height='100%'>{sidebar}</box>\n <box style={{ flexGrow: 1 }} height='100%'>{content}</box>\n {inspector ? <box width={inspectorWidth} height='100%'>{inspector}</box> : null}\n </box>\n )\n }\n\n if (resolvedMode === 'medium') {\n const sidePane = currentActivePane === 'inspector' && inspector ? inspector : content\n return (\n <box style={{ flexDirection: 'row', gap, flexGrow: 1, height: '100%' }}>\n <box width={sidebarWidth} height='100%'>{sidebar}</box>\n <box style={{ flexDirection: 'column', gap: 1, flexGrow: 1 }} height='100%'>\n {inspector\n ? selectorRow(['content', 'inspector'], currentActivePane === 'inspector' ? 'inspector' : 'content')\n : null}\n <box style={{ flexGrow: 1 }} height='100%'>{sidePane}</box>\n </box>\n </box>\n )\n }\n\n const narrowPane = currentActivePane === 'sidebar'\n ? sidebar\n : currentActivePane === 'inspector' && inspector\n ? inspector\n : content\n\n return (\n <box style={{ flexDirection: 'column', gap: 1, flexGrow: 1, height: '100%' }}>\n {renderNarrowSelector}\n <box style={{ flexGrow: 1 }} height='100%'>\n {narrowPane}\n </box>\n </box>\n )\n })()\n\n return (\n <box height={height} style={{ flexDirection: 'column', gap: 1 }}>\n {header}\n <box style={{ flexDirection: 'column', gap: 1, flexGrow: 1 }}>\n {mainBody}\n </box>\n {status}\n </box>\n )\n}\n",
|
|
36
|
+
"import { useEffect, useState } from 'react'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport type SpinnerVariant = 'line' | 'dot' | 'minidot' | 'jump' | 'pulse' | 'points' | 'globe' | 'moon' | 'monkey' | 'meter' | 'hamburger'\n\nexport const SPINNERS: Record<SpinnerVariant, { frames: string[]; interval: number }> = {\n line: { frames: ['|', '/', '-', '\\\\'], interval: 130 },\n dot: { frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'], interval: 80 },\n minidot: { frames: ['⠄', '⠂', '⠁', '⠈', '⠐', '⠠'], interval: 100 },\n jump: { frames: ['⢀⠀', '⡀⠀', '⠄⠀', '⢂⠀', '⡂⠀', '⠅⠀', '⢃⠀', '⡃⠀', '⠍⠀', '⢋⠀', '⡋⠀', '⠍⠁', '⢋⠁', '⡋⠁', '⠍⠉', '⠋⠉', '⠋⠉', '⠉⠙', '⠉⠙', '⠉⠩', '⠈⢙', '⠈⡙', '⢈⠩', '⡂⠩', '⠅⠩', '⢃⠩', '⡃⠩', '⠍⠩', '⢋⠩', '⡋⠩', '⠍⠩', '⢋⠩', '⡋⠩', '⠍⢉', '⠍⡉', '⠍⠋'], interval: 100 },\n pulse: { frames: ['█', '▓', '▒', '░', '▒', '▓'], interval: 120 },\n points: { frames: ['∙∙∙', '●∙∙', '∙●∙', '∙∙●'], interval: 200 },\n globe: { frames: ['🌍', '🌎', '🌏'], interval: 200 },\n moon: { frames: ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘'], interval: 120 },\n monkey: { frames: ['🙈', '🙉', '🙊'], interval: 300 },\n meter: { frames: ['▱▱▱▱▱▱▱', '▰▱▱▱▱▱▱', '▰▰▱▱▱▱▱', '▰▰▰▱▱▱▱', '▰▰▰▰▱▱▱', '▰▰▰▰▰▱▱', '▰▰▰▰▰▰▱', '▰▰▰▰▰▰▰'], interval: 120 },\n hamburger: { frames: ['☱', '☲', '☴'], interval: 150 },\n}\n\nexport interface SpinnerProps {\n variant?: SpinnerVariant\n title?: string\n align?: 'left' | 'right'\n speed?: number\n}\n\nexport function Spinner({ variant = 'dot', title, align = 'left', speed }: SpinnerProps) {\n const { tokens } = useTuiTheme()\n const spinner = SPINNERS[variant]\n const { frames } = spinner\n const interval = speed ?? spinner.interval\n const [frameIndex, setFrameIndex] = useState(0)\n\n useEffect(() => {\n const id = setInterval(() => {\n setFrameIndex(prev => (prev + 1) % frames.length)\n }, interval)\n return () => clearInterval(id)\n }, [frames.length, interval])\n\n const frame = frames[frameIndex] ?? frames[0]!\n\n return (\n <box style={{ flexDirection: 'row', gap: 1 }}>\n {align === 'left' && <text content={frame} fg={tokens.accent} />}\n {title && <text content={title} fg={tokens.textPrimary} />}\n {align === 'right' && <text content={frame} fg={tokens.accent} />}\n </box>\n )\n}\n",
|
|
37
|
+
"import { useCallback, useId, useMemo, useRef, useState } from 'react'\nimport type { KeyEvent, ScrollBoxRenderable } from '@opentui/core'\nimport { useScopedKeyboard, createKeyMatcher, useTuiTheme } from '@bunli/runtime/app'\n\nexport interface PagerProps {\n content: string\n title?: string\n showLineNumbers?: boolean\n height?: number | `${number}%` | 'auto'\n width?: number | `${number}%` | 'auto'\n scopeId?: string\n keyboardEnabled?: boolean\n onQuit?: () => void\n}\n\ntype PagerMode = 'normal' | 'search'\n\nconst pagerKeymap = createKeyMatcher({\n scrollDown: ['down', 'j'],\n scrollUp: ['up', 'k'],\n halfPageDown: ['d'],\n halfPageUp: ['u'],\n top: ['g', 'home'],\n bottom: ['end'],\n search: ['/'],\n nextMatch: ['n'],\n prevMatch: ['shift+n'],\n quit: ['q', 'escape']\n})\n\nfunction padLineNumber(lineNum: number, totalLines: number): string {\n const width = String(totalLines).length\n return String(lineNum).padStart(width, ' ')\n}\n\nfunction findMatchIndices(lines: string[], query: string): number[] {\n if (!query) return []\n const lowerQuery = query.toLowerCase()\n const indices: number[] = []\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].toLowerCase().includes(lowerQuery)) {\n indices.push(i)\n }\n }\n return indices\n}\n\nexport function Pager({\n content,\n title,\n showLineNumbers = false,\n height,\n width,\n scopeId,\n keyboardEnabled = true,\n onQuit\n}: PagerProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const keyboardScopeId = scopeId ?? `pager:${reactScopeId}`\n\n const scrollRef = useRef<ScrollBoxRenderable>(null)\n\n const [mode, setMode] = useState<PagerMode>('normal')\n const [searchQuery, setSearchQuery] = useState('')\n const [matchIndices, setMatchIndices] = useState<number[]>([])\n const [currentMatchIndex, setCurrentMatchIndex] = useState(0)\n\n const lines = useMemo(() => content.split('\\n'), [content])\n\n const scrollToLine = useCallback((lineIndex: number) => {\n if (scrollRef.current) {\n scrollRef.current.scrollTop = lineIndex\n }\n }, [])\n\n const isMatchLine = useCallback((lineIndex: number) => {\n return matchIndices.includes(lineIndex)\n }, [matchIndices])\n\n const executeSearch = useCallback(() => {\n const indices = findMatchIndices(lines, searchQuery)\n setMatchIndices(indices)\n setCurrentMatchIndex(0)\n setMode('normal')\n if (indices.length > 0) {\n scrollToLine(indices[0])\n }\n }, [lines, searchQuery, scrollToLine])\n\n const navigateToMatch = useCallback((direction: 'next' | 'prev') => {\n if (matchIndices.length === 0) return\n let nextIndex: number\n if (direction === 'next') {\n nextIndex = (currentMatchIndex + 1) % matchIndices.length\n } else {\n nextIndex = (currentMatchIndex - 1 + matchIndices.length) % matchIndices.length\n }\n setCurrentMatchIndex(nextIndex)\n scrollToLine(matchIndices[nextIndex])\n }, [matchIndices, currentMatchIndex, scrollToLine])\n\n useScopedKeyboard(\n keyboardScopeId,\n (key: KeyEvent) => {\n if (mode === 'search') {\n if (key.name === 'escape') {\n setSearchQuery('')\n setMode('normal')\n return true\n }\n if (key.name === 'return' || key.sequence === '\\r' || key.sequence === '\\n') {\n executeSearch()\n return true\n }\n return false\n }\n\n // Normal mode\n if (pagerKeymap.match('scrollDown', key)) {\n scrollToLine((scrollRef.current?.scrollTop ?? 0) + 1)\n return true\n }\n\n if (pagerKeymap.match('scrollUp', key)) {\n scrollToLine(Math.max(0, (scrollRef.current?.scrollTop ?? 0) - 1))\n return true\n }\n\n // ctrl+d for half page down\n if (key.ctrl && key.name === 'd') {\n const halfPage = Math.max(1, Math.floor(lines.length / 4))\n scrollToLine((scrollRef.current?.scrollTop ?? 0) + halfPage)\n return true\n }\n\n // ctrl+u for half page up\n if (key.ctrl && key.name === 'u') {\n const halfPage = Math.max(1, Math.floor(lines.length / 4))\n scrollToLine(Math.max(0, (scrollRef.current?.scrollTop ?? 0) - halfPage))\n return true\n }\n\n if (pagerKeymap.match('halfPageDown', key) && !key.ctrl) {\n const halfPage = Math.max(1, Math.floor(lines.length / 4))\n scrollToLine((scrollRef.current?.scrollTop ?? 0) + halfPage)\n return true\n }\n\n if (pagerKeymap.match('halfPageUp', key) && !key.ctrl) {\n const halfPage = Math.max(1, Math.floor(lines.length / 4))\n scrollToLine(Math.max(0, (scrollRef.current?.scrollTop ?? 0) - halfPage))\n return true\n }\n\n if (pagerKeymap.match('top', key)) {\n scrollToLine(0)\n return true\n }\n\n // Shift+G for bottom\n if ((key.name === 'g' && key.shift) || key.name === 'G') {\n scrollToLine(Math.max(0, lines.length - 1))\n return true\n }\n\n if (pagerKeymap.match('bottom', key)) {\n scrollToLine(Math.max(0, lines.length - 1))\n return true\n }\n\n if (pagerKeymap.match('search', key)) {\n setMode('search')\n setSearchQuery('')\n return true\n }\n\n if (pagerKeymap.match('nextMatch', key)) {\n navigateToMatch('next')\n return true\n }\n\n if (pagerKeymap.match('prevMatch', key)) {\n navigateToMatch('prev')\n return true\n }\n\n if (pagerKeymap.match('quit', key)) {\n onQuit?.()\n return true\n }\n\n return false\n },\n { active: keyboardEnabled }\n )\n\n return (\n <box\n border\n height={height}\n width={width}\n style={{\n flexDirection: 'column',\n borderColor: keyboardEnabled ? tokens.accent : tokens.border\n }}\n >\n {title && <text content={title} fg={tokens.textPrimary} />}\n\n <scrollbox\n ref={scrollRef}\n flexGrow={1}\n focused={keyboardEnabled}\n scrollY\n viewportOptions={{ width: '100%' }}\n contentOptions={{ width: '100%' }}\n scrollbarOptions={{\n visible: true,\n trackOptions: {\n backgroundColor: tokens.backgroundMuted,\n foregroundColor: keyboardEnabled ? tokens.accent : tokens.borderMuted\n }\n }}\n >\n {lines.map((line, i) => (\n <text\n key={i}\n content={\n showLineNumbers\n ? `${padLineNumber(i + 1, lines.length)} ${line}`\n : line\n }\n fg={isMatchLine(i) ? tokens.accent : tokens.textPrimary}\n />\n ))}\n </scrollbox>\n\n {mode === 'search' && (\n <box style={{ flexDirection: 'row' }}>\n <text content=\"/\" fg={tokens.accent} />\n <input\n value={searchQuery}\n placeholder=\"Search...\"\n onInput={setSearchQuery}\n onSubmit={() => executeSearch()}\n focused={true}\n />\n </box>\n )}\n\n <text\n content={\n matchIndices.length > 0\n ? `Match ${currentMatchIndex + 1}/${matchIndices.length}`\n : ''\n }\n fg={tokens.textMuted}\n />\n </box>\n )\n}\n",
|
|
38
|
+
"import { useCallback, useId, useMemo, useState } from 'react'\nimport type { KeyEvent } from '@opentui/core'\nimport { useScopedKeyboard, createKeyMatcher, useTuiTheme } from '@bunli/runtime/app'\n\nexport interface ChooseOption {\n label: string\n value: string\n description?: string\n disabled?: boolean\n}\n\nexport interface ChooseProps {\n options: ChooseOption[]\n mode?: 'single' | 'multiple'\n limit?: number\n ordered?: boolean\n height?: number\n cursor?: string\n selectedPrefix?: string\n unselectedPrefix?: string\n onSelect?: (selected: ChooseOption[]) => void\n onAbort?: () => void\n scopeId?: string\n keyboardEnabled?: boolean\n}\n\nconst chooseKeymap = createKeyMatcher({\n up: ['up', 'k'],\n down: ['down', 'j'],\n pageUp: ['left', 'h'],\n pageDown: ['right', 'l'],\n home: ['g', 'home'],\n end: ['end', 'G'],\n toggle: ['space', 'tab', 'x'],\n selectAll: ['a'],\n submit: ['enter'],\n abort: ['escape']\n})\n\nfunction nextEnabledIndex(options: ChooseOption[], from: number, delta: number): number {\n if (options.length === 0) return 0\n for (let step = 0; step < options.length; step += 1) {\n const next = (from + delta * (step + 1) + options.length) % options.length\n if (!options[next]?.disabled) return next\n }\n return from\n}\n\nfunction firstEnabledIndex(options: ChooseOption[]): number {\n for (let i = 0; i < options.length; i++) {\n if (!options[i]?.disabled) return i\n }\n return 0\n}\n\nfunction lastEnabledIndex(options: ChooseOption[]): number {\n for (let i = options.length - 1; i >= 0; i--) {\n if (!options[i]?.disabled) return i\n }\n return 0\n}\n\nexport function Choose({\n options,\n mode = 'single',\n limit = 0,\n ordered = false,\n height = 10,\n cursor = '>',\n selectedPrefix = '[x]',\n unselectedPrefix = '[ ]',\n onSelect,\n onAbort,\n scopeId,\n keyboardEnabled = true\n}: ChooseProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const keyboardScopeId = scopeId ?? `choose:${reactScopeId}`\n\n const [cursorIndex, setCursorIndex] = useState(() => firstEnabledIndex(options))\n const [pageOffset, setPageOffset] = useState(0)\n // For ordered mode, store indices in selection order; for unordered, use a Set\n const [selectedIndices, setSelectedIndices] = useState<number[]>([])\n\n const selectedSet = useMemo(() => new Set(selectedIndices), [selectedIndices])\n\n const pageSize = height\n const totalPages = Math.max(1, Math.ceil(options.length / pageSize))\n\n const adjustPageOffset = useCallback((newCursorIndex: number, currentOffset: number): number => {\n if (newCursorIndex < currentOffset) {\n return newCursorIndex\n }\n if (newCursorIndex >= currentOffset + pageSize) {\n return newCursorIndex - pageSize + 1\n }\n return currentOffset\n }, [pageSize])\n\n const moveCursor = useCallback((delta: number) => {\n setCursorIndex((prev) => {\n const next = nextEnabledIndex(options, prev, delta)\n setPageOffset((offset) => adjustPageOffset(next, offset))\n return next\n })\n }, [options, adjustPageOffset])\n\n const movePage = useCallback((delta: number) => {\n setCursorIndex((prev) => {\n const targetIndex = Math.max(0, Math.min(options.length - 1, prev + delta * pageSize))\n // Find nearest enabled index from the target\n if (!options[targetIndex]?.disabled) {\n setPageOffset((offset) => adjustPageOffset(targetIndex, offset))\n return targetIndex\n }\n const next = nextEnabledIndex(options, targetIndex - (delta > 0 ? 1 : -1), delta > 0 ? 1 : -1)\n setPageOffset((offset) => adjustPageOffset(next, offset))\n return next\n })\n }, [options, pageSize, adjustPageOffset])\n\n const goHome = useCallback(() => {\n const idx = firstEnabledIndex(options)\n setCursorIndex(idx)\n setPageOffset(0)\n }, [options])\n\n const goEnd = useCallback(() => {\n const idx = lastEnabledIndex(options)\n setCursorIndex(idx)\n setPageOffset(Math.max(0, options.length - pageSize))\n }, [options, pageSize])\n\n const toggleIndex = useCallback((index: number) => {\n const option = options[index]\n if (!option || option.disabled) return\n\n setSelectedIndices((prev) => {\n const isSelected = prev.includes(index)\n if (isSelected) {\n return prev.filter((i) => i !== index)\n }\n if (limit > 0 && prev.length >= limit) {\n return prev\n }\n return [...prev, index]\n })\n }, [options, limit])\n\n const toggleAll = useCallback(() => {\n setSelectedIndices((prev) => {\n const enabledIndices = options\n .map((opt, i) => (!opt.disabled ? i : -1))\n .filter((i) => i !== -1)\n const allSelected = enabledIndices.every((i) => prev.includes(i))\n if (allSelected) {\n return []\n }\n if (limit > 0) {\n return enabledIndices.slice(0, limit)\n }\n return enabledIndices\n })\n }, [options, limit])\n\n const submit = useCallback(() => {\n if (mode === 'single') {\n const option = options[cursorIndex]\n if (option && !option.disabled) {\n onSelect?.([option])\n }\n } else {\n const indices = ordered\n ? selectedIndices\n : [...selectedIndices].sort((a, b) => a - b)\n const selected = indices\n .map((i) => options[i])\n .filter((opt): opt is ChooseOption => opt != null)\n onSelect?.(selected)\n }\n }, [mode, options, cursorIndex, selectedIndices, ordered, onSelect])\n\n useScopedKeyboard(\n keyboardScopeId,\n (key) => {\n if (chooseKeymap.match('abort', key)) {\n onAbort?.()\n return true\n }\n\n if (chooseKeymap.match('up', key)) {\n moveCursor(-1)\n return true\n }\n\n if (chooseKeymap.match('down', key)) {\n moveCursor(1)\n return true\n }\n\n if (chooseKeymap.match('pageUp', key)) {\n movePage(-1)\n return true\n }\n\n if (chooseKeymap.match('pageDown', key)) {\n movePage(1)\n return true\n }\n\n if (chooseKeymap.match('home', key)) {\n goHome()\n return true\n }\n\n // Handle shift+g for end (G)\n if ((key.name === 'g' && key.shift) || chooseKeymap.match('end', key)) {\n goEnd()\n return true\n }\n\n if (mode === 'multiple' && chooseKeymap.match('toggle', key)) {\n toggleIndex(cursorIndex)\n return true\n }\n\n if (mode === 'multiple' && chooseKeymap.match('selectAll', key)) {\n toggleAll()\n return true\n }\n\n if (chooseKeymap.match('submit', key)) {\n submit()\n return true\n }\n\n return false\n },\n { active: keyboardEnabled }\n )\n\n const visibleOptions = options.slice(pageOffset, pageOffset + pageSize)\n const currentPage = Math.floor(pageOffset / pageSize) + 1\n const hasMoreAbove = pageOffset > 0\n const hasMoreBelow = pageOffset + pageSize < options.length\n\n return (\n <box style={{ flexDirection: 'column' }}>\n {visibleOptions.map((option, visIndex) => {\n const globalIndex = pageOffset + visIndex\n const isCursor = globalIndex === cursorIndex\n const isSelected = selectedSet.has(globalIndex)\n const prefix = isCursor ? cursor : ' '.repeat(cursor.length)\n const selectionMarker = mode === 'multiple'\n ? (isSelected ? selectedPrefix : unselectedPrefix) + ' '\n : ''\n\n return (\n <text\n key={option.value}\n content={`${prefix} ${selectionMarker}${option.label}${option.description ? ` - ${option.description}` : ''}`}\n fg={option.disabled ? tokens.textMuted : isCursor ? tokens.accent : tokens.textPrimary}\n />\n )\n })}\n {(hasMoreAbove || hasMoreBelow) ? (\n <text\n content={\n hasMoreAbove && hasMoreBelow\n ? `(page ${currentPage}/${totalPages})`\n : hasMoreAbove\n ? '\\u2191 more'\n : '\\u2193 more'\n }\n fg={tokens.textMuted}\n />\n ) : null}\n </box>\n )\n}\n",
|
|
39
|
+
"import { useCallback, useId, useMemo, useState } from 'react'\nimport { resolve } from 'node:path'\nimport type { KeyEvent } from '@opentui/core'\nimport { useScopedKeyboard, createKeyMatcher, useTuiTheme } from '@bunli/runtime/app'\nimport { listDirectory, formatSize } from './file-picker-utils.js'\n\nexport type { FilePickerEntry } from './file-picker-utils.js'\nexport { listDirectory, formatSize } from './file-picker-utils.js'\n\nexport interface FilePickerProps {\n path?: string\n allowFiles?: boolean\n allowDirectories?: boolean\n showHidden?: boolean\n showPermissions?: boolean\n showSize?: boolean\n fileExtensions?: string[]\n height?: number\n cursor?: string\n onSelect?: (entry: import('./file-picker-utils.js').FilePickerEntry) => void\n onAbort?: () => void\n scopeId?: string\n keyboardEnabled?: boolean\n}\n\nconst filePickerKeymap = createKeyMatcher({\n up: ['up', 'k'],\n down: ['down', 'j'],\n enter: ['enter', 'right', 'l'],\n back: ['backspace', 'left', 'h'],\n toggleHidden: ['.'],\n quit: ['q', 'escape']\n})\n\nexport function FilePicker({\n path,\n allowFiles = true,\n allowDirectories = false,\n showHidden: initialShowHidden = false,\n showPermissions = false,\n showSize = false,\n fileExtensions,\n height = 15,\n cursor = '>',\n onSelect,\n onAbort,\n scopeId,\n keyboardEnabled = true\n}: FilePickerProps) {\n const { tokens } = useTuiTheme()\n const reactScopeId = useId()\n const keyboardScopeId = scopeId ?? `file-picker:${reactScopeId}`\n\n const [currentPath, setCurrentPath] = useState(() => resolve(path ?? '.'))\n const [cursorIndex, setCursorIndex] = useState(0)\n const [pageOffset, setPageOffset] = useState(0)\n const [showHidden, setShowHidden] = useState(initialShowHidden)\n\n const entries = useMemo(() => listDirectory(currentPath, {\n showHidden, allowFiles, allowDirectories, fileExtensions\n }), [currentPath, showHidden, fileExtensions, allowFiles, allowDirectories])\n\n const visibleEntries = useMemo(() => {\n return entries.slice(pageOffset, pageOffset + height)\n }, [entries, pageOffset, height])\n\n const moveCursor = useCallback((delta: number) => {\n if (entries.length === 0) return\n\n setCursorIndex(prev => {\n const next = Math.max(0, Math.min(entries.length - 1, prev + delta))\n\n if (next < pageOffset) {\n setPageOffset(next)\n } else if (next >= pageOffset + height) {\n setPageOffset(next - height + 1)\n }\n\n return next\n })\n }, [entries.length, pageOffset, height])\n\n const navigateToDirectory = useCallback((dirPath: string) => {\n setCurrentPath(dirPath)\n setCursorIndex(0)\n setPageOffset(0)\n }, [])\n\n useScopedKeyboard(\n keyboardScopeId,\n (key: KeyEvent) => {\n if (filePickerKeymap.match('up', key)) {\n moveCursor(-1)\n return true\n }\n\n if (filePickerKeymap.match('down', key)) {\n moveCursor(1)\n return true\n }\n\n if (filePickerKeymap.match('enter', key)) {\n const entry = entries[cursorIndex]\n if (!entry) return false\n\n if (entry.isDirectory) {\n if (allowDirectories) {\n onSelect?.(entry)\n } else {\n navigateToDirectory(entry.path)\n }\n } else if (allowFiles) {\n onSelect?.(entry)\n }\n return true\n }\n\n if (filePickerKeymap.match('back', key)) {\n const parentPath = resolve(currentPath, '..')\n if (parentPath !== currentPath) {\n navigateToDirectory(parentPath)\n }\n return true\n }\n\n if (filePickerKeymap.match('toggleHidden', key)) {\n setShowHidden(prev => !prev)\n setCursorIndex(0)\n setPageOffset(0)\n return true\n }\n\n if (filePickerKeymap.match('quit', key)) {\n onAbort?.()\n return true\n }\n\n return false\n },\n { active: keyboardEnabled }\n )\n\n const separatorLength = Math.min(currentPath.length, 40)\n\n return (\n <box style={{ flexDirection: 'column' }}>\n <text content={currentPath} fg={tokens.accent} />\n <text content={'\\u2500'.repeat(separatorLength)} fg={tokens.border} />\n\n <box style={{ flexDirection: 'column' }}>\n {visibleEntries.map((entry, visIndex) => {\n const globalIndex = pageOffset + visIndex\n const isCursor = globalIndex === cursorIndex\n const prefix = isCursor ? cursor : ' '.repeat(cursor.length)\n const typeIndicator = entry.isDirectory ? '\\uD83D\\uDCC1 ' : ' '\n const suffix = entry.isDirectory ? '/' : ''\n const sizeStr = showSize && entry.size != null ? ` (${formatSize(entry.size)})` : ''\n const permStr = showPermissions && entry.permissions ? ` [${entry.permissions}]` : ''\n\n return (\n <text\n key={entry.path}\n content={`${prefix} ${typeIndicator}${entry.name}${suffix}${sizeStr}${permStr}`}\n fg={isCursor ? tokens.accent : entry.isDirectory ? tokens.textPrimary : tokens.textMuted}\n />\n )\n })}\n </box>\n\n <text content={'\\u2191\\u2193 navigate \\u2190 parent \\u2192 enter . hidden q quit'} fg={tokens.textMuted} />\n </box>\n )\n}\n",
|
|
40
|
+
"import { useMemo } from 'react'\nimport { useTuiTheme } from '@bunli/runtime/app'\n\nexport interface SeriesPoint {\n label?: string\n value?: number | null\n}\n\nexport interface ChartSeries {\n name?: string\n color?: string\n points: SeriesPoint[]\n}\n\nexport interface AxisOptions {\n xLabel?: string\n yLabel?: string\n min?: number\n max?: number\n showRange?: boolean\n}\n\nexport type ChartSeriesInput = ChartSeries | ChartSeries[]\nexport type ValueFormatter = (value: number, point: SeriesPoint) => string\n\nexport interface ChartDomain {\n min: number\n max: number\n maxAbs: number\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n\nfunction toSeriesArray(series: ChartSeriesInput): ChartSeries[] {\n return Array.isArray(series) ? series : [series]\n}\n\nfunction toNumber(value: unknown): number | null {\n if (typeof value !== 'number') return null\n if (!Number.isFinite(value)) return null\n return value\n}\n\nfunction computeDomain(seriesList: ChartSeries[], axis?: AxisOptions): ChartDomain {\n const values = seriesList\n .flatMap((series) => series.points)\n .map((point) => toNumber(point.value))\n .filter((value): value is number => value !== null)\n\n const minFromValues = values.length > 0 ? Math.min(...values) : 0\n const maxFromValues = values.length > 0 ? Math.max(...values) : 0\n\n const min = axis?.min ?? Math.min(minFromValues, 0)\n const max = axis?.max ?? Math.max(maxFromValues, 0)\n const maxAbs = Math.max(Math.abs(min), Math.abs(max), 1)\n\n return { min, max, maxAbs }\n}\n\nfunction defaultFormatter(value: number): string {\n return Number.isInteger(value) ? String(value) : value.toFixed(2)\n}\n\nfunction paletteAt(index: number, fallback: string, palette?: string[]): string {\n if (palette && palette.length > 0) {\n return palette[index % palette.length] ?? fallback\n }\n return fallback\n}\n\nfunction pointLabel(point: SeriesPoint, index: number): string {\n return point.label ?? `#${index + 1}`\n}\n\nfunction resampleValues(values: Array<number | null>, width?: number): Array<number | null> {\n if (!width || width <= 0 || values.length === 0 || values.length === width) {\n return values\n }\n\n if (width === 1) {\n return [values[0] ?? null]\n }\n\n if (values.length === 1) {\n return Array.from({ length: width }, () => values[0] ?? null)\n }\n\n return Array.from({ length: width }, (_, outputIndex) => {\n const inputIndex = Math.round((outputIndex * (values.length - 1)) / (width - 1))\n return values[inputIndex] ?? null\n })\n}\n\nfunction renderBarLine(args: {\n point: SeriesPoint\n index: number\n maxAbs: number\n halfWidth: number\n formatter: ValueFormatter\n}): string {\n const value = toNumber(args.point.value)\n const label = pointLabel(args.point, args.index)\n\n if (value === null) {\n return `${label}: ${' '.repeat(args.halfWidth)}|${' '.repeat(args.halfWidth)} ·`\n }\n\n const ratio = clamp(Math.abs(value) / args.maxAbs, 0, 1)\n const width = Math.round(ratio * args.halfWidth)\n const negativeBar = value < 0 ? '█'.repeat(width).padStart(args.halfWidth, ' ') : ' '.repeat(args.halfWidth)\n const positiveBar = value > 0 ? '█'.repeat(width).padEnd(args.halfWidth, ' ') : ' '.repeat(args.halfWidth)\n\n return `${label}: ${negativeBar}|${positiveBar} ${args.formatter(value, args.point)}`\n}\n\nconst SPARKS = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']\n\nfunction renderSparkline(values: Array<number | null>, min: number, max: number, placeholder = '·'): string {\n const span = max - min || 1\n\n return values\n .map((value) => {\n if (value === null) return placeholder\n const normalized = (value - min) / span\n const index = clamp(Math.round(normalized * (SPARKS.length - 1)), 0, SPARKS.length - 1)\n return SPARKS[index] ?? SPARKS[0] ?? '▁'\n })\n .join('')\n}\n\nexport interface BarChartProps {\n series: ChartSeriesInput\n width?: number\n color?: string\n palette?: string[]\n axis?: AxisOptions\n valueFormatter?: ValueFormatter\n}\n\nexport function BarChart({\n series,\n width = 24,\n color,\n palette,\n axis,\n valueFormatter = (value) => defaultFormatter(value)\n}: BarChartProps) {\n const { tokens } = useTuiTheme()\n const seriesList = toSeriesArray(series)\n const domain = useMemo(() => computeDomain(seriesList, axis), [axis, seriesList])\n const halfWidth = Math.max(6, Math.floor(width / 2))\n\n return (\n <box style={{ flexDirection: 'column' }}>\n {axis?.yLabel ? <text content={axis.yLabel} fg={tokens.textMuted} /> : null}\n\n {seriesList.map((item, seriesIndex) => {\n const seriesColor = item.color ?? color ?? paletteAt(seriesIndex, tokens.accent, palette)\n return (\n <box key={`bar-series-${seriesIndex}`} style={{ flexDirection: 'column' }}>\n {item.name ? <text content={`${item.name}:`} fg={tokens.textMuted} /> : null}\n {item.points.map((point, pointIndex) => (\n <text\n key={`bar-point-${seriesIndex}-${pointIndex}`}\n content={renderBarLine({\n point,\n index: pointIndex,\n maxAbs: domain.maxAbs,\n halfWidth,\n formatter: valueFormatter\n })}\n fg={seriesColor}\n />\n ))}\n </box>\n )\n })}\n\n {axis?.showRange !== false ? (\n <text content={`${domain.min} <- 0 -> ${domain.max}`} fg={tokens.textMuted} />\n ) : null}\n {axis?.xLabel ? <text content={axis.xLabel} fg={tokens.textMuted} /> : null}\n </box>\n )\n}\n\nexport interface LineChartProps {\n series: ChartSeriesInput\n width?: number\n color?: string\n palette?: string[]\n axis?: AxisOptions\n}\n\nexport function LineChart({ series, width, color, palette, axis }: LineChartProps) {\n const { tokens } = useTuiTheme()\n const seriesList = toSeriesArray(series)\n const domain = useMemo(() => computeDomain(seriesList, axis), [axis, seriesList])\n\n return (\n <box style={{ flexDirection: 'column' }}>\n {axis?.yLabel ? <text content={axis.yLabel} fg={tokens.textMuted} /> : null}\n {seriesList.map((item, seriesIndex) => {\n const seriesColor = item.color ?? color ?? paletteAt(seriesIndex, tokens.accent, palette)\n const points = resampleValues(item.points.map((point) => toNumber(point.value)), width)\n const sparkline = renderSparkline(points, domain.min, domain.max)\n const name = item.name ?? `Series ${seriesIndex + 1}`\n\n return (\n <text\n key={`line-series-${seriesIndex}`}\n content={`${name}: ${sparkline}`}\n fg={seriesColor}\n />\n )\n })}\n {axis?.showRange !== false ? (\n <text content={`${domain.min} … ${domain.max}`} fg={tokens.textMuted} />\n ) : null}\n {axis?.xLabel ? <text content={axis.xLabel} fg={tokens.textMuted} /> : null}\n </box>\n )\n}\n\nexport interface SparklineProps {\n values: Array<number | null | undefined>\n width?: number\n color?: string\n min?: number\n max?: number\n placeholder?: string\n}\n\nexport function Sparkline({ values, width, color, min, max, placeholder = '·' }: SparklineProps) {\n const { tokens } = useTuiTheme()\n if (values.length === 0) return <text content=\"\" fg={tokens.textMuted} />\n\n const normalizedValues = resampleValues(values.map((value) => toNumber(value)), width)\n const valid = normalizedValues.filter((value): value is number => value !== null)\n const computedMin = min ?? (valid.length > 0 ? Math.min(...valid) : 0)\n const computedMax = max ?? (valid.length > 0 ? Math.max(...valid) : 0)\n const spark = renderSparkline(normalizedValues, computedMin, computedMax, placeholder)\n\n return <text content={spark} fg={color ?? tokens.accent} />\n}\n\nexport const __chartInternalsForTests = {\n clamp,\n toSeriesArray,\n toNumber,\n computeDomain,\n resampleValues,\n renderBarLine,\n renderSparkline\n}\n",
|
|
41
|
+
"import { createElement } from \"react\";\n\nimport { defineCommand } from \"@bunli/core\";\n\nimport {\n\tbuildCommandOptions,\n\tCREATE_OPTION_METADATA,\n\tresolveCommandOptionValues,\n} from \"../command-option-metadata\";\nimport { getCreateDefaults } from \"../config\";\nimport { resolveBundledModuleHref } from \"../render-loader\";\nimport { executeCreateCommand } from \"../runtime-bridge\";\nimport { supportsInteractiveTui } from \"../runtime-capabilities\";\nimport type { WpTypiaRenderArgs } from \"./render-types\";\nimport { LazyFlow } from \"../ui/lazy-flow\";\n\nfunction loadCreateFlow() {\n\treturn import(\n\t\tresolveBundledModuleHref(import.meta.url, [\n\t\t\t\"./ui/create-flow.js\",\n\t\t\t\"../ui/create-flow.js\",\n\t\t\t\"../ui/create-flow.tsx\",\n\t\t], {\n\t\t\tmoduleLabel: \"the create-flow UI\",\n\t\t}),\n\t).then((module) => ({ default: module.CreateFlow }));\n}\n\nconst createOptions = buildCommandOptions(CREATE_OPTION_METADATA);\n\nexport const createCommand = defineCommand({\n\tdefaultFormat: \"toon\",\n\tdescription: \"Scaffold a new wp-typia project.\",\n\thandler: async (args) => {\n\t\tconst projectDir = args.positional[0];\n\t\tif (!projectDir) {\n\t\t\tconst { createCliCommandError } = await import(\"@wp-typia/project-tools/cli-diagnostics\");\n\t\t\tthrow createCliCommandError({\n\t\t\t\tcommand: \"create\",\n\t\t\t\tdetailLines: [\n\t\t\t\t\t\"`wp-typia create` requires <project-dir>.\",\n\t\t\t\t\t\"`--dry-run` still needs a logical project directory name because wp-typia derives slugs, package names, and planned file paths from it.\",\n\t\t\t\t],\n\t\t\t});\n\t\t}\n\t\tawait executeCreateCommand({\n\t\t\tcwd: args.cwd,\n\t\t\tflags: args.flags as Record<string, unknown>,\n\t\t\tprojectDir,\n\t\t});\n\t},\n\tname: \"create\",\n\toptions: createOptions,\n\t...(supportsInteractiveTui()\n\t\t? {\n\t\t\t\trender: (args: WpTypiaRenderArgs) => {\n\t\t\t\t\tconst config =\n\t\t\t\t\t\targs.context?.store?.wpTypiaUserConfig &&\n\t\t\t\t\t\ttypeof args.context.store.wpTypiaUserConfig === \"object\"\n\t\t\t\t\t\t\t? getCreateDefaults(args.context.store.wpTypiaUserConfig)\n\t\t\t\t\t\t\t: {};\n\t\t\t\t\tconst initialValues = resolveCommandOptionValues(CREATE_OPTION_METADATA, {\n\t\t\t\t\t\tdefaults: config,\n\t\t\t\t\t\tflags: args.flags as Record<string, unknown>,\n\t\t\t\t\t});\n\t\t\t\t\treturn createElement(LazyFlow, {\n\t\t\t\t\t\tloader: loadCreateFlow,\n\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\tcwd: args.cwd,\n\t\t\t\t\t\t\tinitialValues: {\n\t\t\t\t\t\t\t\t...initialValues,\n\t\t\t\t\t\t\t\t\"project-dir\": args.positional[0] ?? \"\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\ttui: {\n\t\t\t\t\trenderer: {\n\t\t\t\t\t\tbufferMode: \"alternate\" as const,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t: {}),\n});\n\nexport default createCommand;\n",
|
|
42
|
+
"import { defineCommand } from \"@bunli/core\";\nimport { executeDoctorCommand } from \"../runtime-bridge\";\n\nexport const doctorCommand = defineCommand({\n\tdefaultFormat: \"toon\",\n\tdescription: \"Run repository and project diagnostics.\",\n\thandler: async (args) => {\n\t\tconst prefersStructuredOutput =\n\t\t\t(args.formatExplicit && args.format !== \"toon\") ||\n\t\t\tBoolean(args.context?.store?.isAIAgent);\n\t\tif (prefersStructuredOutput) {\n\t\t\tconst [{ getDoctorChecks }, { createCliCommandError, getDoctorFailureDetailLines }] =\n\t\t\t\tawait Promise.all([\n\t\t\t\t\timport(\"@wp-typia/project-tools/cli-doctor\"),\n\t\t\t\t\timport(\"@wp-typia/project-tools/cli-diagnostics\"),\n\t\t\t\t]);\n\t\t\tconst checks = await getDoctorChecks(args.cwd);\n\t\t\targs.output({ checks });\n\t\t\tif (checks.some((check) => check.status === \"fail\")) {\n\t\t\t\tthrow createCliCommandError({\n\t\t\t\t\tcommand: \"doctor\",\n\t\t\t\t\tdetailLines: getDoctorFailureDetailLines(checks),\n\t\t\t\t\tsummary: \"One or more doctor checks failed.\",\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tawait executeDoctorCommand(args.cwd);\n\t},\n\tname: \"doctor\",\n});\n\nexport default doctorCommand;\n",
|
|
43
|
+
"import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport {\n\tcreateCommandsFromMCPTools,\n\textractCommandMetadata,\n\tgenerateMCPTypes,\n\ttype MCPToolGroup,\n\ttype MCPTool,\n} from \"@bunli/plugin-mcp\";\n\nimport type { WpTypiaSchemaSource } from \"./config\";\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isTool(value: unknown): value is MCPTool {\n\treturn (\n\t\tisObject(value) &&\n\t\ttypeof value.name === \"string\" &&\n\t\t(value.description === undefined || typeof value.description === \"string\")\n\t);\n}\n\nfunction isToolGroup(value: unknown): value is MCPToolGroup {\n\treturn (\n\t\tisObject(value) &&\n\t\ttypeof value.namespace === \"string\" &&\n\t\tArray.isArray(value.tools) &&\n\t\tvalue.tools.every(isTool)\n\t);\n}\n\nasync function readSchemaSource(\n\tcwd: string,\n\tsource: WpTypiaSchemaSource,\n): Promise<MCPToolGroup> {\n\tconst schemaPath = path.resolve(cwd, source.path);\n\tconst raw = await fs.readFile(schemaPath, \"utf8\");\n\tconst parsed = JSON.parse(raw);\n\n\tif (isToolGroup(parsed)) {\n\t\treturn parsed;\n\t}\n\n\tif (Array.isArray(parsed) && parsed.every(isTool)) {\n\t\treturn {\n\t\t\tnamespace: source.namespace,\n\t\t\ttools: parsed,\n\t\t};\n\t}\n\n\tthrow new Error(\n\t\t`Schema source \"${source.path}\" must contain either one MCPToolGroup object or an array of MCP tools.`,\n\t);\n}\n\nexport async function loadMcpToolGroups(\n\tcwd: string,\n\tschemaSources: WpTypiaSchemaSource[],\n): Promise<MCPToolGroup[]> {\n\treturn Promise.all(schemaSources.map((source) => readSchemaSource(cwd, source)));\n}\n\nexport async function syncMcpSchemas(\n\tcwd: string,\n\tschemaSources: WpTypiaSchemaSource[],\n\toutputDir = path.join(cwd, \".bunli\", \"mcp\"),\n): Promise<{\n\tcommandCount: number;\n\tgroups: MCPToolGroup[];\n\toutputDir: string;\n}> {\n\tconst groups = await loadMcpToolGroups(cwd, schemaSources);\n\tconst result = await generateMCPTypes({\n\t\toutputDir,\n\t\ttools: groups,\n\t});\n\tif (Result.isError(result)) {\n\t\tthrow result.error;\n\t}\n\n\tconst registry = groups.map((group) => ({\n\t\tnamespace: group.namespace,\n\t\ttools: group.tools.map((tool) => extractCommandMetadata(tool, group.namespace)),\n\t}));\n\n\tfor (const group of groups) {\n\t\tconst convert = createCommandsFromMCPTools(group.tools, {\n\t\t\tcreateHandler: () => async () => {},\n\t\t\tnamespace: group.namespace,\n\t\t});\n\t\tif (Result.isError(convert)) {\n\t\t\tthrow convert.error;\n\t\t}\n\t}\n\n\tawait fs.mkdir(outputDir, { recursive: true });\n\tawait fs.writeFile(\n\t\tpath.join(outputDir, \"registry.json\"),\n\t\t`${JSON.stringify(registry, null, 2)}\\n`,\n\t\t\"utf8\",\n\t);\n\n\treturn {\n\t\tcommandCount: registry.reduce((count, group) => count + group.tools.length, 0),\n\t\tgroups,\n\t\toutputDir,\n\t};\n}\n",
|
|
44
|
+
"import { TaggedError } from 'better-result'\n\nexport class SchemaConversionError extends TaggedError('SchemaConversionError')<{\n path: string\n message: string\n cause: unknown\n}>() {}\n\nexport class ConvertToolsError extends TaggedError('ConvertToolsError')<{\n toolName: string\n message: string\n cause: unknown\n}>() {}\n\nexport class GenerateMCPTypesError extends TaggedError('GenerateMCPTypesError')<{\n outputDir: string\n message: string\n cause: unknown\n}>() {}\n\nexport class McpToolsProviderError extends TaggedError('McpToolsProviderError')<{\n message: string\n cause: unknown\n}>() {}\n",
|
|
45
|
+
"/**\n * JSON Schema 7 to Zod Schema converter\n *\n * Converts MCP tool inputSchema (JSON Schema format) to Zod schemas\n * for use in Bunli CLI options.\n */\n\nimport { Result } from 'better-result'\nimport { z, type ZodTypeAny } from 'zod'\nimport { SchemaConversionError } from './errors.js'\nimport type { JSONSchema7 } from './types.js'\n\n/**\n * Options for schema conversion\n */\nexport interface SchemaConversionOptions {\n /**\n * Whether to make schemas coercive (parse strings to numbers, etc.)\n * Useful for CLI input which is always strings initially\n * @default true\n */\n coerce?: boolean\n}\n\n/**\n * Convert JSON Schema to Zod schema\n *\n * Supports:\n * - Primitive types: string, number, integer, boolean\n * - Enums and literals\n * - Arrays with item schemas\n * - Objects\n * - Constraints: min/max, minLength/maxLength, pattern\n * - Default values\n *\n * Returns `Err<SchemaConversionError>` for invalid runtime schema values\n * (for example, malformed regex patterns).\n */\nexport function jsonSchemaToZodSchema(\n schema: JSONSchema7 | undefined,\n options: SchemaConversionOptions = {}\n): Result<ZodTypeAny, SchemaConversionError> {\n return convertSchema(schema, options, '$')\n}\n\nfunction convertSchema(\n schema: JSONSchema7 | undefined,\n options: SchemaConversionOptions,\n path: string\n): Result<ZodTypeAny, SchemaConversionError> {\n const { coerce = true } = options\n\n // Handle undefined/null schema\n if (!schema || typeof schema !== 'object') {\n return Result.ok(z.unknown())\n }\n\n // Handle const (literal value)\n if (schema.const !== undefined) {\n return Result.ok(z.literal(schema.const as string | number | boolean))\n }\n\n // Handle enum\n if (schema.enum && schema.enum.length > 0) {\n // Filter out null values and ensure at least one value\n const enumValues = schema.enum.filter((v): v is string | number => v !== null)\n if (enumValues.length > 0) {\n if (enumValues.every((v): v is string => typeof v === 'string')) {\n return Result.ok(z.enum(enumValues as [string, ...string[]]))\n }\n\n const literals = enumValues.map(v => z.literal(v))\n return unionFromSchemas(literals, path)\n }\n }\n\n // Handle anyOf/oneOf (union types)\n if (schema.anyOf && schema.anyOf.length > 0) {\n const schemas = mapSchemas(schema.anyOf, options, `${path}.anyOf`)\n if (Result.isError(schemas)) {\n return schemas\n }\n return unionFromSchemas(schemas.value, `${path}.anyOf`)\n }\n\n if (schema.oneOf && schema.oneOf.length > 0) {\n const schemas = mapSchemas(schema.oneOf, options, `${path}.oneOf`)\n if (Result.isError(schemas)) {\n return schemas\n }\n return unionFromSchemas(schemas.value, `${path}.oneOf`)\n }\n\n // Handle type-based conversion\n const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type\n\n switch (schemaType) {\n case 'string':\n return buildStringSchema(schema, path)\n\n case 'number':\n case 'integer':\n return Result.ok(buildNumberSchema(schema, coerce))\n\n case 'boolean':\n return Result.ok(buildBooleanSchema())\n\n case 'array':\n return buildArraySchema(schema, options, path)\n\n case 'object':\n return buildObjectSchema(schema, options, path)\n\n case 'null':\n return Result.ok(z.null())\n\n default:\n // No type specified - try to infer from other properties\n if (schema.properties) {\n return buildObjectSchema(schema, options, path)\n }\n if (schema.items) {\n return buildArraySchema(schema, options, path)\n }\n return Result.ok(z.unknown())\n }\n}\n\n/**\n * Build Zod string schema with constraints\n */\nfunction buildStringSchema(\n schema: JSONSchema7,\n path: string\n): Result<z.ZodString, SchemaConversionError> {\n let zodSchema = z.string()\n\n // Apply constraints\n if (schema.minLength !== undefined) {\n zodSchema = zodSchema.min(schema.minLength)\n }\n if (schema.maxLength !== undefined) {\n zodSchema = zodSchema.max(schema.maxLength)\n }\n if (schema.pattern) {\n const pattern = schema.pattern\n const regexResult = Result.try({\n try: () => new RegExp(pattern),\n catch: (cause) =>\n new SchemaConversionError({\n path,\n message: `Invalid regex pattern \"${pattern}\"`,\n cause\n })\n })\n\n if (Result.isError(regexResult)) {\n return regexResult\n }\n\n zodSchema = zodSchema.regex(regexResult.value)\n }\n\n // Apply format validations\n if (schema.format) {\n switch (schema.format) {\n case 'email':\n zodSchema = zodSchema.email()\n break\n case 'uri':\n case 'url':\n zodSchema = zodSchema.url()\n break\n case 'uuid':\n zodSchema = zodSchema.uuid()\n break\n case 'date-time':\n zodSchema = zodSchema.datetime()\n break\n case 'date':\n zodSchema = zodSchema.date()\n break\n // Other formats: skip validation\n }\n }\n\n return Result.ok(zodSchema)\n}\n\n/**\n * Build Zod number schema with constraints\n */\nfunction buildNumberSchema(\n schema: JSONSchema7,\n coerce: boolean,\n): z.ZodNumber | z.ZodCoercedNumber {\n let zodSchema = coerce ? z.coerce.number() : z.number()\n\n // Integer constraint\n if (schema.type === 'integer' || (Array.isArray(schema.type) && schema.type.includes('integer'))) {\n zodSchema = zodSchema.int()\n }\n\n // Apply constraints\n if (schema.minimum !== undefined) {\n zodSchema = zodSchema.min(schema.minimum)\n }\n if (schema.maximum !== undefined) {\n zodSchema = zodSchema.max(schema.maximum)\n }\n if (schema.exclusiveMinimum !== undefined) {\n zodSchema = zodSchema.gt(schema.exclusiveMinimum)\n }\n if (schema.exclusiveMaximum !== undefined) {\n zodSchema = zodSchema.lt(schema.exclusiveMaximum)\n }\n if (schema.multipleOf !== undefined) {\n zodSchema = zodSchema.multipleOf(schema.multipleOf)\n }\n\n return zodSchema\n}\n\n/**\n * Build Zod boolean schema\n */\nfunction buildBooleanSchema(): z.ZodBoolean {\n return z.boolean()\n}\n\n/**\n * Build Zod array schema\n */\nfunction buildArraySchema(\n schema: JSONSchema7,\n options: SchemaConversionOptions,\n path: string\n): Result<ZodTypeAny, SchemaConversionError> {\n let itemSchema: ZodTypeAny = z.unknown()\n\n if (schema.items) {\n if (Array.isArray(schema.items)) {\n if (schema.items.length > 0) {\n const itemResult = convertSchema(schema.items[0], options, `${path}.items[0]`)\n if (Result.isError(itemResult)) {\n return itemResult\n }\n itemSchema = itemResult.value\n }\n } else {\n const itemResult = convertSchema(schema.items, options, `${path}.items`)\n if (Result.isError(itemResult)) {\n return itemResult\n }\n itemSchema = itemResult.value\n }\n }\n\n let zodSchema = z.array(itemSchema)\n\n // Apply constraints\n if (schema.minItems !== undefined) {\n zodSchema = zodSchema.min(schema.minItems)\n }\n if (schema.maxItems !== undefined) {\n zodSchema = zodSchema.max(schema.maxItems)\n }\n\n return Result.ok(zodSchema)\n}\n\n/**\n * Build Zod object schema\n */\nfunction buildObjectSchema(\n schema: JSONSchema7,\n options: SchemaConversionOptions,\n path: string\n): Result<ZodTypeAny, SchemaConversionError> {\n // For nested objects without properties, use record\n if (!schema.properties) {\n return Result.ok(z.record(z.string(), z.unknown()))\n }\n\n // Build object shape\n const shape: Record<string, ZodTypeAny> = {}\n const requiredFields = new Set(schema.required || [])\n\n for (const [propName, propSchema] of Object.entries(schema.properties)) {\n const propResult = convertSchema(propSchema, options, `${path}.properties.${propName}`)\n if (Result.isError(propResult)) {\n return propResult\n }\n\n let propZodSchema = propResult.value\n\n // Apply default if present\n if (propSchema.default !== undefined) {\n propZodSchema = propZodSchema.default(propSchema.default)\n }\n\n // Make optional if not required\n if (!requiredFields.has(propName)) {\n propZodSchema = propZodSchema.optional()\n }\n\n shape[propName] = propZodSchema\n }\n\n return Result.ok(z.object(shape))\n}\n\nfunction mapSchemas(\n schemas: JSONSchema7[],\n options: SchemaConversionOptions,\n path: string\n): Result<ZodTypeAny[], SchemaConversionError> {\n const zodSchemas: ZodTypeAny[] = []\n\n for (let index = 0; index < schemas.length; index += 1) {\n const converted = convertSchema(schemas[index], options, `${path}[${index}]`)\n if (Result.isError(converted)) {\n return converted\n }\n zodSchemas.push(converted.value)\n }\n\n return Result.ok(zodSchemas)\n}\n\nfunction unionFromSchemas(\n schemas: ZodTypeAny[],\n path: string\n): Result<ZodTypeAny, SchemaConversionError> {\n if (schemas.length === 0) {\n return Result.err(\n new SchemaConversionError({\n path,\n message: `Cannot create union from an empty schema set at ${path}`,\n cause: new Error('Empty schema union')\n })\n )\n }\n\n if (schemas.length === 1) {\n return Result.ok(schemas[0]!)\n }\n\n let unionSchema: ZodTypeAny = z.union([schemas[0]!, schemas[1]!])\n for (let index = 2; index < schemas.length; index += 1) {\n unionSchema = z.union([unionSchema, schemas[index]!])\n }\n\n return Result.ok(unionSchema)\n}\n\n/**\n * Extract metadata from JSON Schema for codegen\n *\n * Returns information useful for generating TypeScript types\n * and completion hints.\n */\nexport interface SchemaMetadata {\n type: string\n description?: string\n enumValues?: Array<string | number>\n minimum?: number\n maximum?: number\n minLength?: number\n maxLength?: number\n pattern?: string\n format?: string\n isArray?: boolean\n itemType?: string\n hasDefault?: boolean\n default?: unknown\n}\n\nexport function extractSchemaMetadata(schema: JSONSchema7 | undefined): SchemaMetadata {\n if (!schema) {\n return { type: 'unknown' }\n }\n\n const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type\n const metadata: SchemaMetadata = {\n type: schemaType || 'unknown',\n description: schema.description,\n hasDefault: schema.default !== undefined,\n default: schema.default\n }\n\n // Extract enum values\n if (schema.enum) {\n metadata.enumValues = schema.enum.filter((v): v is string | number =>\n typeof v === 'string' || typeof v === 'number'\n )\n }\n\n // Extract constraints\n if (schema.minimum !== undefined) metadata.minimum = schema.minimum\n if (schema.maximum !== undefined) metadata.maximum = schema.maximum\n if (schema.minLength !== undefined) metadata.minLength = schema.minLength\n if (schema.maxLength !== undefined) metadata.maxLength = schema.maxLength\n if (schema.pattern) metadata.pattern = schema.pattern\n if (schema.format) metadata.format = schema.format\n\n // Array info\n if (schemaType === 'array') {\n metadata.isArray = true\n if (schema.items && !Array.isArray(schema.items)) {\n const itemType = Array.isArray(schema.items.type) ? schema.items.type[0] : schema.items.type\n metadata.itemType = itemType || 'unknown'\n }\n }\n\n return metadata\n}\n",
|
|
46
|
+
"/**\n * Naming utilities for converting MCP tool names to CLI command names\n */\n\n/**\n * Convert a string to kebab-case\n * Handles snake_case, camelCase, and PascalCase\n *\n * @example\n * toKebabCase('create_issue') // 'create-issue'\n * toKebabCase('createIssue') // 'create-issue'\n * toKebabCase('CreateIssue') // 'create-issue'\n */\nexport function toKebabCase(str: string): string {\n return str\n // Handle snake_case\n .replace(/_/g, '-')\n // Handle camelCase and PascalCase\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .toLowerCase()\n}\n\n/**\n * Convert MCP tool name to CLI command name\n * Applies toKebabCase and optionally adds namespace prefix\n *\n * @example\n * toCommandName('create_issue', 'linear') // 'linear:create-issue'\n * toCommandName('list-users') // 'list-users'\n */\nexport function toCommandName(toolName: string, namespace?: string): string {\n const kebabName = toKebabCase(toolName)\n return namespace ? `${namespace}:${kebabName}` : kebabName\n}\n\n/**\n * Convert MCP property name to CLI flag name\n * Uses kebab-case for consistency with CLI conventions\n *\n * @example\n * toFlagName('teamId') // 'team-id'\n * toFlagName('issue_priority') // 'issue-priority'\n */\nexport function toFlagName(propName: string): string {\n return toKebabCase(propName)\n}\n\n/**\n * Convert string to PascalCase\n * Handles kebab-case, snake_case, and namespaced names\n * Used for generating TypeScript variable names\n *\n * @example\n * toPascalCase('create-issue') // 'CreateIssue'\n * toPascalCase('create_issue') // 'CreateIssue'\n * toPascalCase('linear:list-users') // 'LinearListUsers'\n * toPascalCase('company_research_exa') // 'CompanyResearchExa'\n */\nexport function toPascalCase(str: string): string {\n return str\n .split(/[-:_]/)\n .filter(Boolean)\n .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())\n .join('')\n}\n\n/**\n * Convert kebab-case to camelCase\n * Used for generating TypeScript property names\n *\n * @example\n * toCamelCase('team-id') // 'teamId'\n * toCamelCase('issue-priority') // 'issuePriority'\n */\nexport function toCamelCase(str: string): string {\n const pascal = toPascalCase(str)\n return pascal.charAt(0).toLowerCase() + pascal.slice(1)\n}\n\n/**\n * Escape string for use in TypeScript string literals\n */\nexport function escapeString(str: string): string {\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\t/g, '\\\\t')\n}\n\n/**\n * Generate a unique variable name from a command name\n * Ensures uniqueness by tracking used names\n */\nexport function generateUniqueVarName(\n commandName: string,\n usedNames: Set<string>\n): string {\n let baseName = toPascalCase(commandName)\n let uniqueName = baseName\n let counter = 1\n\n while (usedNames.has(uniqueName)) {\n uniqueName = `${baseName}${counter}`\n counter++\n }\n\n usedNames.add(uniqueName)\n return uniqueName\n}\n",
|
|
47
|
+
"/**\n * MCP Tool to Bunli Command converter\n *\n * This is the core transformation layer - converts MCP tool schemas\n * into Bunli commands. No SDK dependency, just pure transformation.\n */\n\nimport { option, type Command, type CLIOption, type Options } from '@bunli/core'\nimport { Result } from 'better-result'\nimport type { MCPTool, ConvertOptions, MCPCommand } from './types.js'\nimport { ConvertToolsError } from './errors.js'\nimport { jsonSchemaToZodSchema } from './schema-to-zod.js'\nimport { toCommandName, toFlagName } from './utils.js'\n\n/**\n * Convert MCP tools to Bunli commands\n *\n * This is the main entry point for the plugin. Takes an array of MCP tool\n * objects (fetched by your MCP client) and converts them to Bunli commands.\n *\n * @example\n * ```typescript\n * // Fetch tools from your MCP client\n * const tools = await yourMcpClient.listTools()\n *\n * // Convert to Bunli commands\n * const commands = createCommandsFromMCPTools(tools, {\n * namespace: 'linear',\n * createHandler: (toolName) => async ({ flags }) => {\n * return yourMcpClient.call(toolName, flags)\n * }\n * })\n *\n * // Register with CLI\n * commands.forEach(cmd => cli.command(cmd))\n * ```\n */\nexport function createCommandsFromMCPTools<TStore = Record<string, unknown>>(\n tools: MCPTool[],\n options: ConvertOptions<TStore>\n): Result<MCPCommand<TStore>[], ConvertToolsError> {\n const commands: MCPCommand<TStore>[] = []\n\n for (const tool of tools) {\n const converted = convertToolToCommand(tool, options)\n if (Result.isError(converted)) {\n return converted\n }\n commands.push(converted.value)\n }\n\n return Result.ok(commands)\n}\n\n/**\n * Convert a single MCP tool to a Bunli command\n */\nfunction convertToolToCommand<TStore>(\n tool: MCPTool,\n options: ConvertOptions<TStore>\n): Result<MCPCommand<TStore>, ConvertToolsError> {\n const {\n namespace,\n createHandler,\n commandName: customCommandName,\n flagName: customFlagName\n } = options\n\n // Generate command name\n const commandName = customCommandName\n ? customCommandName(tool.name)\n : toCommandName(tool.name, namespace)\n\n // Convert input schema to Bunli options\n const bunliOptionsResult = convertInputSchemaToOptions(\n tool.name,\n tool.inputSchema,\n customFlagName || toFlagName\n )\n if (Result.isError(bunliOptionsResult)) {\n return bunliOptionsResult\n }\n\n // Create the command\n const command: MCPCommand<TStore> = {\n name: commandName,\n description: tool.description || `Invoke MCP tool: ${tool.name}`,\n options: bunliOptionsResult.value,\n handler: createHandler(tool.name)\n }\n\n return Result.ok(command)\n}\n\n/**\n * Convert MCP inputSchema to Bunli options\n */\nfunction convertInputSchemaToOptions(\n toolName: string,\n inputSchema: MCPTool['inputSchema'],\n flagNameTransform: (name: string) => string\n): Result<Options, ConvertToolsError> {\n const bunliOptions: Options = {}\n\n if (!inputSchema?.properties) {\n return Result.ok(bunliOptions)\n }\n\n const requiredFields = new Set(inputSchema.required || [])\n\n for (const [propName, propSchema] of Object.entries(inputSchema.properties)) {\n // Convert JSON Schema to Zod\n const schemaResult = jsonSchemaToZodSchema(propSchema, { coerce: true })\n if (Result.isError(schemaResult)) {\n return Result.err(\n new ConvertToolsError({\n toolName,\n message: `Failed to convert input schema field \"${propName}\" for tool \"${toolName}\"`,\n cause: schemaResult.error\n })\n )\n }\n let zodSchema = schemaResult.value\n\n // Apply default if present\n if (propSchema.default !== undefined) {\n zodSchema = zodSchema.default(propSchema.default)\n }\n\n // Make optional if not required\n if (!requiredFields.has(propName)) {\n zodSchema = zodSchema.optional()\n }\n\n // Convert property name to flag name\n const flagName = flagNameTransform(propName)\n\n // Extract short option from description if present (e.g., \"[-t] Title\")\n const shortMatch = propSchema.description?.match(/^\\[-([a-zA-Z])\\]\\s*/)\n const short = shortMatch ? shortMatch[1] : undefined\n const description = shortMatch\n ? propSchema.description?.slice(shortMatch[0].length)\n : propSchema.description\n\n // Create CLI option\n bunliOptions[flagName] = option(zodSchema, {\n description,\n short\n })\n }\n\n return Result.ok(bunliOptions)\n}\n\n/**\n * Convert a single MCP tool to command metadata (for codegen)\n *\n * Returns metadata that can be used to generate TypeScript types\n * without creating the full command object.\n */\nexport interface MCPCommandMetadata {\n name: string\n toolName: string\n namespace?: string\n description?: string\n options: Record<string, {\n type: string\n required: boolean\n description?: string\n short?: string\n enumValues?: Array<string | number>\n minimum?: number\n maximum?: number\n hasDefault?: boolean\n default?: unknown\n }>\n}\n\nexport function extractCommandMetadata(\n tool: MCPTool,\n namespace?: string,\n flagNameTransform: (name: string) => string = toFlagName\n): MCPCommandMetadata {\n const commandName = toCommandName(tool.name, namespace)\n\n const optionsMeta: MCPCommandMetadata['options'] = {}\n\n if (tool.inputSchema?.properties) {\n const requiredFields = new Set(tool.inputSchema.required || [])\n\n for (const [propName, propSchema] of Object.entries(tool.inputSchema.properties)) {\n const flagName = flagNameTransform(propName)\n const schemaType = Array.isArray(propSchema.type) ? propSchema.type[0] : propSchema.type\n\n // Extract short option\n const shortMatch = propSchema.description?.match(/^\\[-([a-zA-Z])\\]\\s*/)\n const short = shortMatch ? shortMatch[1] : undefined\n const description = shortMatch\n ? propSchema.description?.slice(shortMatch[0].length)\n : propSchema.description\n\n optionsMeta[flagName] = {\n type: schemaType || 'unknown',\n required: requiredFields.has(propName) && propSchema.default === undefined,\n description,\n short,\n hasDefault: propSchema.default !== undefined,\n default: propSchema.default\n }\n\n // Add constraints\n if (propSchema.enum) {\n optionsMeta[flagName]!.enumValues = propSchema.enum.filter(\n (v): v is string | number => typeof v === 'string' || typeof v === 'number'\n )\n }\n if (propSchema.minimum !== undefined) {\n optionsMeta[flagName]!.minimum = propSchema.minimum\n }\n if (propSchema.maximum !== undefined) {\n optionsMeta[flagName]!.maximum = propSchema.maximum\n }\n }\n }\n\n return {\n name: commandName,\n toolName: tool.name,\n namespace,\n description: tool.description,\n options: optionsMeta\n }\n}\n",
|
|
48
|
+
"/**\n * Type generation for MCP commands\n *\n * Generates TypeScript files with Zod schemas and module augmentation\n * for RegisteredCommands. Similar to Prisma's client generation pattern.\n */\n\nimport { mkdir, writeFile } from 'fs/promises'\nimport { join } from 'path'\nimport { Result } from 'better-result'\nimport type { GenerateTypesOptions, MCPToolGroup, MCPTool, JSONSchema7 } from './types.js'\nimport { extractCommandMetadata, type MCPCommandMetadata } from './converter.js'\nimport { toPascalCase, escapeString } from './utils.js'\nimport { GenerateMCPTypesError } from './errors.js'\n\n/**\n * Generate TypeScript types for MCP commands\n *\n * Creates files like `.bunli/mcp-linear.gen.ts` with:\n * - Zod schemas for each command's options\n * - TypeScript types inferred from schemas\n * - Module augmentation for RegisteredCommands\n *\n * @example\n * ```typescript\n * await generateMCPTypes({\n * tools: [\n * { namespace: 'linear', tools: linearTools }\n * ],\n * outputDir: '.bunli'\n * })\n * ```\n */\nexport async function generateMCPTypes(\n options: GenerateTypesOptions\n): Promise<Result<void, GenerateMCPTypesError>> {\n const { tools, outputDir } = options\n\n return await Result.tryPromise({\n try: async () => {\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true })\n\n // Generate a file for each namespace\n for (const { namespace, tools: toolList } of tools) {\n if (!toolList || toolList.length === 0) continue\n\n const content = generateNamespaceTypes(namespace, toolList)\n const fileName = `mcp-${namespace}.gen.ts`\n const filePath = join(outputDir, fileName)\n\n await writeFile(filePath, content, 'utf-8')\n }\n\n // Generate index file that re-exports all\n const indexContent = generateIndexFile(tools)\n await writeFile(join(outputDir, 'mcp-index.gen.ts'), indexContent, 'utf-8')\n },\n catch: (cause) =>\n new GenerateMCPTypesError({\n outputDir,\n message: `Failed to generate MCP types in ${outputDir}`,\n cause\n })\n })\n}\n\n/**\n * Generate types for a single namespace\n */\nfunction generateNamespaceTypes(namespace: string, tools: MCPTool[]): string {\n const lines: string[] = []\n\n // Header\n lines.push(`// This file was automatically generated by @bunli/plugin-mcp`)\n lines.push(`// DO NOT EDIT - changes will be overwritten`)\n lines.push(``)\n lines.push(`import type { Command, Options, CLIOption } from '@bunli/core'`)\n lines.push(`import { option } from '@bunli/core'`)\n lines.push(`import { z } from 'zod'`)\n lines.push(``)\n\n // Extract metadata for all tools\n const metadata = tools.map(tool => extractCommandMetadata(tool, namespace))\n\n // Generate Zod schemas and types for each command\n for (const cmd of metadata) {\n lines.push(generateCommandSchema(cmd))\n lines.push(``)\n }\n\n // Generate module augmentation\n lines.push(generateModuleAugmentation(namespace, metadata))\n\n return lines.join('\\n')\n}\n\n/**\n * Generate Zod schema and types for a single command\n */\nfunction generateCommandSchema(cmd: MCPCommandMetadata): string {\n const lines: string[] = []\n const baseName = toPascalCase(cmd.name)\n\n // Generate options object\n lines.push(`// ${cmd.description || cmd.toolName}`)\n lines.push(`export const ${baseName}Options = {`)\n\n for (const [flagName, opt] of Object.entries(cmd.options)) {\n const zodSchema = generateZodSchemaString(opt)\n const metadata: string[] = []\n\n if (opt.description) {\n metadata.push(`description: '${escapeString(opt.description)}'`)\n }\n if (opt.short) {\n metadata.push(`short: '${opt.short}'`)\n }\n\n const metadataStr = metadata.length > 0 ? `, { ${metadata.join(', ')} }` : ''\n lines.push(` '${flagName}': option(${zodSchema}${metadataStr}),`)\n }\n\n lines.push(`} as const`)\n lines.push(``)\n\n // Generate flags type\n lines.push(`export type ${baseName}Flags = {`)\n\n for (const [flagName, opt] of Object.entries(cmd.options)) {\n const tsType = jsonSchemaTypeToTS(opt.type, opt.enumValues)\n const optional = !opt.required ? '?' : ''\n lines.push(` '${flagName}'${optional}: ${tsType}`)\n }\n\n lines.push(`}`)\n\n return lines.join('\\n')\n}\n\n/**\n * Generate Zod schema string from option metadata\n */\nfunction generateZodSchemaString(opt: MCPCommandMetadata['options'][string]): string {\n let schema: string\n\n // Handle enum types\n if (opt.enumValues && opt.enumValues.length > 0) {\n if (opt.enumValues.every((v): v is string => typeof v === 'string')) {\n const values = opt.enumValues.map(v => `'${escapeString(v as string)}'`).join(', ')\n schema = `z.enum([${values}])`\n } else {\n const literals = opt.enumValues.map(v =>\n typeof v === 'string' ? `z.literal('${escapeString(v)}')` : `z.literal(${v})`\n ).join(', ')\n schema = `z.union([${literals}])`\n }\n } else {\n // Handle base types\n switch (opt.type) {\n case 'string':\n schema = 'z.string()'\n break\n case 'number':\n schema = 'z.coerce.number()'\n break\n case 'integer':\n schema = 'z.coerce.number().int()'\n break\n case 'boolean':\n schema = 'z.boolean()'\n break\n case 'array':\n schema = 'z.array(z.unknown())'\n break\n case 'object':\n schema = 'z.record(z.unknown())'\n break\n default:\n schema = 'z.unknown()'\n }\n }\n\n // Add constraints\n if (opt.minimum !== undefined) {\n schema += `.min(${opt.minimum})`\n }\n if (opt.maximum !== undefined) {\n schema += `.max(${opt.maximum})`\n }\n\n // Add default\n if (opt.hasDefault && opt.default !== undefined) {\n const defaultVal = typeof opt.default === 'string'\n ? `'${escapeString(opt.default)}'`\n : JSON.stringify(opt.default)\n schema += `.default(${defaultVal})`\n }\n\n // Make optional if not required\n if (!opt.required && !opt.hasDefault) {\n schema += '.optional()'\n }\n\n return schema\n}\n\n/**\n * Convert JSON Schema type to TypeScript type\n */\nfunction jsonSchemaTypeToTS(\n type: string,\n enumValues?: Array<string | number>\n): string {\n // Handle enum\n if (enumValues && enumValues.length > 0) {\n return enumValues\n .map(v => typeof v === 'string' ? `'${escapeString(v)}'` : String(v))\n .join(' | ')\n }\n\n switch (type) {\n case 'string':\n return 'string'\n case 'number':\n case 'integer':\n return 'number'\n case 'boolean':\n return 'boolean'\n case 'array':\n return 'unknown[]'\n case 'object':\n return 'Record<string, unknown>'\n case 'null':\n return 'null'\n default:\n return 'unknown'\n }\n}\n\n/**\n * Generate module augmentation for RegisteredCommands\n */\nfunction generateModuleAugmentation(\n namespace: string,\n metadata: MCPCommandMetadata[]\n): string {\n const lines: string[] = []\n\n lines.push(`// Module augmentation for type-safe execute()`)\n lines.push(`declare module '@bunli/core' {`)\n lines.push(` interface RegisteredCommands {`)\n\n for (const cmd of metadata) {\n const baseName = toPascalCase(cmd.name)\n lines.push(` '${cmd.name}': Command<typeof ${baseName}Options>`)\n }\n\n lines.push(` }`)\n lines.push(`}`)\n\n return lines.join('\\n')\n}\n\n/**\n * Generate index file that re-exports all namespaces\n */\nfunction generateIndexFile(toolGroups: MCPToolGroup[]): string {\n const lines: string[] = []\n\n lines.push(`// This file was automatically generated by @bunli/plugin-mcp`)\n lines.push(`// DO NOT EDIT - changes will be overwritten`)\n lines.push(``)\n\n for (const { namespace } of toolGroups) {\n if (!namespace) continue\n lines.push(`export * from './mcp-${namespace}.gen.js'`)\n }\n\n return lines.join('\\n')\n}\n",
|
|
49
|
+
"import { defineCommand } from \"@bunli/core\";\nimport { z } from \"zod\";\n\nimport {\n\tCLI_DIAGNOSTIC_CODES,\n\tcreateCliCommandError,\n\tserializeCliDiagnosticError,\n} from \"@wp-typia/project-tools/cli-diagnostics\";\nimport { getMcpSchemaSources } from \"../config\";\nimport { loadMcpToolGroups, syncMcpSchemas } from \"../mcp\";\n\nexport const mcpCommand = defineCommand({\n\tdefaultFormat: \"json\",\n\tdescription: \"Inspect or sync schema-driven MCP metadata for wp-typia.\",\n\thandler: async (args) => {\n\t\tconst subcommand = args.positional[0] ?? \"list\";\n\t\tconst prefersStructuredOutput =\n\t\t\t(args.formatExplicit && args.format !== \"toon\") ||\n\t\t\targs.agent ||\n\t\t\tBoolean(args.context?.store?.isAIAgent);\n\t\tconst userConfig =\n\t\t\targs.context?.store?.wpTypiaUserConfig &&\n\t\t\ttypeof args.context.store.wpTypiaUserConfig === \"object\"\n\t\t\t\t? args.context.store.wpTypiaUserConfig\n\t\t\t\t: {};\n\t\tconst schemaSources = getMcpSchemaSources(userConfig);\n\n\t\tif (schemaSources.length === 0) {\n\t\t\tconst error = createCliCommandError({\n\t\t\t\tcode: CLI_DIAGNOSTIC_CODES.CONFIGURATION_MISSING,\n\t\t\t\tcommand: \"mcp\",\n\t\t\t\tdetailLines: [\n\t\t\t\t\t\"No MCP schema sources are configured. Add `mcp.schemaSources` in ~/.config/wp-typia/config.json, .wp-typiarc(.json), or package.json#wp-typia.\",\n\t\t\t\t],\n\t\t\t});\n\t\t\tif (prefersStructuredOutput) {\n\t\t\t\targs.output({ ok: false, error: serializeCliDiagnosticError(error) });\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\tif (subcommand === \"list\") {\n\t\t\tconst groups = await loadMcpToolGroups(args.cwd, schemaSources);\n\t\t\tconst summary = groups.map((group) => ({\n\t\t\t\tnamespace: group.namespace,\n\t\t\t\ttoolCount: group.tools.length,\n\t\t\t\ttools: group.tools.map((tool) => tool.name),\n\t\t\t}));\n\t\t\tif (prefersStructuredOutput) {\n\t\t\t\targs.output({ groups: summary });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (const group of summary) {\n\t\t\t\tconsole.log(`${group.namespace} (${group.toolCount})`);\n\t\t\t\tfor (const tool of group.tools) {\n\t\t\t\t\tconsole.log(` - ${tool}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (subcommand === \"sync\") {\n\t\t\tconst outputDir =\n\t\t\t\t(args.flags[\"output-dir\"] as string | undefined) ?? `${args.cwd}/.bunli/mcp`;\n\t\t\tconst result = await syncMcpSchemas(args.cwd, schemaSources, outputDir);\n\t\t\tconsole.log(\n\t\t\t\t`Synced ${result.commandCount} MCP tools across ${result.groups.length} namespaces into ${result.outputDir}.`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst error = createCliCommandError({\n\t\t\tcode: CLI_DIAGNOSTIC_CODES.INVALID_COMMAND,\n\t\t\tcommand: \"mcp\",\n\t\t\tdetailLines: [`Unknown mcp subcommand \"${subcommand}\". Expected list or sync.`],\n\t\t});\n\t\tif (prefersStructuredOutput) {\n\t\t\targs.output({ ok: false, error: serializeCliDiagnosticError(error) });\n\t\t\tprocess.exitCode = 1;\n\t\t\treturn;\n\t\t}\n\t\tthrow error;\n\t},\n\tname: \"mcp\",\n\toptions: {\n\t\t\"output-dir\": {\n\t\t\tdescription: \"Output directory for generated MCP metadata during `mcp sync`.\",\n\t\t\tschema: z.string().optional(),\n\t\t},\n\t},\n});\n\nexport default mcpCommand;\n",
|
|
50
|
+
"import { createElement } from \"react\";\n\nimport { defineCommand } from \"@bunli/core\";\n\nimport {\n\tbuildCommandOptions,\n\tMIGRATE_OPTION_METADATA,\n\tresolveCommandOptionValues,\n} from \"../command-option-metadata\";\nimport { resolveBundledModuleHref } from \"../render-loader\";\nimport { executeMigrateCommand } from \"../runtime-bridge\";\nimport { supportsInteractiveTui } from \"../runtime-capabilities\";\nimport type { WpTypiaRenderArgs } from \"./render-types\";\nimport { LazyFlow } from \"../ui/lazy-flow\";\n\nfunction loadMigrateFlow() {\n\treturn import(\n\t\tresolveBundledModuleHref(import.meta.url, [\n\t\t\t\"./ui/migrate-flow.js\",\n\t\t\t\"../ui/migrate-flow.js\",\n\t\t\t\"../ui/migrate-flow.tsx\",\n\t\t], {\n\t\t\tmoduleLabel: \"the migrate-flow UI\",\n\t\t}),\n\t).then((module) => ({ default: module.MigrateFlow }));\n}\n\nconst migrateOptions = buildCommandOptions(MIGRATE_OPTION_METADATA);\n\nexport const migrateCommand = defineCommand({\n\tdefaultFormat: \"toon\",\n\tdescription: \"Run migration workflows for migration-capable wp-typia projects.\",\n\thandler: async (args) => {\n\t\tawait executeMigrateCommand({\n\t\t\tcommand: args.positional[0],\n\t\t\tcwd: args.cwd,\n\t\t\tflags: args.flags as Record<string, unknown>,\n\t\t});\n\t},\n\tname: \"migrate\",\n\toptions: migrateOptions,\n\t...(supportsInteractiveTui()\n\t\t? {\n\t\t\t\trender: (args: WpTypiaRenderArgs) => {\n\t\t\t\t\tconst initialValues = resolveCommandOptionValues(MIGRATE_OPTION_METADATA, {\n\t\t\t\t\t\tflags: args.flags as Record<string, unknown>,\n\t\t\t\t\t});\n\n\t\t\t\t\treturn createElement(LazyFlow, {\n\t\t\t\t\t\tloader: loadMigrateFlow,\n\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\tcwd: args.cwd,\n\t\t\t\t\t\t\tinitialValues: {\n\t\t\t\t\t\t\t\t...initialValues,\n\t\t\t\t\t\t\t\tcommand:\n\t\t\t\t\t\t\t\t\t(args.positional[0] as\n\t\t\t\t\t\t\t\t\t\t| \"init\"\n\t\t\t\t\t\t\t\t\t\t| \"snapshot\"\n\t\t\t\t\t\t\t\t\t\t| \"plan\"\n\t\t\t\t\t\t\t\t\t\t| \"wizard\"\n\t\t\t\t\t\t\t\t\t\t| \"diff\"\n\t\t\t\t\t\t\t\t\t\t| \"scaffold\"\n\t\t\t\t\t\t\t\t\t\t| \"verify\"\n\t\t\t\t\t\t\t\t\t\t| \"doctor\"\n\t\t\t\t\t\t\t\t\t\t| \"fixtures\"\n\t\t\t\t\t\t\t\t\t\t| \"fuzz\"\n\t\t\t\t\t\t\t\t\t\t| undefined) ?? \"plan\",\n\t\t\t\t\t\t\t\t\"to-migration-version\":\n\t\t\t\t\t\t\t\t\t(initialValues[\"to-migration-version\"] as string | undefined) ??\n\t\t\t\t\t\t\t\t\t\"current\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\ttui: {\n\t\t\t\t\trenderer: {\n\t\t\t\t\t\tbufferMode: \"alternate\" as const,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t: {}),\n});\n\nexport default migrateCommand;\n",
|
|
51
|
+
"import { defineCommand } from \"@bunli/core\";\nimport { z } from \"zod\";\n\nimport { createCliCommandError } from \"@wp-typia/project-tools/cli-diagnostics\";\nimport { executeSyncCommand } from \"../runtime-bridge\";\n\nexport const syncCommand = defineCommand({\n\tdescription:\n\t\t\"Run the generated-project sync workflow from a scaffolded project or official workspace root.\",\n\thandler: async (args) => {\n\t\ttry {\n\t\t\tawait executeSyncCommand({\n\t\t\t\tcheck: Boolean(args.flags.check),\n\t\t\t\tcwd: args.cwd,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow createCliCommandError({\n\t\t\t\tcommand: \"sync\",\n\t\t\t\terror,\n\t\t\t});\n\t\t}\n\t},\n\tname: \"sync\",\n\toptions: {\n\t\tcheck: {\n\t\t\targumentKind: \"flag\" as const,\n\t\t\tdescription:\n\t\t\t\t\"Check generated artifacts without writing changes. Advanced sync-types-only flags stay on sync-types.\",\n\t\t\tschema: z.boolean().default(false),\n\t\t},\n\t},\n});\n\nexport default syncCommand;\n",
|
|
52
|
+
"import { defineCommand } from \"@bunli/core\";\n\nimport {\n\tCLI_DIAGNOSTIC_CODES,\n\tcreateCliCommandError,\n} from \"@wp-typia/project-tools/cli-diagnostics\";\nimport {\n\tbuildCommandOptions,\n\tTEMPLATES_OPTION_METADATA,\n} from \"../command-option-metadata\";\nimport { executeTemplatesCommand, listTemplatesForRuntime } from \"../runtime-bridge\";\n\nexport const templatesCommand = defineCommand({\n\tdefaultFormat: \"json\",\n\tdescription: \"Inspect built-in and external scaffold templates.\",\n\thandler: async (args) => {\n\t\tconst subcommand = (args.positional[0] ?? \"list\") as string;\n\t\tconst id = args.positional[1] ?? (args.flags.id as string | undefined);\n\t\tconst effectiveSubcommand =\n\t\t\tsubcommand === \"list\" && typeof id === \"string\" && id.length > 0\n\t\t\t\t? \"inspect\"\n\t\t\t\t: subcommand;\n\t\tconst prefersStructuredOutput =\n\t\t\t(args.formatExplicit && args.format !== \"toon\") ||\n\t\t\targs.agent ||\n\t\t\tBoolean(args.context?.store?.isAIAgent);\n\n\t\tif (prefersStructuredOutput) {\n\t\t\tconst templates = await listTemplatesForRuntime();\n\t\t\tif (effectiveSubcommand === \"list\") {\n\t\t\t\targs.output({ templates });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (effectiveSubcommand === \"inspect\" && id) {\n\t\t\t\tconst template = templates.find((entry) => entry.id === id);\n\t\t\t\tif (!template) {\n\t\t\t\t\tthrow createCliCommandError({\n\t\t\t\t\t\tcode: CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT,\n\t\t\t\t\t\tcommand: \"templates\",\n\t\t\t\t\t\tdetailLines: [`Unknown template \"${id}\".`],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\targs.output({ template });\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tawait executeTemplatesCommand({\n\t\t\tflags: { id, subcommand: effectiveSubcommand },\n\t\t});\n\t},\n\tname: \"templates\",\n\toptions: buildCommandOptions(TEMPLATES_OPTION_METADATA),\n});\n\nexport default templatesCommand;\n",
|
|
53
|
+
"import type { Command } from \"@bunli/core\";\n\nimport { addCommand } from \"./commands/add\";\nimport { createCommand } from \"./commands/create\";\nimport { doctorCommand } from \"./commands/doctor\";\nimport { mcpCommand } from \"./commands/mcp\";\nimport { migrateCommand } from \"./commands/migrate\";\nimport { syncCommand } from \"./commands/sync\";\nimport { templatesCommand } from \"./commands/templates\";\n\nexport const wpTypiaCommands: Command<any, any>[] = [\n\tcreateCommand,\n\tsyncCommand,\n\taddCommand,\n\tmigrateCommand,\n\ttemplatesCommand,\n\tdoctorCommand,\n\tmcpCommand,\n];\n"
|
|
54
|
+
],
|
|
55
|
+
"mappings": "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",
|
|
56
|
+
"debugId": "F0B5D9D741D873E864756E2164756E21",
|
|
57
|
+
"names": []
|
|
58
|
+
}
|