@ubean/cli 0.5.1 → 0.5.2
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/dist/cli.js +116 -21
- package/package.json +12 -12
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@ import { A as summarizeBundle, D as compareBundleBaseline, E as createFsOps, O a
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, watch } from "node:fs";
|
|
3
3
|
import { basename, join, relative, resolve } from "pathe";
|
|
4
4
|
import { extname, normalize, sep } from "node:path";
|
|
5
|
-
import { getLogger, setDebugLogging } from "@ubean/shared/logger";
|
|
5
|
+
import { getLogger, setDebugLogging, setMinLevel } from "@ubean/shared/logger";
|
|
6
6
|
import { defineCommand, runMain } from "citty";
|
|
7
7
|
import { BUILTIN_MODULES, isBuiltinDisabled, loadUbeanConfig, resolveModules, resolvePrerenderConfig, resolveSsrConfig } from "@ubean/config";
|
|
8
8
|
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -490,6 +490,32 @@ const buildCommand = {
|
|
|
490
490
|
//#region src/dev-server/vite-server.ts
|
|
491
491
|
const logger$8 = getLogger("dev-server");
|
|
492
492
|
/**
|
|
493
|
+
* 收编 Vite 原生日志(hmr update、依赖预构建提示等)到 ubean 分类闸门:
|
|
494
|
+
* - info 级别(如 `[vite] hmr update /src/x.vue`)默认静默;`logging.lifecycle` 开启时以 `[vite]` 前缀透传
|
|
495
|
+
* - warn/error 始终透传(不可静默)
|
|
496
|
+
*/
|
|
497
|
+
function createGatedViteLogger(logging) {
|
|
498
|
+
return {
|
|
499
|
+
info(msg) {
|
|
500
|
+
if (logging.lifecycle) logger$8.info(`[vite] ${msg}`);
|
|
501
|
+
},
|
|
502
|
+
warn(msg) {
|
|
503
|
+
logger$8.warn(msg);
|
|
504
|
+
},
|
|
505
|
+
warnOnce(msg) {
|
|
506
|
+
logger$8.warn(msg);
|
|
507
|
+
},
|
|
508
|
+
error(msg) {
|
|
509
|
+
logger$8.error(msg);
|
|
510
|
+
},
|
|
511
|
+
clearScreen() {},
|
|
512
|
+
hasWarned: false,
|
|
513
|
+
hasErrorLogged() {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
493
519
|
* Compute the vue-router locale param (e.g. `:locale(zh)?`) from the resolved
|
|
494
520
|
* i18n routing config, mirroring `localeVueParamFromI18n` (`@ubean/build`).
|
|
495
521
|
* Returns `''` when i18n is disabled or no prefix is needed.
|
|
@@ -760,6 +786,7 @@ async function createViteDevServer(options) {
|
|
|
760
786
|
viteServer = await createServer$1({
|
|
761
787
|
root: cwd,
|
|
762
788
|
configFile: userViteConfig ?? false,
|
|
789
|
+
customLogger: createGatedViteLogger({ lifecycle: config.logging.lifecycle }),
|
|
763
790
|
resolve: { dedupe: ["vue", "vue-router"] },
|
|
764
791
|
server: {
|
|
765
792
|
middlewareMode: { server: httpServer },
|
|
@@ -1100,7 +1127,13 @@ function formatDiagnostics(diagnostics) {
|
|
|
1100
1127
|
warnings
|
|
1101
1128
|
};
|
|
1102
1129
|
}
|
|
1103
|
-
|
|
1130
|
+
/**
|
|
1131
|
+
* 输出能力诊断结果。
|
|
1132
|
+
*
|
|
1133
|
+
* `'auto'` 语义(默认):健康时静默 —— "All capability checks passed" 仅在
|
|
1134
|
+
* `verbose` 时输出;告警/错误属于 warn/error 级别,始终输出。
|
|
1135
|
+
*/
|
|
1136
|
+
function logDiagnostics(diagnostics, options = {}) {
|
|
1104
1137
|
const { errors, warnings } = formatDiagnostics(diagnostics);
|
|
1105
1138
|
if (errors.length > 0) {
|
|
1106
1139
|
logger$7.error(`Capability errors (${errors.length}):`);
|
|
@@ -1110,7 +1143,7 @@ function logDiagnostics(diagnostics) {
|
|
|
1110
1143
|
logger$7.warn(`Capability warnings (${warnings.length}):`);
|
|
1111
1144
|
for (const w of warnings) logger$7.warn(w);
|
|
1112
1145
|
}
|
|
1113
|
-
if (errors.length === 0 && warnings.length === 0) logger$7.info("All capability checks passed");
|
|
1146
|
+
if (errors.length === 0 && warnings.length === 0 && options.verbose) logger$7.info("All capability checks passed");
|
|
1114
1147
|
}
|
|
1115
1148
|
//#endregion
|
|
1116
1149
|
//#region src/dev.ts
|
|
@@ -1164,6 +1197,14 @@ const devCommand = {
|
|
|
1164
1197
|
description: "Exit if the port is already in use, instead of auto-incrementing",
|
|
1165
1198
|
default: false
|
|
1166
1199
|
},
|
|
1200
|
+
verbose: {
|
|
1201
|
+
type: "boolean",
|
|
1202
|
+
description: "Show detailed startup logs (directories, scan, codegen, diagnostics, reload events)"
|
|
1203
|
+
},
|
|
1204
|
+
logRequests: {
|
|
1205
|
+
type: "boolean",
|
|
1206
|
+
description: "Log each request (GET /path 200 12ms). Ignored in ssg/spa modes."
|
|
1207
|
+
},
|
|
1167
1208
|
open: {
|
|
1168
1209
|
type: "boolean",
|
|
1169
1210
|
description: "Open browser on startup"
|
|
@@ -1176,27 +1217,47 @@ const devCommand = {
|
|
|
1176
1217
|
},
|
|
1177
1218
|
async run({ args }) {
|
|
1178
1219
|
const cwd = resolve(args.cwd || process.cwd());
|
|
1179
|
-
logger$6.info("Starting ubean dev server...");
|
|
1180
1220
|
registerBuiltinPresets();
|
|
1181
1221
|
const config = await loadUbeanConfig(cwd);
|
|
1222
|
+
const logging = config.logging;
|
|
1223
|
+
if (args.verbose) {
|
|
1224
|
+
logging.scan = true;
|
|
1225
|
+
logging.lifecycle = true;
|
|
1226
|
+
logging.diagnostics = true;
|
|
1227
|
+
}
|
|
1228
|
+
if (args.logRequests) {
|
|
1229
|
+
logging.request = true;
|
|
1230
|
+
logging.requestSuppressed = false;
|
|
1231
|
+
}
|
|
1232
|
+
const backendCapable = config.mode === "fullstack" || config.mode === "backend";
|
|
1233
|
+
if (logging.request && !backendCapable) {
|
|
1234
|
+
logging.request = false;
|
|
1235
|
+
logging.requestSuppressed = true;
|
|
1236
|
+
}
|
|
1237
|
+
if (logging.requestSuppressed) logger$6.warn(`Request logging is not applicable in "${config.mode}" mode — skipping.`);
|
|
1238
|
+
setMinLevel(logging.level);
|
|
1239
|
+
const startedAt = Date.now();
|
|
1240
|
+
if (logging.lifecycle) logger$6.info("Starting ubean dev server...");
|
|
1182
1241
|
const port = Number(args.port) || config.dev.port;
|
|
1183
1242
|
const host = args.host || config.dev.host;
|
|
1184
|
-
logger$6.info(`Root directory: ${config.rootDir}`);
|
|
1185
|
-
logger$6.info(`Source directory: ${config.srcDir}`);
|
|
1186
1243
|
const preset = resolvePresetByName(config.build.preset);
|
|
1187
|
-
|
|
1244
|
+
if (logging.scan) {
|
|
1245
|
+
logger$6.info(`Root directory: ${config.rootDir}`);
|
|
1246
|
+
logger$6.info(`Source directory: ${config.srcDir}`);
|
|
1247
|
+
logger$6.info(`Preset: ${preset.name}`);
|
|
1248
|
+
}
|
|
1188
1249
|
const capabilities = createCapabilitySet(preset.capabilities || {});
|
|
1189
|
-
logger$6.info("Running capability diagnostics...");
|
|
1250
|
+
if (logging.scan) logger$6.info("Running capability diagnostics...");
|
|
1190
1251
|
const diagnostics = diagnoseCapabilities(preset.name, preset.capabilities || {}, NODE_REQUIREMENTS);
|
|
1191
|
-
logDiagnostics(diagnostics);
|
|
1252
|
+
logDiagnostics(diagnostics, { verbose: logging.diagnostics });
|
|
1192
1253
|
if (!diagnostics.valid) logger$6.warn("Some capability requirements are not met. Dev server may not function correctly.");
|
|
1193
|
-
let { app: currentApp, layouts: currentLayouts, scanResult: currentScanResult } = await buildApp(cwd, config);
|
|
1254
|
+
let { app: currentApp, layouts: currentLayouts, scanResult: currentScanResult } = await buildApp(cwd, config, logging);
|
|
1194
1255
|
let rescanInProgress = false;
|
|
1195
1256
|
async function rescan() {
|
|
1196
1257
|
if (rescanInProgress) return;
|
|
1197
1258
|
rescanInProgress = true;
|
|
1198
1259
|
try {
|
|
1199
|
-
const { app: newApp, layouts: newLayouts, scanResult } = await buildApp(cwd, config);
|
|
1260
|
+
const { app: newApp, layouts: newLayouts, scanResult } = await buildApp(cwd, config, logging);
|
|
1200
1261
|
currentApp = newApp;
|
|
1201
1262
|
currentLayouts = newLayouts;
|
|
1202
1263
|
currentScanResult = scanResult;
|
|
@@ -1209,6 +1270,8 @@ const devCommand = {
|
|
|
1209
1270
|
rescanInProgress = false;
|
|
1210
1271
|
}
|
|
1211
1272
|
}
|
|
1273
|
+
let reloadStartedAt = Date.now();
|
|
1274
|
+
let lastChangedFile = null;
|
|
1212
1275
|
const runner = await createDevRunner({
|
|
1213
1276
|
cwd,
|
|
1214
1277
|
srcDir: config.srcDir,
|
|
@@ -1238,8 +1301,9 @@ const devCommand = {
|
|
|
1238
1301
|
},
|
|
1239
1302
|
onListen({ url, networkUrls }) {
|
|
1240
1303
|
const label = (text) => dim(text);
|
|
1304
|
+
const modeSuffix = dim(` (${config.mode} · ${preset.name})`);
|
|
1241
1305
|
const lines = [
|
|
1242
|
-
`${green(bold(
|
|
1306
|
+
`${green(bold(`🚀 ubean dev server ready in ${Date.now() - startedAt}ms`))}${modeSuffix}\n`,
|
|
1243
1307
|
` → ${label("Local:")} ${cyan(url)}`,
|
|
1244
1308
|
...(networkUrls || []).map((n) => ` → ${label("Network:")} ${cyan(n)}`)
|
|
1245
1309
|
];
|
|
@@ -1249,16 +1313,22 @@ const devCommand = {
|
|
|
1249
1313
|
lines.push(` → ${dim("Press Ctrl+C to stop")}`);
|
|
1250
1314
|
logger$6.info(lines.join("\n"));
|
|
1251
1315
|
if (hasBackend) generateOpenApiTypesFromServer(url, { outDir: resolve(cwd, ".ubean") }).then((filePath) => {
|
|
1252
|
-
if (filePath) logger$6.info(`OpenAPI types generated: ${filePath}`);
|
|
1316
|
+
if (filePath && logging.scan) logger$6.info(`OpenAPI types generated: ${filePath}`);
|
|
1253
1317
|
}).catch((err) => {
|
|
1254
1318
|
logger$6.warn(`Failed to generate OpenAPI types: ${err instanceof Error ? err.message : String(err)}`);
|
|
1255
1319
|
});
|
|
1256
1320
|
},
|
|
1257
1321
|
onBeforeReload() {
|
|
1258
|
-
|
|
1322
|
+
reloadStartedAt = Date.now();
|
|
1323
|
+
if (logging.lifecycle) logger$6.info("Reloading...");
|
|
1259
1324
|
},
|
|
1260
1325
|
onAfterReload() {
|
|
1261
|
-
logger$6.info("Reloaded");
|
|
1326
|
+
if (logging.lifecycle) logger$6.info("Reloaded");
|
|
1327
|
+
else {
|
|
1328
|
+
const file = lastChangedFile ? dim(` · ${lastChangedFile}`) : "";
|
|
1329
|
+
logger$6.info(dim(`↻ Reloaded in ${Date.now() - reloadStartedAt}ms${file}`));
|
|
1330
|
+
}
|
|
1331
|
+
lastChangedFile = null;
|
|
1262
1332
|
}
|
|
1263
1333
|
});
|
|
1264
1334
|
try {
|
|
@@ -1287,13 +1357,14 @@ const devCommand = {
|
|
|
1287
1357
|
async onChange(events) {
|
|
1288
1358
|
const relevantEvents = events.filter((e) => /\.(ts|js|vue|mjs|cjs|json)$/.test(e.relativePath) && !e.relativePath.includes(".bak"));
|
|
1289
1359
|
if (relevantEvents.length === 0) return;
|
|
1290
|
-
|
|
1360
|
+
lastChangedFile = relevantEvents[0].relativePath;
|
|
1361
|
+
if (logging.lifecycle) logger$6.info(`File change detected: ${lastChangedFile}`);
|
|
1291
1362
|
await rescan();
|
|
1292
1363
|
}
|
|
1293
1364
|
});
|
|
1294
1365
|
watcher.start();
|
|
1295
1366
|
const cleanup = async () => {
|
|
1296
|
-
logger$6.info("\nShutting down...");
|
|
1367
|
+
if (logging.lifecycle) logger$6.info("\nShutting down...");
|
|
1297
1368
|
watcher.stop();
|
|
1298
1369
|
await runner.stop();
|
|
1299
1370
|
process.exit(0);
|
|
@@ -1302,16 +1373,20 @@ const devCommand = {
|
|
|
1302
1373
|
process.on("SIGTERM", cleanup);
|
|
1303
1374
|
}
|
|
1304
1375
|
};
|
|
1305
|
-
|
|
1306
|
-
|
|
1376
|
+
/** 默认跳过请求日志的内部路径前缀(`/_health`、`/_devtools`、`/_openapi.json`、Vite 内部 `/@id/...` 等)。 */
|
|
1377
|
+
const REQUEST_LOG_INTERNAL_PREFIXES = ["/_", "/@"];
|
|
1378
|
+
/** 请求日志中,超过该毫秒数的请求提升为 warn(慢请求告警)。 */
|
|
1379
|
+
const REQUEST_LOG_SLOW_THRESHOLD = 1e3;
|
|
1380
|
+
async function buildApp(cwd, config, logging) {
|
|
1381
|
+
if (logging.scan) logger$6.info("Scanning project files...");
|
|
1307
1382
|
const result = await scanProject({
|
|
1308
1383
|
cwd,
|
|
1309
1384
|
srcDir: config.srcDir,
|
|
1310
1385
|
dirs: config.dir,
|
|
1311
1386
|
ignore: config.scanOptions?.ignore
|
|
1312
1387
|
});
|
|
1313
|
-
logger$6.info(`Found ${result.apiRoutes.length} API routes, ${result.pages.length} pages, ${result.layouts.length} layouts, ${result.middlewares.length} middlewares, ${result.plugins.length} plugins`);
|
|
1314
|
-
logger$6.info("Generating type definitions...");
|
|
1388
|
+
if (logging.scan) logger$6.info(`Found ${result.apiRoutes.length} API routes, ${result.pages.length} pages, ${result.layouts.length} layouts, ${result.middlewares.length} middlewares, ${result.plugins.length} plugins`);
|
|
1389
|
+
if (logging.scan) logger$6.info("Generating type definitions...");
|
|
1315
1390
|
await generateTypes(result, {
|
|
1316
1391
|
cwd,
|
|
1317
1392
|
srcDir: config.srcDir,
|
|
@@ -1344,9 +1419,29 @@ async function buildApp(cwd, config) {
|
|
|
1344
1419
|
cache: config.cache,
|
|
1345
1420
|
seoConventions: { srcDir: resolve(cwd, config.srcDir) }
|
|
1346
1421
|
});
|
|
1422
|
+
const requestStartTimes = /* @__PURE__ */ new WeakMap();
|
|
1423
|
+
const isInternalPath = (path) => REQUEST_LOG_INTERNAL_PREFIXES.some((p) => path.startsWith(p));
|
|
1347
1424
|
app.hooks.hook("request:start", (c) => {
|
|
1425
|
+
requestStartTimes.set(c, Date.now());
|
|
1348
1426
|
logger$6.debug(`${c.req.method} ${c.req.path}`);
|
|
1349
1427
|
});
|
|
1428
|
+
app.hooks.hook("request:end", (c, res) => {
|
|
1429
|
+
if (!logging.request) return;
|
|
1430
|
+
const start = requestStartTimes.get(c);
|
|
1431
|
+
const duration = start === void 0 ? 0 : Date.now() - start;
|
|
1432
|
+
const line = `${c.req.method} ${c.req.path} ${res.status} ${duration}ms`;
|
|
1433
|
+
if (res.status >= 500) {
|
|
1434
|
+
logger$6.error(line);
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
if (isInternalPath(c.req.path)) return;
|
|
1438
|
+
if (res.status >= 400 || duration >= REQUEST_LOG_SLOW_THRESHOLD) logger$6.warn(line);
|
|
1439
|
+
else logger$6.info(line);
|
|
1440
|
+
});
|
|
1441
|
+
app.hooks.hook("request:error", (c, err) => {
|
|
1442
|
+
if (!logging.request) return;
|
|
1443
|
+
logger$6.error(`${c.req.method} ${c.req.path} ERROR ${err instanceof Error ? err.message : String(err)}`);
|
|
1444
|
+
});
|
|
1350
1445
|
return {
|
|
1351
1446
|
app,
|
|
1352
1447
|
layouts: result.layouts,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ubean/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "CLI for ubean (ubean-next binary: dev, build, prepare, init, preview, page, env, config, devtools, api/layout/middleware/cron/plugin scaffolds) plus the Vite dev server runner",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ubean-next": "./bin/ubean-next.mjs"
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@ubean/app": "0.5.
|
|
28
|
-
"@ubean/build": "0.5.
|
|
29
|
-
"@ubean/client": "0.5.
|
|
30
|
-
"@ubean/config": "0.5.
|
|
31
|
-
"@ubean/i18n": "0.5.
|
|
32
|
-
"@ubean/islands": "0.5.
|
|
33
|
-
"@ubean/preset": "0.5.
|
|
34
|
-
"@ubean/scan": "0.5.
|
|
35
|
-
"@ubean/shared": "0.5.
|
|
27
|
+
"@ubean/app": "0.5.2",
|
|
28
|
+
"@ubean/build": "0.5.2",
|
|
29
|
+
"@ubean/client": "0.5.2",
|
|
30
|
+
"@ubean/config": "0.5.2",
|
|
31
|
+
"@ubean/i18n": "0.5.2",
|
|
32
|
+
"@ubean/islands": "0.5.2",
|
|
33
|
+
"@ubean/preset": "0.5.2",
|
|
34
|
+
"@ubean/scan": "0.5.2",
|
|
35
|
+
"@ubean/shared": "0.5.2",
|
|
36
36
|
"citty": "^0.2.2",
|
|
37
37
|
"kolorist": "^1.8.0",
|
|
38
38
|
"pathe": "^2.0.3"
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"vite-plus": "0.3.1"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@ubean/content": "0.5.
|
|
49
|
-
"@ubean/devtools": "0.5.
|
|
48
|
+
"@ubean/content": "0.5.2",
|
|
49
|
+
"@ubean/devtools": "0.5.2",
|
|
50
50
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
51
51
|
"vite": "npm:@voidzero-dev/vite-plus-core@0.3.1"
|
|
52
52
|
},
|