@ubean/cli 0.5.0 → 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 +130 -27
- 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";
|
|
@@ -22,7 +22,7 @@ import { ubeanPlugin } from "@ubean/build/vite";
|
|
|
22
22
|
import { VUE_PLUGIN_INCLUDE, ubeanVite } from "@ubean/build/vue";
|
|
23
23
|
import { getVueLocaleParam, toVueRouterLocalePath } from "@ubean/i18n";
|
|
24
24
|
import { ubeanIslandsPlugin } from "@ubean/islands/vite";
|
|
25
|
-
import { findAvailablePort, findUserViteConfig, waitForPort } from "@ubean/shared/node";
|
|
25
|
+
import { findAvailablePort, findUserViteConfig, getLanAddresses, isLoopbackHost, isWildcardHost, waitForPort } from "@ubean/shared/node";
|
|
26
26
|
import { execSync, spawn } from "node:child_process";
|
|
27
27
|
//#region src/config.ts
|
|
28
28
|
const logger$11 = getLogger("cli");
|
|
@@ -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 },
|
|
@@ -867,7 +894,9 @@ async function createViteDevServer(options) {
|
|
|
867
894
|
app.resetInit();
|
|
868
895
|
}
|
|
869
896
|
enhanceAppWithVite(currentApp, currentLayouts);
|
|
870
|
-
const
|
|
897
|
+
const displayHost = isWildcardHost(host) ? "localhost" : host;
|
|
898
|
+
const getNetworkUrls = () => isLoopbackHost(host) ? [] : getLanAddresses().map((address) => `http://${address}:${actualPort}`);
|
|
899
|
+
const getUrl = () => `http://${displayHost}:${actualPort}`;
|
|
871
900
|
return {
|
|
872
901
|
get port() {
|
|
873
902
|
return actualPort;
|
|
@@ -902,7 +931,8 @@ async function createViteDevServer(options) {
|
|
|
902
931
|
options.onListen?.({
|
|
903
932
|
port: actualPort,
|
|
904
933
|
host,
|
|
905
|
-
url: getUrl()
|
|
934
|
+
url: getUrl(),
|
|
935
|
+
networkUrls: getNetworkUrls()
|
|
906
936
|
});
|
|
907
937
|
},
|
|
908
938
|
async stop() {
|
|
@@ -969,12 +999,13 @@ var ViteNodeDevRunner = class {
|
|
|
969
999
|
app: this.currentApp,
|
|
970
1000
|
layouts: this.currentLayouts,
|
|
971
1001
|
devtools: this.options.devtools,
|
|
972
|
-
onListen: ({ port, host, url }) => {
|
|
1002
|
+
onListen: ({ port, host, url, networkUrls }) => {
|
|
973
1003
|
this._port = port;
|
|
974
1004
|
this.options.onListen?.({
|
|
975
1005
|
port,
|
|
976
1006
|
host,
|
|
977
|
-
url
|
|
1007
|
+
url,
|
|
1008
|
+
networkUrls
|
|
978
1009
|
});
|
|
979
1010
|
}
|
|
980
1011
|
});
|
|
@@ -1096,7 +1127,13 @@ function formatDiagnostics(diagnostics) {
|
|
|
1096
1127
|
warnings
|
|
1097
1128
|
};
|
|
1098
1129
|
}
|
|
1099
|
-
|
|
1130
|
+
/**
|
|
1131
|
+
* 输出能力诊断结果。
|
|
1132
|
+
*
|
|
1133
|
+
* `'auto'` 语义(默认):健康时静默 —— "All capability checks passed" 仅在
|
|
1134
|
+
* `verbose` 时输出;告警/错误属于 warn/error 级别,始终输出。
|
|
1135
|
+
*/
|
|
1136
|
+
function logDiagnostics(diagnostics, options = {}) {
|
|
1100
1137
|
const { errors, warnings } = formatDiagnostics(diagnostics);
|
|
1101
1138
|
if (errors.length > 0) {
|
|
1102
1139
|
logger$7.error(`Capability errors (${errors.length}):`);
|
|
@@ -1106,7 +1143,7 @@ function logDiagnostics(diagnostics) {
|
|
|
1106
1143
|
logger$7.warn(`Capability warnings (${warnings.length}):`);
|
|
1107
1144
|
for (const w of warnings) logger$7.warn(w);
|
|
1108
1145
|
}
|
|
1109
|
-
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");
|
|
1110
1147
|
}
|
|
1111
1148
|
//#endregion
|
|
1112
1149
|
//#region src/dev.ts
|
|
@@ -1160,6 +1197,14 @@ const devCommand = {
|
|
|
1160
1197
|
description: "Exit if the port is already in use, instead of auto-incrementing",
|
|
1161
1198
|
default: false
|
|
1162
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
|
+
},
|
|
1163
1208
|
open: {
|
|
1164
1209
|
type: "boolean",
|
|
1165
1210
|
description: "Open browser on startup"
|
|
@@ -1172,27 +1217,47 @@ const devCommand = {
|
|
|
1172
1217
|
},
|
|
1173
1218
|
async run({ args }) {
|
|
1174
1219
|
const cwd = resolve(args.cwd || process.cwd());
|
|
1175
|
-
logger$6.info("Starting ubean dev server...");
|
|
1176
1220
|
registerBuiltinPresets();
|
|
1177
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...");
|
|
1178
1241
|
const port = Number(args.port) || config.dev.port;
|
|
1179
1242
|
const host = args.host || config.dev.host;
|
|
1180
|
-
logger$6.info(`Root directory: ${config.rootDir}`);
|
|
1181
|
-
logger$6.info(`Source directory: ${config.srcDir}`);
|
|
1182
1243
|
const preset = resolvePresetByName(config.build.preset);
|
|
1183
|
-
|
|
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
|
+
}
|
|
1184
1249
|
const capabilities = createCapabilitySet(preset.capabilities || {});
|
|
1185
|
-
logger$6.info("Running capability diagnostics...");
|
|
1250
|
+
if (logging.scan) logger$6.info("Running capability diagnostics...");
|
|
1186
1251
|
const diagnostics = diagnoseCapabilities(preset.name, preset.capabilities || {}, NODE_REQUIREMENTS);
|
|
1187
|
-
logDiagnostics(diagnostics);
|
|
1252
|
+
logDiagnostics(diagnostics, { verbose: logging.diagnostics });
|
|
1188
1253
|
if (!diagnostics.valid) logger$6.warn("Some capability requirements are not met. Dev server may not function correctly.");
|
|
1189
|
-
let { app: currentApp, layouts: currentLayouts, scanResult: currentScanResult } = await buildApp(cwd, config);
|
|
1254
|
+
let { app: currentApp, layouts: currentLayouts, scanResult: currentScanResult } = await buildApp(cwd, config, logging);
|
|
1190
1255
|
let rescanInProgress = false;
|
|
1191
1256
|
async function rescan() {
|
|
1192
1257
|
if (rescanInProgress) return;
|
|
1193
1258
|
rescanInProgress = true;
|
|
1194
1259
|
try {
|
|
1195
|
-
const { app: newApp, layouts: newLayouts, scanResult } = await buildApp(cwd, config);
|
|
1260
|
+
const { app: newApp, layouts: newLayouts, scanResult } = await buildApp(cwd, config, logging);
|
|
1196
1261
|
currentApp = newApp;
|
|
1197
1262
|
currentLayouts = newLayouts;
|
|
1198
1263
|
currentScanResult = scanResult;
|
|
@@ -1205,6 +1270,8 @@ const devCommand = {
|
|
|
1205
1270
|
rescanInProgress = false;
|
|
1206
1271
|
}
|
|
1207
1272
|
}
|
|
1273
|
+
let reloadStartedAt = Date.now();
|
|
1274
|
+
let lastChangedFile = null;
|
|
1208
1275
|
const runner = await createDevRunner({
|
|
1209
1276
|
cwd,
|
|
1210
1277
|
srcDir: config.srcDir,
|
|
@@ -1232,25 +1299,36 @@ const devCommand = {
|
|
|
1232
1299
|
getCustomTabs: () => getCustomTabs(),
|
|
1233
1300
|
triggerRescan: () => rescan()
|
|
1234
1301
|
},
|
|
1235
|
-
onListen({ url }) {
|
|
1302
|
+
onListen({ url, networkUrls }) {
|
|
1236
1303
|
const label = (text) => dim(text);
|
|
1237
|
-
const
|
|
1304
|
+
const modeSuffix = dim(` (${config.mode} · ${preset.name})`);
|
|
1305
|
+
const lines = [
|
|
1306
|
+
`${green(bold(`🚀 ubean dev server ready in ${Date.now() - startedAt}ms`))}${modeSuffix}\n`,
|
|
1307
|
+
` → ${label("Local:")} ${cyan(url)}`,
|
|
1308
|
+
...(networkUrls || []).map((n) => ` → ${label("Network:")} ${cyan(n)}`)
|
|
1309
|
+
];
|
|
1238
1310
|
const hasBackend = config.mode !== "ssg" && config.mode !== "spa";
|
|
1239
1311
|
if (hasBackend) lines.push(` → ${label("Scalar UI:")} ${cyan(`${url}/_scalar`)}`, ` → ${label("OpenAPI:")} ${cyan(`${url}/_openapi.json`)}`);
|
|
1240
1312
|
if (config.devtools.enabled) lines.push(` → ${label("DevTools:")} ${cyan(`${url}${config.devtools.route}`)}`);
|
|
1241
1313
|
lines.push(` → ${dim("Press Ctrl+C to stop")}`);
|
|
1242
1314
|
logger$6.info(lines.join("\n"));
|
|
1243
1315
|
if (hasBackend) generateOpenApiTypesFromServer(url, { outDir: resolve(cwd, ".ubean") }).then((filePath) => {
|
|
1244
|
-
if (filePath) logger$6.info(`OpenAPI types generated: ${filePath}`);
|
|
1316
|
+
if (filePath && logging.scan) logger$6.info(`OpenAPI types generated: ${filePath}`);
|
|
1245
1317
|
}).catch((err) => {
|
|
1246
1318
|
logger$6.warn(`Failed to generate OpenAPI types: ${err instanceof Error ? err.message : String(err)}`);
|
|
1247
1319
|
});
|
|
1248
1320
|
},
|
|
1249
1321
|
onBeforeReload() {
|
|
1250
|
-
|
|
1322
|
+
reloadStartedAt = Date.now();
|
|
1323
|
+
if (logging.lifecycle) logger$6.info("Reloading...");
|
|
1251
1324
|
},
|
|
1252
1325
|
onAfterReload() {
|
|
1253
|
-
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;
|
|
1254
1332
|
}
|
|
1255
1333
|
});
|
|
1256
1334
|
try {
|
|
@@ -1279,13 +1357,14 @@ const devCommand = {
|
|
|
1279
1357
|
async onChange(events) {
|
|
1280
1358
|
const relevantEvents = events.filter((e) => /\.(ts|js|vue|mjs|cjs|json)$/.test(e.relativePath) && !e.relativePath.includes(".bak"));
|
|
1281
1359
|
if (relevantEvents.length === 0) return;
|
|
1282
|
-
|
|
1360
|
+
lastChangedFile = relevantEvents[0].relativePath;
|
|
1361
|
+
if (logging.lifecycle) logger$6.info(`File change detected: ${lastChangedFile}`);
|
|
1283
1362
|
await rescan();
|
|
1284
1363
|
}
|
|
1285
1364
|
});
|
|
1286
1365
|
watcher.start();
|
|
1287
1366
|
const cleanup = async () => {
|
|
1288
|
-
logger$6.info("\nShutting down...");
|
|
1367
|
+
if (logging.lifecycle) logger$6.info("\nShutting down...");
|
|
1289
1368
|
watcher.stop();
|
|
1290
1369
|
await runner.stop();
|
|
1291
1370
|
process.exit(0);
|
|
@@ -1294,16 +1373,20 @@ const devCommand = {
|
|
|
1294
1373
|
process.on("SIGTERM", cleanup);
|
|
1295
1374
|
}
|
|
1296
1375
|
};
|
|
1297
|
-
|
|
1298
|
-
|
|
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...");
|
|
1299
1382
|
const result = await scanProject({
|
|
1300
1383
|
cwd,
|
|
1301
1384
|
srcDir: config.srcDir,
|
|
1302
1385
|
dirs: config.dir,
|
|
1303
1386
|
ignore: config.scanOptions?.ignore
|
|
1304
1387
|
});
|
|
1305
|
-
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`);
|
|
1306
|
-
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...");
|
|
1307
1390
|
await generateTypes(result, {
|
|
1308
1391
|
cwd,
|
|
1309
1392
|
srcDir: config.srcDir,
|
|
@@ -1336,9 +1419,29 @@ async function buildApp(cwd, config) {
|
|
|
1336
1419
|
cache: config.cache,
|
|
1337
1420
|
seoConventions: { srcDir: resolve(cwd, config.srcDir) }
|
|
1338
1421
|
});
|
|
1422
|
+
const requestStartTimes = /* @__PURE__ */ new WeakMap();
|
|
1423
|
+
const isInternalPath = (path) => REQUEST_LOG_INTERNAL_PREFIXES.some((p) => path.startsWith(p));
|
|
1339
1424
|
app.hooks.hook("request:start", (c) => {
|
|
1425
|
+
requestStartTimes.set(c, Date.now());
|
|
1340
1426
|
logger$6.debug(`${c.req.method} ${c.req.path}`);
|
|
1341
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
|
+
});
|
|
1342
1445
|
return {
|
|
1343
1446
|
app,
|
|
1344
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
|
},
|