@ubean/cli 0.5.2 → 0.6.0-beta.1
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 +314 -1012
- package/dist/index.d.ts +24 -3
- package/dist/index.js +1 -1
- package/dist/{page-Um0fHKa9.js → page-C2Bde9EG.js} +73 -11
- package/package.json +13 -12
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ interface BundleBudgetEntry {
|
|
|
43
43
|
file: string;
|
|
44
44
|
bytes: number;
|
|
45
45
|
gzip: number;
|
|
46
|
+
/** Brotli size, reported for context; no gate reads it yet. */
|
|
47
|
+
brotli?: number;
|
|
46
48
|
isEntry?: boolean;
|
|
47
49
|
}
|
|
48
50
|
interface BundleBaseline {
|
|
@@ -51,18 +53,37 @@ interface BundleBaseline {
|
|
|
51
53
|
entries: BundleBudgetEntry[];
|
|
52
54
|
totalBytes: number;
|
|
53
55
|
totalGzip: number;
|
|
56
|
+
totalBrotli?: number;
|
|
54
57
|
entryGzip: number;
|
|
55
58
|
}
|
|
59
|
+
interface BundleBudgetOptions {
|
|
60
|
+
/** Allowed relative gzip growth for total and entry. Defaults to 0.05. */
|
|
61
|
+
maxIncrease?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Absolute gzip ceilings in bytes. Checked against the current build alone, so they hold
|
|
64
|
+
* even when the committed baseline was recorded from a smaller tree.
|
|
65
|
+
*/
|
|
66
|
+
maxTotalGzip?: number;
|
|
67
|
+
maxEntryGzip?: number;
|
|
68
|
+
maxChunkGzip?: number;
|
|
69
|
+
}
|
|
70
|
+
interface BundleBudgetViolation {
|
|
71
|
+
kind: 'total' | 'entry' | 'chunk' | 'missing';
|
|
72
|
+
/** Measured gzip size in bytes. */
|
|
73
|
+
actual: number;
|
|
74
|
+
/** Ceiling that was exceeded, in bytes. */
|
|
75
|
+
limit: number;
|
|
76
|
+
file?: string;
|
|
77
|
+
}
|
|
56
78
|
export declare function readViteManifest(manifestPath: string): Record<string, ManifestChunk>;
|
|
57
79
|
export declare function summarizeBundle(outDir: string, manifest: Record<string, ManifestChunk>): BundleBaseline;
|
|
58
|
-
export declare function compareBundleBaseline(current: Pick<BundleBaseline, 'totalGzip' | 'entryGzip'
|
|
59
|
-
maxIncrease?: number;
|
|
60
|
-
}): {
|
|
80
|
+
export declare function compareBundleBaseline(current: Pick<BundleBaseline, 'totalGzip' | 'entryGzip'> & Partial<Pick<BundleBaseline, 'entries'>>, committed: Pick<BundleBaseline, 'totalGzip' | 'entryGzip'> & Partial<Pick<BundleBaseline, 'entries'>>, options?: BundleBudgetOptions): {
|
|
61
81
|
ok: boolean;
|
|
62
82
|
maxIncrease: number;
|
|
63
83
|
totalRatio: number;
|
|
64
84
|
entryRatio: number;
|
|
65
85
|
messages: string[];
|
|
86
|
+
violations: BundleBudgetViolation[];
|
|
66
87
|
};
|
|
67
88
|
export declare function writeBundleBaseline(filePath: string, baseline: BundleBaseline): void;
|
|
68
89
|
export declare function findClientManifest(cwd: string): {
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as getScaffoldManifest, t as SCAFFOLD_CONTRACT_VERSION } from "./scaffold-manifest-TrH6OCX8.js";
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as readViteManifest, C as toCamelCase, D as compareBundleBaseline, E as createFsOps, M as writeBundleBaseline, O as findClientManifest, S as renderTemplate, T as toPascalCase, _ as renderCronTemplate, a as scaffold, b as renderPageTemplate, d as CRON_TEMPLATE, f as LAYOUT_TEMPLATE, g as renderApiTemplate, h as PLUGIN_TEMPLATE, i as recoverScaffold, j as summarizeBundle, m as PAGE_TEMPLATE, n as listScaffoldableFiles, p as MIDDLEWARE_TEMPLATE, t as deleteScaffold, u as API_TEMPLATE, v as renderLayoutTemplate, w as toKebabCase, x as renderPluginTemplate, y as renderMiddlewareTemplate } from "./page-C2Bde9EG.js";
|
|
3
3
|
export { API_TEMPLATE, CRON_TEMPLATE, LAYOUT_TEMPLATE, MIDDLEWARE_TEMPLATE, PAGE_TEMPLATE, PLUGIN_TEMPLATE, SCAFFOLD_CONTRACT_VERSION, compareBundleBaseline, createFsOps, deleteScaffold, findClientManifest, getScaffoldManifest, listScaffoldableFiles, readViteManifest, recoverScaffold, renderApiTemplate, renderCronTemplate, renderLayoutTemplate, renderMiddlewareTemplate, renderPageTemplate, renderPluginTemplate, renderTemplate, scaffold, summarizeBundle, toCamelCase, toKebabCase, toPascalCase, writeBundleBaseline };
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, promises, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { gzipSync } from "node:zlib";
|
|
2
|
+
import { brotliCompressSync, gzipSync } from "node:zlib";
|
|
3
3
|
import { dirname, join, resolve } from "pathe";
|
|
4
4
|
import { dirname as dirname$1, extname, join as join$1, relative as relative$1, resolve as resolve$1 } from "node:path";
|
|
5
5
|
import { getLogger } from "@ubean/shared/logger";
|
|
6
6
|
//#region src/analyze-lib.ts
|
|
7
|
+
const CHUNK_VIOLATION_DISPLAY_LIMIT = 5;
|
|
8
|
+
/**
|
|
9
|
+
* 内容哈希规范化:`assets/chunks/Foo-AbC12dEf.js` → `assets/chunks/Foo.<hash>.js`。
|
|
10
|
+
*
|
|
11
|
+
* 用于按**名字**对照两次构建的 chunk 名单 —— 名称稳定、哈希不稳定,比名字才有意义。
|
|
12
|
+
*/
|
|
13
|
+
function normalizeChunkFileName(file) {
|
|
14
|
+
return file.replace(/-[A-Za-z0-9_-]{8}\./g, ".<hash>.");
|
|
15
|
+
}
|
|
16
|
+
function formatKilobytes(bytes) {
|
|
17
|
+
return `${(bytes / 1024).toFixed(1)} kB`;
|
|
18
|
+
}
|
|
7
19
|
function readViteManifest(manifestPath) {
|
|
8
20
|
return JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
9
21
|
}
|
|
@@ -13,18 +25,19 @@ function summarizeBundle(outDir, manifest) {
|
|
|
13
25
|
if (!chunk.file || chunk.file.endsWith(".css")) continue;
|
|
14
26
|
const abs = join(outDir, chunk.file);
|
|
15
27
|
if (!existsSync(abs)) continue;
|
|
16
|
-
const
|
|
17
|
-
const gzip = gzipSync(readFileSync(abs)).length;
|
|
28
|
+
const source = readFileSync(abs);
|
|
18
29
|
entries.push({
|
|
19
30
|
file: chunk.file,
|
|
20
|
-
bytes,
|
|
21
|
-
gzip,
|
|
31
|
+
bytes: statSync(abs).size,
|
|
32
|
+
gzip: gzipSync(source).length,
|
|
33
|
+
brotli: brotliCompressSync(source).length,
|
|
22
34
|
isEntry: chunk.isEntry
|
|
23
35
|
});
|
|
24
36
|
}
|
|
25
37
|
entries.sort((a, b) => b.gzip - a.gzip);
|
|
26
38
|
const totalBytes = entries.reduce((sum, e) => sum + e.bytes, 0);
|
|
27
39
|
const totalGzip = entries.reduce((sum, e) => sum + e.gzip, 0);
|
|
40
|
+
const totalBrotli = entries.reduce((sum, e) => sum + (e.brotli ?? 0), 0);
|
|
28
41
|
const entryGzip = entries.filter((e) => e.isEntry).reduce((sum, e) => sum + e.gzip, 0);
|
|
29
42
|
return {
|
|
30
43
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -32,24 +45,73 @@ function summarizeBundle(outDir, manifest) {
|
|
|
32
45
|
entries,
|
|
33
46
|
totalBytes,
|
|
34
47
|
totalGzip,
|
|
48
|
+
totalBrotli,
|
|
35
49
|
entryGzip
|
|
36
50
|
};
|
|
37
51
|
}
|
|
38
52
|
function compareBundleBaseline(current, committed, options = {}) {
|
|
39
53
|
const maxIncrease = options.maxIncrease ?? .05;
|
|
40
|
-
const totalRatio =
|
|
41
|
-
const entryRatio =
|
|
54
|
+
const totalRatio = growthRatio(current.totalGzip, committed.totalGzip);
|
|
55
|
+
const entryRatio = growthRatio(current.entryGzip, committed.entryGzip);
|
|
42
56
|
const messages = [];
|
|
43
|
-
|
|
44
|
-
if (
|
|
57
|
+
const violations = [];
|
|
58
|
+
if (totalRatio > maxIncrease) messages.push(`total gzip ${formatKilobytes(current.totalGzip)} exceeds baseline ${formatKilobytes(committed.totalGzip)} by ${(totalRatio * 100).toFixed(1)}% (max ${(maxIncrease * 100).toFixed(0)}%)`);
|
|
59
|
+
if (entryRatio > maxIncrease) messages.push(`entry gzip ${formatKilobytes(current.entryGzip)} exceeds baseline ${formatKilobytes(committed.entryGzip)} by ${(entryRatio * 100).toFixed(1)}% (max ${(maxIncrease * 100).toFixed(0)}%)`);
|
|
60
|
+
if (options.maxTotalGzip !== void 0 && current.totalGzip > options.maxTotalGzip) {
|
|
61
|
+
violations.push({
|
|
62
|
+
kind: "total",
|
|
63
|
+
actual: current.totalGzip,
|
|
64
|
+
limit: options.maxTotalGzip
|
|
65
|
+
});
|
|
66
|
+
messages.push(`total gzip ${formatKilobytes(current.totalGzip)} exceeds the absolute budget ${formatKilobytes(options.maxTotalGzip)}`);
|
|
67
|
+
}
|
|
68
|
+
if (options.maxEntryGzip !== void 0 && current.entryGzip > options.maxEntryGzip) {
|
|
69
|
+
violations.push({
|
|
70
|
+
kind: "entry",
|
|
71
|
+
actual: current.entryGzip,
|
|
72
|
+
limit: options.maxEntryGzip
|
|
73
|
+
});
|
|
74
|
+
messages.push(`entry gzip ${formatKilobytes(current.entryGzip)} exceeds the absolute budget ${formatKilobytes(options.maxEntryGzip)}`);
|
|
75
|
+
}
|
|
76
|
+
const oversizedChunks = options.maxChunkGzip === void 0 ? [] : (current.entries ?? []).filter((entry) => entry.gzip > options.maxChunkGzip);
|
|
77
|
+
for (const entry of oversizedChunks) violations.push({
|
|
78
|
+
kind: "chunk",
|
|
79
|
+
actual: entry.gzip,
|
|
80
|
+
limit: options.maxChunkGzip,
|
|
81
|
+
file: entry.file
|
|
82
|
+
});
|
|
83
|
+
if (oversizedChunks.length > 0) {
|
|
84
|
+
const shown = oversizedChunks.slice(0, CHUNK_VIOLATION_DISPLAY_LIMIT).map((entry) => `${entry.file} ${formatKilobytes(entry.gzip)}`).join(", ");
|
|
85
|
+
const rest = oversizedChunks.length - CHUNK_VIOLATION_DISPLAY_LIMIT;
|
|
86
|
+
messages.push(`${oversizedChunks.length} chunk(s) exceed the absolute budget ${formatKilobytes(options.maxChunkGzip)}: ${shown}${rest > 0 ? ` (+${rest} more)` : ""}`);
|
|
87
|
+
}
|
|
88
|
+
const currentNames = new Set((current.entries ?? []).map((entry) => normalizeChunkFileName(entry.file)));
|
|
89
|
+
const missing = committed.entries === void 0 ? [] : committed.entries.map((entry) => normalizeChunkFileName(entry.file)).filter((name) => !currentNames.has(name));
|
|
90
|
+
const uniqueMissing = [...new Set(missing)];
|
|
91
|
+
for (const file of uniqueMissing) violations.push({
|
|
92
|
+
kind: "missing",
|
|
93
|
+
actual: 0,
|
|
94
|
+
limit: -1,
|
|
95
|
+
file
|
|
96
|
+
});
|
|
97
|
+
if (uniqueMissing.length > 0) {
|
|
98
|
+
const shown = uniqueMissing.slice(0, CHUNK_VIOLATION_DISPLAY_LIMIT).join(", ");
|
|
99
|
+
const rest = uniqueMissing.length - CHUNK_VIOLATION_DISPLAY_LIMIT;
|
|
100
|
+
messages.push(`${uniqueMissing.length} chunk(s) present in the baseline are missing from this build: ${shown}${rest > 0 ? ` (+${rest} more)` : ""}`);
|
|
101
|
+
}
|
|
45
102
|
return {
|
|
46
103
|
ok: messages.length === 0,
|
|
47
104
|
maxIncrease,
|
|
48
105
|
totalRatio,
|
|
49
106
|
entryRatio,
|
|
50
|
-
messages
|
|
107
|
+
messages,
|
|
108
|
+
violations
|
|
51
109
|
};
|
|
52
110
|
}
|
|
111
|
+
function growthRatio(current, committed) {
|
|
112
|
+
if (committed === 0) return current === 0 ? 0 : Infinity;
|
|
113
|
+
return current / committed - 1;
|
|
114
|
+
}
|
|
53
115
|
function writeBundleBaseline(filePath, baseline) {
|
|
54
116
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
55
117
|
writeFileSync(filePath, `${JSON.stringify(baseline, null, 2)}\n`, "utf8");
|
|
@@ -1733,4 +1795,4 @@ const pageCommand = {
|
|
|
1733
1795
|
}
|
|
1734
1796
|
};
|
|
1735
1797
|
//#endregion
|
|
1736
|
-
export {
|
|
1798
|
+
export { readViteManifest as A, toCamelCase as C, compareBundleBaseline as D, createFsOps as E, writeBundleBaseline as M, findClientManifest as O, renderTemplate as S, toPascalCase as T, renderCronTemplate as _, scaffold as a, renderPageTemplate as b, scaffoldStarterTemplate as c, CRON_TEMPLATE as d, LAYOUT_TEMPLATE as f, renderApiTemplate as g, PLUGIN_TEMPLATE as h, recoverScaffold as i, summarizeBundle as j, formatKilobytes as k, scaffoldUnifyTemplate as l, PAGE_TEMPLATE as m, listScaffoldableFiles as n, scaffoldBlogTemplate as o, MIDDLEWARE_TEMPLATE as p, pageCommand as r, scaffoldMinimalTemplate as s, deleteScaffold as t, API_TEMPLATE as u, renderLayoutTemplate as v, toKebabCase as w, renderPluginTemplate as x, renderMiddlewareTemplate as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ubean/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0-beta.1",
|
|
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.
|
|
28
|
-
"@ubean/build": "0.
|
|
29
|
-
"@ubean/client": "0.
|
|
30
|
-
"@ubean/config": "0.
|
|
31
|
-
"@ubean/i18n": "0.
|
|
32
|
-
"@ubean/islands": "0.
|
|
33
|
-
"@ubean/preset": "0.
|
|
34
|
-
"@ubean/scan": "0.
|
|
35
|
-
"@ubean/shared": "0.
|
|
27
|
+
"@ubean/app": "0.6.0-beta.1",
|
|
28
|
+
"@ubean/build": "0.6.0-beta.1",
|
|
29
|
+
"@ubean/client": "0.6.0-beta.1",
|
|
30
|
+
"@ubean/config": "0.6.0-beta.1",
|
|
31
|
+
"@ubean/i18n": "0.6.0-beta.1",
|
|
32
|
+
"@ubean/islands": "0.6.0-beta.1",
|
|
33
|
+
"@ubean/preset": "0.6.0-beta.1",
|
|
34
|
+
"@ubean/scan": "0.6.0-beta.1",
|
|
35
|
+
"@ubean/shared": "0.6.0-beta.1",
|
|
36
36
|
"citty": "^0.2.2",
|
|
37
37
|
"kolorist": "^1.8.0",
|
|
38
38
|
"pathe": "^2.0.3"
|
|
@@ -40,13 +40,14 @@
|
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^26.5.0",
|
|
42
42
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
43
|
+
"env-runner": "0.2.3",
|
|
43
44
|
"typescript": "npm:typescript-native-bridge@latest",
|
|
44
45
|
"vite": "npm:@voidzero-dev/vite-plus-core@0.3.1",
|
|
45
46
|
"vite-plus": "0.3.1"
|
|
46
47
|
},
|
|
47
48
|
"peerDependencies": {
|
|
48
|
-
"@ubean/content": "0.
|
|
49
|
-
"@ubean/devtools": "0.
|
|
49
|
+
"@ubean/content": "0.6.0-beta.1",
|
|
50
|
+
"@ubean/devtools": "0.6.0-beta.1",
|
|
50
51
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
51
52
|
"vite": "npm:@voidzero-dev/vite-plus-core@0.3.1"
|
|
52
53
|
},
|