fix(cli): clean terminal output for TUI apps
- Silence Vite server logs (logLevel: 'silent') - Intercept console.log/info/warn/error/debug in child process to suppress [vite] and [Vue warn] noise from HMR client - Add 3s startup grace period to ignore initial reload requests (prevents double-spawn on first WS connection) - Clear screen before spawning child process Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@vue-tui/example-flappy-bird",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"build": "vite build",
|
|
||||||
"start": "vite build && node dist/game.mjs"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@vue-tui/runtime": "workspace:*",
|
|
||||||
"chalk": "^5.3.0",
|
|
||||||
"vue": "^3.4.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@vitejs/plugin-vue": "^6",
|
|
||||||
"vite": "catalog:"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { shallowRef } from "vue";
|
|
||||||
import { Box, Text, useFocusManager, useInput } from "@vue-tui/runtime";
|
|
||||||
import Item from "./Item.vue";
|
|
||||||
|
|
||||||
const items = ["apple", "banana", "orange", "grape", "watermelon"];
|
|
||||||
const selected = shallowRef<null | string>(null);
|
|
||||||
|
|
||||||
const focusManager = useFocusManager();
|
|
||||||
|
|
||||||
useInput((input, key) => {
|
|
||||||
if (key.upArrow) {
|
|
||||||
focusManager.focusPrevious();
|
|
||||||
} else if (key.downArrow) {
|
|
||||||
focusManager.focusNext();
|
|
||||||
} else if (key.return) {
|
|
||||||
console.log("Selected:", focusManager.activeId);
|
|
||||||
selected.value = focusManager.activeId;
|
|
||||||
}
|
|
||||||
if (input === "q") {
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Box>
|
|
||||||
<Item v-for="item in items" :key="item" :id="item" :label="item" />
|
|
||||||
<Text>You selected: {{ focusManager.activeId }}</Text>
|
|
||||||
</Box>
|
|
||||||
</template>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { Box, Text, useFocus } from "@vue-tui/runtime";
|
|
||||||
const props = defineProps<{
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const focusCtx = useFocus({ id: props.id });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Box>
|
|
||||||
<Text :color="focusCtx.isFocused.value ? 'cyan' : 'undefined'"
|
|
||||||
>{{ focusCtx.isFocused.value ? ">" : " " }} {{ props.label }}</Text
|
|
||||||
>
|
|
||||||
</Box>
|
|
||||||
</template>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// Flappy Bird example for @vue-tui/runtime.
|
|
||||||
//
|
|
||||||
// pnpm --filter @vue-tui/example-flappy-bird start
|
|
||||||
//
|
|
||||||
// Controls: space / ↑ / w to flap, q or Ctrl-C to quit, r to restart after dying.
|
|
||||||
|
|
||||||
import { createApp } from "@vue-tui/runtime";
|
|
||||||
import App from "./App.vue";
|
|
||||||
|
|
||||||
createApp(App).mount();
|
|
||||||
Vendored
-6
@@ -1,6 +0,0 @@
|
|||||||
declare module "*.vue" {
|
|
||||||
import type { DefineComponent } from "vue";
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const component: DefineComponent<{}, {}, any>;
|
|
||||||
export default component;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "esnext",
|
|
||||||
"lib": ["es2023"],
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"types": ["node"],
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"jsx": "preserve"
|
|
||||||
},
|
|
||||||
"include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"]
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { defineConfig } from "vite";
|
|
||||||
import vue from "@vitejs/plugin-vue";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
|
|
||||||
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
||||||
|
|
||||||
// Build the example as a Node ESM bundle. SFCs are compiled by
|
|
||||||
// @vitejs/plugin-vue; runtime deps (vue, @vue-tui/runtime, chalk, …) stay
|
|
||||||
// external so Node resolves them from node_modules at startup.
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [vue()],
|
|
||||||
build: {
|
|
||||||
target: "node22",
|
|
||||||
outDir: "dist",
|
|
||||||
emptyOutDir: true,
|
|
||||||
minify: false,
|
|
||||||
lib: {
|
|
||||||
entry: `${here}src/main.ts`,
|
|
||||||
formats: ["es"],
|
|
||||||
fileName: () => "game.mjs",
|
|
||||||
},
|
|
||||||
rollupOptions: {
|
|
||||||
// Bundle anything resolved as a relative / absolute path (the .vue
|
|
||||||
// file + its `?vue` virtual modules); externalize bare specifiers.
|
|
||||||
external: (id) => !id.startsWith(".") && !id.startsWith("/") && !id.startsWith("\0"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -17,6 +17,7 @@ export async function dev(entry?: string) {
|
|||||||
|
|
||||||
const server = await createServer({
|
const server = await createServer({
|
||||||
plugins: [vueTuiDevPlugin({ entry })],
|
plugins: [vueTuiDevPlugin({ entry })],
|
||||||
|
logLevel: "silent",
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.listen();
|
await server.listen();
|
||||||
@@ -55,8 +56,16 @@ export async function dev(entry?: string) {
|
|||||||
|
|
||||||
pm.spawn();
|
pm.spawn();
|
||||||
|
|
||||||
|
// Ignore reload requests during startup — the initial WS connection
|
||||||
|
// may trigger vite:beforeFullReload before the app is ready
|
||||||
|
let acceptReloads = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
acceptReloads = true;
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
// Listen for full reload requests from child
|
// Listen for full reload requests from child
|
||||||
server.hot.on("vue-tui:request-reload", async () => {
|
server.hot.on("vue-tui:request-reload", async () => {
|
||||||
|
if (!acceptReloads) return;
|
||||||
const newPath = await extractBundle(clientEnv.memoryFiles, outDir);
|
const newPath = await extractBundle(clientEnv.memoryFiles, outDir);
|
||||||
pm.setBundlePath(newPath);
|
pm.setBundlePath(newPath);
|
||||||
pm.restart();
|
pm.restart();
|
||||||
|
|||||||
@@ -9,7 +9,22 @@ export interface ProcessManagerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loaderUrl = new URL("./hmr-loader.mjs", import.meta.url).href;
|
const loaderUrl = new URL("./hmr-loader.mjs", import.meta.url).href;
|
||||||
const loaderBootstrap = `data:text/javascript,import{register}from"node:module";register(${JSON.stringify(loaderUrl)})`;
|
|
||||||
|
// Bootstrap script: register HMR loader hooks + silence Vite/Vue console noise
|
||||||
|
const loaderBootstrap = `data:text/javascript,${encodeURIComponent(`
|
||||||
|
import{register}from"node:module";
|
||||||
|
register(${JSON.stringify(loaderUrl)});
|
||||||
|
|
||||||
|
// Intercept all console methods to suppress Vite HMR client and Vue warn noise
|
||||||
|
for (const method of ["log", "info", "warn", "error", "debug"]) {
|
||||||
|
const orig = console[method];
|
||||||
|
console[method] = (...args) => {
|
||||||
|
const first = typeof args[0] === "string" ? args[0] : "";
|
||||||
|
if (first.includes("[vite]") || first.includes("[Vue warn]")) return;
|
||||||
|
orig.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`)}`;
|
||||||
|
|
||||||
export function createProcessManager(options: ProcessManagerOptions) {
|
export function createProcessManager(options: ProcessManagerOptions) {
|
||||||
let child: ChildProcess | null = null;
|
let child: ChildProcess | null = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user