charbi-font 0.2.0 → 0.3.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 CHANGED
@@ -69,10 +69,12 @@ export default defineConfig({
69
69
  });
70
70
  ```
71
71
 
72
- ### 2. 执行构建
72
+ ### 2. 执行构建和上传
73
73
 
74
74
  ```bash
75
- charbi-font
75
+ charbi # 构建 + 上传
76
+ charbi build # 仅构建(生成字体子集)
77
+ charbi upload # 仅上传(上传已构建的字体)
76
78
  ```
77
79
 
78
80
  ### 3. 引入字体
@@ -102,11 +104,16 @@ import "@/styles/fonts";
102
104
  ## 输出结构
103
105
 
104
106
  ```
105
- src/styles/
106
- ├── font-assets/
107
- │ ├── alibaba-pu-hui-ti.scss # 阿里普惠体(多字重)
108
- └── fonts.scss # 汇总引入文件
109
- └── fonts.scss # 入口文件
107
+ 项目目录/
108
+ ├── src/styles/
109
+ │ ├── font-assets/
110
+ │ ├── alibaba-pu-hui-ti.scss # 阿里普惠体(多字重)
111
+ │ │ └── fonts.scss # 汇总引入文件
112
+ │ └── fonts.scss # 入口文件
113
+
114
+ └── node_modules/charbi-font/.cache/fonts/ # 字体缓存
115
+ ├── subsets/ # 字体子集(构建产物)
116
+ └── AlibabaPuHuiTi-400.ttf # 原始字体缓存
110
117
  ```
111
118
 
112
119
  ## 配置说明
@@ -139,15 +146,15 @@ src/styles/
139
146
  | `format` | `'woff' \| 'woff2' \| 'ttf'` | `'woff'` | 输出格式 |
140
147
  | `styleFormat` | `'scss' \| 'css'` | `'scss'` | 样式文件格式 |
141
148
 
142
- ### `cacheDir`
149
+ ### `build.cacheDir`
143
150
 
144
151
  自定义缓存目录(默认:`node_modules/charbi-font/.cache/fonts`):
145
152
 
146
153
  ```typescript
147
154
  export default defineConfig({
148
- cacheDir: ".cache/fonts", // 相对于项目根目录
149
- // 或绝对路径
150
- cacheDir: "/path/to/cache/fonts"
155
+ build: {
156
+ cacheDir: ".cache/fonts" // 相对于项目根目录
157
+ }
151
158
  });
152
159
  ```
153
160
 
@@ -163,10 +170,33 @@ export default defineConfig({
163
170
  }
164
171
  ```
165
172
 
173
+ ## Vite 插件
174
+
175
+ 配合 Vite 使用时,可引入 `virtual:charbi-font` 获取构建版本号:
176
+
177
+ ```typescript
178
+ import { FONT_BUILD_VERSION } from "virtual:charbi-font";
179
+
180
+ console.log(FONT_BUILD_VERSION); // "1.0.0"
181
+ ```
182
+
183
+ 在 `vite.config.ts` 中启用插件:
184
+
185
+ ```typescript
186
+ import charbiFont from "charbi-font/vite";
187
+
188
+ export default defineConfig({
189
+ plugins: [charbiFont()]
190
+ });
191
+ ```
192
+
166
193
  ## 使用命令
167
194
 
168
195
  ```bash
169
- charbi-font # 执行构建
196
+ charbi # 构建 + 上传到 CDN
197
+ charbi build # 仅构建(生成字体子集到缓存目录)
198
+ charbi upload # 仅上传(上传缓存目录中的字体到 CDN)
199
+ charbi --mode production # 使用 production 模式
170
200
  ```
171
201
 
172
202
  ## License
package/dist/cli.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const e=require(`./chunk-Bmb41Sf3.cjs`),t=require(`./schema-ClqJnGnv.cjs`),n=require(`./config/loader.cjs`);let r=require(`node:fs`);r=e.t(r);let i=require(`node:path`);i=e.t(i);let a=require(`node:process`);a=e.t(a);let o=require(`node:url`),s=require(`cac`),c=require(`node:http`);c=e.t(c);let l=require(`node:https`);l=e.t(l);let u=require(`consola`);u=e.t(u);let d=require(`fast-glob`);d=e.t(d);let f=require(`fontmin`);f=e.t(f);let p=require(`p-limit`);p=e.t(p);let m=require(`enquirer`);m=e.t(m);async function h(e,t){return new Promise((n,i)=>{let a=e.startsWith(`https`)?l.default:c.default,o=r.default.createWriteStream(t);a.get(e,e=>{if(e.statusCode!==200){o.close(),r.default.unlink(t,()=>{}),i(Error(`下载失败: ${e.statusCode}`));return}e.pipe(o),o.on(`finish`,()=>{if(o.close(),r.default.statSync(t).size===0){r.default.unlink(t,()=>{}),i(Error(`下载的文件为空`));return}n()})}).on(`error`,e=>{o.close(),r.default.unlink(t,()=>{}),i(e)})})}async function g(e,t,n=!1){u.default.info(`下载字体文件...`),u.default.info(` 缓存目录: ${e}`),r.default.mkdirSync(e,{recursive:!0});let a=new Map;for(let o of t){let t=`${o.family}-${o.weight}.ttf`,s=i.default.join(e,t);if(n&&r.default.existsSync(s)&&r.default.unlinkSync(s),r.default.existsSync(s)){let e=r.default.statSync(s);if(e.size===0)r.default.unlinkSync(s);else{let t=(e.size/1024).toFixed(2);u.default.info(` ✓ ${o.family} ${o.name} (缓存, ${t} KB)`),a.set(`${o.family}-${o.weight}`,s);continue}}u.default.info(` ↓ ${o.family} ${o.name}...`);try{await h(o.url,s);let e=(r.default.statSync(s).size/1024).toFixed(2);u.default.success(` 完成 ${t} (${e} KB)`),a.set(`${o.family}-${o.weight}`,s)}catch(e){u.default.error(` 失败 ${o.family} ${o.name} 下载失败:`,e.message),r.default.existsSync(s)&&r.default.unlinkSync(s)}}return a}function _(e){return e.replace(/\s+/g,``)}function v(e,t,n,r){let{cos:i}=e;if(i.cdnUrl){if(!i.basePath)throw Error(`COS 配置缺少 basePath,请在 fonts.config.ts 中设置 cos.basePath`);let e=`${_(t.family)}-${t.weight}.${r}`,a=i.basePath.replace(`{version}`,n),o=a.startsWith(`/`)?a.slice(1):a;return`${i.cdnUrl}/${o}/${e}`.replace(/\/+/g,`/`)}return`./fonts/${_(t.family)}-${t.weight}.${r}`}function y(e,t,n,r,i=`normal`){return`@font-face {
1
+ const e=require(`./chunk-Bmb41Sf3.cjs`),t=require(`./schema-ClqJnGnv.cjs`),n=require(`./config/loader.cjs`);let r=require(`node:fs`);r=e.t(r);let i=require(`node:path`);i=e.t(i);let a=require(`node:process`);a=e.t(a);let o=require(`cac`),s=require(`node:url`),c=require(`node:http`);c=e.t(c);let l=require(`node:https`);l=e.t(l);let u=require(`consola`);u=e.t(u);let d=require(`fast-glob`);d=e.t(d);let f=require(`fontmin`);f=e.t(f);let p=require(`p-limit`);p=e.t(p);let m=require(`enquirer`);m=e.t(m);async function h(e,t){return new Promise((n,i)=>{let a=e.startsWith(`https`)?l.default:c.default,o=r.default.createWriteStream(t);a.get(e,e=>{if(e.statusCode!==200){o.close(),r.default.unlink(t,()=>{}),i(Error(`下载失败: ${e.statusCode}`));return}e.pipe(o),o.on(`finish`,()=>{if(o.close(),r.default.statSync(t).size===0){r.default.unlink(t,()=>{}),i(Error(`下载的文件为空`));return}n()})}).on(`error`,e=>{o.close(),r.default.unlink(t,()=>{}),i(e)})})}async function g(e,t,n=!1){u.default.info(`下载字体文件...`),u.default.info(` 缓存目录: ${e}`),r.default.mkdirSync(e,{recursive:!0});let a=new Map;for(let o of t){let t=`${o.family}-${o.weight}.ttf`,s=i.default.join(e,t);if(n&&r.default.existsSync(s)&&r.default.unlinkSync(s),r.default.existsSync(s)){let e=r.default.statSync(s);if(e.size===0)r.default.unlinkSync(s);else{let t=(e.size/1024).toFixed(2);u.default.info(` ✓ ${o.family} ${o.name} (缓存, ${t} KB)`),a.set(`${o.family}-${o.weight}`,s);continue}}u.default.info(` ↓ ${o.family} ${o.name}...`);try{await h(o.url,s);let e=(r.default.statSync(s).size/1024).toFixed(2);u.default.success(` 完成 ${t} (${e} KB)`),a.set(`${o.family}-${o.weight}`,s)}catch(e){u.default.error(` 失败 ${o.family} ${o.name} 下载失败:`,e.message),r.default.existsSync(s)&&r.default.unlinkSync(s)}}return a}function _(e){return e.replace(/\s+/g,``)}function v(e,t,n,r){let{cos:i}=e;if(i.cdnUrl){if(!i.basePath)throw Error(`COS 配置缺少 basePath,请在 fonts.config.ts 中设置 cos.basePath`);let e=`${_(t.family)}-${t.weight}.${r}`,a=i.basePath.replace(`{version}`,n),o=a.startsWith(`/`)?a.slice(1):a;return`${i.cdnUrl}/${o}/${e}`.replace(/\/+/g,`/`)}return`./fonts/${_(t.family)}-${t.weight}.${r}`}function y(e,t,n,r,i=`normal`){return`@font-face {
2
2
  font-family: '${e}';
3
3
  src: url('${n}') format('${r}');
4
4
  font-display: swap;
@@ -29,4 +29,4 @@ const e=require(`./chunk-Bmb41Sf3.cjs`),t=require(`./schema-ClqJnGnv.cjs`),n=req
29
29
 
30
30
  ${f.map(e=>x(`${t.t}/${e.replace(/\.(scss|css)$/,``)}`,n.output.styleFormat)).join(`
31
31
  `)}
32
- `,_=i.default.join(l,h);r.default.writeFileSync(_,g);let S=(p/1024).toFixed(2);u.default.success(`生成汇总文件: ${h} (${S} KB)`)}function C(e){return e.replace(/\/\/.*$/gm,``).replace(/\/\*[\s\S]*?\*\//g,``).replace(/\/\*\*[\s\S]*?\*\//g,``)}function w(e){return e.replace(/<!--[\s\S]*?-->/g,``)}function T(e){return e.replace(/\/\*[\s\S]*?\*\//g,``)}function E(e){let t=e;return t=t.replace(/<template[^>]*>([\s\S]*?)<\/template>/gi,(e,t)=>`<template>${w(t)}</template>`),t=t.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,(e,t)=>`<script>${C(t)}<\/script>`),t=t.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi,(e,t)=>`<style>${T(t)}</style>`),t}function D(e,t){switch(t){case`vue`:return E(e);case`tsx`:case`ts`:case`jsx`:case`js`:return C(e);case`scss`:case`css`:return T(e);default:return e}}function O(e,t){(e.match(/[\u4E00-\u9FA5]/g)||[]).forEach(e=>t.add(e)),(e.match(/\d/g)||[]).forEach(e=>t.add(e)),(e.match(/[a-z]/gi)||[]).forEach(e=>t.add(e)),(e.match(/[·.,;:!?@#$%^&*()_+\-=[\]{}|\\/"'<>,。;:!?、【】《》「」『』()]/g)||[]).forEach(e=>t.add(e))}function k(e){return e?Array.isArray(e)?e:[e]:[]}function A(e,t){for(let n of e)for(let e of n)e.trim()&&t.add(e)}async function j(e){u.default.info(`扫描项目文件...`);let t=await(0,d.default)(e.scan.srcDir.flatMap(t=>e.scan.extensions.map(e=>`${t}/**/*.${e}`)),{cwd:e.root,absolute:!1,onlyFiles:!0});u.default.info(` 找到 ${t.length} 个文件`);let n=new Set;for(let a of t)O(D(r.default.readFileSync(i.default.join(e.root,a),`utf-8`),a.split(`.`).pop()||``),n);A(k(e.scan.extraText),n),u.default.info(` 收集到 ${n.size} 个唯一字符`);for(let e of`0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.;:!?'",。;:!?`)n.add(e);return u.default.info(` 添加默认字符集后共 ${n.size} 个字符`),n}function M(e){return e?Array.isArray(e)?e:[e]:[]}function N(e,t){let n=new Set(e);for(let e of M(t))for(let t of e)t.trim()&&n.add(t);return Array.from(n).join(``)}async function P(e,t,n,a,o=`woff`){u.default.info(`生成字体子集...`);let s=new Map;for(let c of a){let a=e.get(`${c.family}-${c.weight}`);if(!a){u.default.warn(` 跳过 ${c.family} ${c.name} (未下载)`);continue}u.default.info(` 处理 ${c.family} ${c.name}...`);let l=c.format||o,d=N(n,c.extraText);try{let e=await new Promise((e,n)=>{let o=(0,f.default)().src(a).use(f.default.glyph({text:d,hinting:!1}));l===`woff2`?o.use(f.default.ttf2woff2()):l===`woff`&&o.use(f.default.ttf2woff()),o.dest(t).run((a,o)=>{if(a)n(a);else if(o&&o[0]){let n=o[0],a=l===`woff2`?`woff2`:l,s=`${_(c.family)}-${c.weight}.${a}`,u=i.default.join(t,s);n.path&&r.default.existsSync(n.path)&&r.default.renameSync(n.path,u),e({outputPath:u,size:r.default.statSync(u).size})}else n(Error(`No output file generated`))})}),n=(e.size/1024).toFixed(2);u.default.success(` 完成 ${c.family} ${c.name} (${n} KB)`),s.has(c.family)||s.set(c.family,[]),s.get(c.family).push({config:c,filePath:e.outputPath,size:e.size,format:l})}catch(e){u.default.error(` 失败 ${c.family} ${c.name}: ${e.message}`)}}return s}async function F(e,t){if(!t.basePath)throw Error(`COS 配置缺少 basePath,请在 fonts.config.ts 中设置 cos.basePath`);if(!t.bucket)throw Error(`COS 配置缺少 bucket,请在 fonts.config.ts 中设置 cos.bucket`);if(!t.region)throw Error(`COS 配置缺少 region,请在 fonts.config.ts 中设置 cos.region`);if(!t.cdnUrl)throw Error(`COS 配置缺少 cdnUrl,请在 fonts.config.ts 中设置 cos.cdnUrl`);let n=t.basePath.replace(`{version}`,e),r=[];r.push({type:`input`,name:`secretId`,message:`请输入腾讯云 SecretId:`,validate:e=>e.length>0||`SecretId 不能为空`}),r.push({type:`password`,name:`secretKey`,message:`请输入腾讯云 SecretKey:`,validate:e=>e.length>0||`SecretKey 不能为空`}),t.overwrite===void 0&&r.push({type:`confirm`,name:`overwrite`,message:`如果文件已存在,是否覆盖?`,initial:!1});let i=await m.default.prompt(r);return{secretId:i.secretId,secretKey:i.secretKey,bucket:t.bucket,region:t.region,uploadPath:n,cdnUrl:t.cdnUrl,overwrite:t.overwrite??i.overwrite??!1}}async function I(){try{let e=await import(`cos-nodejs-sdk-v5`);return e.default||e}catch{throw Error(`缺少依赖 cos-nodejs-sdk-v5,请在使用项目中安装该包后重试`)}}var L=class{validateConfig(e){if(!e.cos.basePath||!e.cos.bucket||!e.cos.region||!e.cos.cdnUrl)throw Error(`COS 上传配置不完整,请检查 fonts.config.ts 中的 cos 配置`)}async create(e,t){this.cosConfig=await F(e,t.cos),this.cos=new(await(I()))({SecretId:this.cosConfig.secretId,SecretKey:this.cosConfig.secretKey}),u.default.info(`开始上传到 COS...`),u.default.info(` Bucket: ${this.cosConfig.bucket}`),u.default.info(` 区域: ${this.cosConfig.region}`),u.default.info(` 路径: ${this.cosConfig.uploadPath}`)}async uploadFile(e){let t=`${this.cosConfig.uploadPath}/${e.fileName}`.replace(/\/+/g,`/`);if(!this.cosConfig.overwrite)try{return await this.cos.headObject({Bucket:this.cosConfig.bucket,Region:this.cosConfig.region,Key:t}),{status:`skipped`}}catch{}return await this.cos.putObject({Bucket:this.cosConfig.bucket,Region:this.cosConfig.region,Key:t,StorageClass:`STANDARD`,Body:r.default.createReadStream(e.filePath)}),{status:`uploaded`}}finalize(){u.default.success(`上传完成!`),u.default.info(` CDN 地址: ${this.cosConfig.cdnUrl}${this.cosConfig.uploadPath}/`)}};function R(e){switch(e.upload.provider||`cos`){case`cos`:return new L;default:throw Error(`不支持的上传 provider: ${e.upload.provider}`)}}async function z(e,t,n){let a=R(n);a.validateConfig(n);let o=e.filter(e=>r.default.existsSync(e)?r.default.statSync(e).size>0:!1).map(e=>{let t=r.default.statSync(e);return{filePath:e,fileName:i.default.basename(e),sizeKB:(t.size/1024).toFixed(2)}});if(o.length===0){u.default.error(`没有有效的文件可上传`);return}await a.create(t,n);let s=(0,p.default)(n.upload.concurrency??5);await Promise.all(o.map(e=>s(async()=>{try{(await a.uploadFile(e)).status===`skipped`?u.default.info(` 跳过 ${e.fileName} (远端已存在)`):u.default.success(` 完成 ${e.fileName} (${e.sizeKB} KB)`)}catch(t){u.default.error(` 失败 ${e.fileName} 上传失败:`,t.message)}}))),a.finalize()}const B=i.default.dirname((()=>{try{return r.default.realpathSync((0,o.fileURLToPath)(require(`url`).pathToFileURL(__filename).href))}catch{return(0,o.fileURLToPath)(require(`url`).pathToFileURL(__filename).href)}})()),V=JSON.parse(r.default.readFileSync(i.default.resolve(B,`../package.json`),`utf-8`)),H=new s.CAC(`charbi`);H.version(V.version),H.option(`--mode <mode>`,`development 或 production`),H.command(``).action(U),H.command(`build`).option(`--no-cache`,`强制重新下载字体文件`).action(W),H.command(`upload`).action(G);async function U(e,t){await W({cache:!0},t),await G(void 0,t)}async function W(e,t){let o=t?.mode||`development`,s=await n.loadConfig(o),c=n.getVersion(s.version);u.default.info(`charbi`),u.default.info(` 模式: ${o}`),u.default.info(` 版本基线: ${c}`),u.default.info(` 格式: ${s.output.format}`);let l=i.default.join(s.cacheDir,`subsets`);r.default.mkdirSync(l,{recursive:!0});for(let e of r.default.readdirSync(l))r.default.unlinkSync(i.default.join(l,e));let d=await g(s.cacheDir,s.fonts,!e.cache);d.size===0&&(u.default.error(`没有可用的字体文件,构建失败`),a.default.exit(1));let f=await P(d,l,await j(s),s.fonts,s.output.format),p=0,m=0;for(let e of f.values())for(let t of e)p+=t.size,m++;let h=(p/1024).toFixed(2);u.default.success(`字体子集生成完成!`),u.default.info(` 生成文件: ${m} 个`),u.default.info(` 总大小: ${h} KB`),u.default.info(` 输出目录: ${l}`);let _=c;u.default.info(` 字体版本: ${_}`),await S(f,s,_,s.output.format)}async function G(e,t){let o=t?.mode||`development`,s=await n.loadConfig(o),c=n.getVersion(s.version);u.default.info(`charbi upload`),u.default.info(` 模式: ${o}`),u.default.info(` 版本: ${c}`);let l=i.default.join(s.cacheDir,`subsets`);r.default.existsSync(l)||(u.default.error(`没有找到字体文件目录: ${l}`),u.default.error(`请先执行 build 命令`),a.default.exit(1));let d=[];for(let e of r.default.readdirSync(l)){let t=i.default.join(l,e);r.default.statSync(t).isFile()&&d.push(t)}d.length===0&&(u.default.error(`没有找到字体文件,请先执行 build`),a.default.exit(1)),await z(d,c,s)}H.help(),H.parse();
32
+ `,_=i.default.join(l,h);r.default.writeFileSync(_,g);let S=(p/1024).toFixed(2);u.default.success(`生成汇总文件: ${h} (${S} KB)`)}function C(e){return e.replace(/\/\/.*$/gm,``).replace(/\/\*[\s\S]*?\*\//g,``).replace(/\/\*\*[\s\S]*?\*\//g,``)}function w(e){return e.replace(/<!--[\s\S]*?-->/g,``)}function T(e){return e.replace(/\/\*[\s\S]*?\*\//g,``)}function E(e){let t=e;return t=t.replace(/<template[^>]*>([\s\S]*?)<\/template>/gi,(e,t)=>`<template>${w(t)}</template>`),t=t.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,(e,t)=>`<script>${C(t)}<\/script>`),t=t.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi,(e,t)=>`<style>${T(t)}</style>`),t}function D(e,t){switch(t){case`vue`:return E(e);case`tsx`:case`ts`:case`jsx`:case`js`:return C(e);case`scss`:case`css`:return T(e);default:return e}}function O(e,t){(e.match(/[\u4E00-\u9FA5]/g)||[]).forEach(e=>t.add(e)),(e.match(/\d/g)||[]).forEach(e=>t.add(e)),(e.match(/[a-z]/gi)||[]).forEach(e=>t.add(e)),(e.match(/[·.,;:!?@#$%^&*()_+\-=[\]{}|\\/"'<>,。;:!?、【】《》「」『』()]/g)||[]).forEach(e=>t.add(e))}function k(e){return e?Array.isArray(e)?e:[e]:[]}function A(e,t){for(let n of e)for(let e of n)e.trim()&&t.add(e)}async function j(e){u.default.info(`扫描项目文件...`);let t=await(0,d.default)(e.scan.srcDir.flatMap(t=>e.scan.extensions.map(e=>`${t}/**/*.${e}`)),{cwd:e.root,absolute:!1,onlyFiles:!0});u.default.info(` 找到 ${t.length} 个文件`);let n=new Set;for(let a of t)O(D(r.default.readFileSync(i.default.join(e.root,a),`utf-8`),a.split(`.`).pop()||``),n);A(k(e.scan.extraText),n),u.default.info(` 收集到 ${n.size} 个唯一字符`);for(let e of`0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.;:!?'",。;:!?`)n.add(e);return u.default.info(` 添加默认字符集后共 ${n.size} 个字符`),n}function M(e){return e?Array.isArray(e)?e:[e]:[]}function N(e,t){let n=new Set(e);for(let e of M(t))for(let t of e)t.trim()&&n.add(t);return Array.from(n).join(``)}async function P(e,t,n,a,o=`woff`){u.default.info(`生成字体子集...`);let s=new Map;for(let c of a){let a=e.get(`${c.family}-${c.weight}`);if(!a){u.default.warn(` 跳过 ${c.family} ${c.name} (未下载)`);continue}u.default.info(` 处理 ${c.family} ${c.name}...`);let l=c.format||o,d=N(n,c.extraText);try{let e=await new Promise((e,n)=>{let o=(0,f.default)().src(a).use(f.default.glyph({text:d,hinting:!1}));l===`woff2`?o.use(f.default.ttf2woff2()):l===`woff`&&o.use(f.default.ttf2woff()),o.dest(t).run((a,o)=>{if(a)n(a);else if(o&&o[0]){let n=o[0],a=l===`woff2`?`woff2`:l,s=`${_(c.family)}-${c.weight}.${a}`,u=i.default.join(t,s);n.path&&r.default.existsSync(n.path)&&r.default.renameSync(n.path,u),e({outputPath:u,size:r.default.statSync(u).size})}else n(Error(`No output file generated`))})}),n=(e.size/1024).toFixed(2);u.default.success(` 完成 ${c.family} ${c.name} (${n} KB)`),s.has(c.family)||s.set(c.family,[]),s.get(c.family).push({config:c,filePath:e.outputPath,size:e.size,format:l})}catch(e){u.default.error(` 失败 ${c.family} ${c.name}: ${e.message}`)}}return s}async function F(e,t){if(!t.basePath)throw Error(`COS 配置缺少 basePath,请在 fonts.config.ts 中设置 cos.basePath`);if(!t.bucket)throw Error(`COS 配置缺少 bucket,请在 fonts.config.ts 中设置 cos.bucket`);if(!t.region)throw Error(`COS 配置缺少 region,请在 fonts.config.ts 中设置 cos.region`);if(!t.cdnUrl)throw Error(`COS 配置缺少 cdnUrl,请在 fonts.config.ts 中设置 cos.cdnUrl`);let n=t.basePath.replace(`{version}`,e),r=[];r.push({type:`input`,name:`secretId`,message:`请输入腾讯云 SecretId:`,validate:e=>e.length>0||`SecretId 不能为空`}),r.push({type:`password`,name:`secretKey`,message:`请输入腾讯云 SecretKey:`,validate:e=>e.length>0||`SecretKey 不能为空`}),t.overwrite===void 0&&r.push({type:`confirm`,name:`overwrite`,message:`如果文件已存在,是否覆盖?`,initial:!1});let i=await m.default.prompt(r);return{secretId:i.secretId,secretKey:i.secretKey,bucket:t.bucket,region:t.region,uploadPath:n,cdnUrl:t.cdnUrl,overwrite:t.overwrite??i.overwrite??!1}}async function I(){try{let e=await import(`cos-nodejs-sdk-v5`);return e.default||e}catch{throw Error(`缺少依赖 cos-nodejs-sdk-v5,请在使用项目中安装该包后重试`)}}var L=class{validateConfig(e){if(!e.cos.basePath||!e.cos.bucket||!e.cos.region||!e.cos.cdnUrl)throw Error(`COS 上传配置不完整,请检查 fonts.config.ts 中的 cos 配置`)}async create(e,t){this.cosConfig=await F(e,t.cos),this.cos=new(await(I()))({SecretId:this.cosConfig.secretId,SecretKey:this.cosConfig.secretKey}),u.default.info(`开始上传到 COS...`),u.default.info(` Bucket: ${this.cosConfig.bucket}`),u.default.info(` 区域: ${this.cosConfig.region}`),u.default.info(` 路径: ${this.cosConfig.uploadPath}`)}async uploadFile(e){let t=`${this.cosConfig.uploadPath}/${e.fileName}`.replace(/\/+/g,`/`);if(!this.cosConfig.overwrite)try{return await this.cos.headObject({Bucket:this.cosConfig.bucket,Region:this.cosConfig.region,Key:t}),{status:`skipped`}}catch{}return await this.cos.putObject({Bucket:this.cosConfig.bucket,Region:this.cosConfig.region,Key:t,StorageClass:`STANDARD`,Body:r.default.createReadStream(e.filePath)}),{status:`uploaded`}}finalize(){u.default.success(`上传完成!`),u.default.info(` CDN 地址: ${this.cosConfig.cdnUrl}${this.cosConfig.uploadPath}/`)}};function R(e){switch(e.upload.provider||`cos`){case`cos`:return new L;default:throw Error(`不支持的上传 provider: ${e.upload.provider}`)}}async function z(e,t,n){let a=R(n);a.validateConfig(n);let o=e.filter(e=>r.default.existsSync(e)?r.default.statSync(e).size>0:!1).map(e=>{let t=r.default.statSync(e);return{filePath:e,fileName:i.default.basename(e),sizeKB:(t.size/1024).toFixed(2)}});if(o.length===0){u.default.error(`没有有效的文件可上传`);return}await a.create(t,n);let s=(0,p.default)(n.upload.concurrency??5);await Promise.all(o.map(e=>s(async()=>{try{(await a.uploadFile(e)).status===`skipped`?u.default.info(` 跳过 ${e.fileName} (远端已存在)`):u.default.success(` 完成 ${e.fileName} (${e.sizeKB} KB)`)}catch(t){u.default.error(` 失败 ${e.fileName} 上传失败:`,t.message)}}))),a.finalize()}const B=i.default.dirname((()=>{try{return r.default.realpathSync((0,s.fileURLToPath)(require(`url`).pathToFileURL(__filename).href))}catch{return(0,s.fileURLToPath)(require(`url`).pathToFileURL(__filename).href)}})()),V=JSON.parse(r.default.readFileSync(i.default.resolve(B,`../package.json`),`utf-8`)),H=new o.CAC(`charbi`);H.version(V.version),H.option(`--mode <mode>`,`development 或 production`),H.command(``).action(U),H.command(`build`).option(`--no-cache`,`强制重新下载字体文件`).action(W),H.command(`upload`).action(G);async function U(e,t){await W({cache:!0},t),await G(void 0,t)}async function W(e,t){let o=t?.mode||`development`,s=await n.loadConfig(o),c=n.getVersion(s.version);u.default.info(`charbi`),u.default.info(` 模式: ${o}`),u.default.info(` 版本基线: ${c}`),u.default.info(` 格式: ${s.output.format}`);let l=i.default.join(s.cacheDir,`subsets`);r.default.mkdirSync(l,{recursive:!0});for(let e of r.default.readdirSync(l))r.default.unlinkSync(i.default.join(l,e));let d=await g(s.cacheDir,s.fonts,!e.cache);d.size===0&&(u.default.error(`没有可用的字体文件,构建失败`),a.default.exit(1));let f=await P(d,l,await j(s),s.fonts,s.output.format),p=0,m=0;for(let e of f.values())for(let t of e)p+=t.size,m++;let h=(p/1024).toFixed(2);u.default.success(`字体子集生成完成!`),u.default.info(` 生成文件: ${m} 个`),u.default.info(` 总大小: ${h} KB`),u.default.info(` 输出目录: ${l}`);let _=c;u.default.info(` 字体版本: ${_}`),await S(f,s,_,s.output.format)}async function G(e,t){let o=t?.mode||`development`,s=await n.loadConfig(o),c=n.getVersion(s.version);u.default.info(`charbi upload`),u.default.info(` 模式: ${o}`),u.default.info(` 版本: ${c}`);let l=i.default.join(s.cacheDir,`subsets`);r.default.existsSync(l)||(u.default.error(`没有找到字体文件目录: ${l}`),u.default.error(`请先执行 build 命令`),a.default.exit(1));let d=[];for(let e of r.default.readdirSync(l)){let t=i.default.join(l,e);r.default.statSync(t).isFile()&&d.push(t)}d.length===0&&(u.default.error(`没有找到字体文件,请先执行 build`),a.default.exit(1)),await z(d,c,s)}H.help(),H.parse();
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import{t as e}from"./schema-BuMY-k7u.mjs";import{getVersion as t,loadConfig as n}from"./config/loader.mjs";import r from"node:fs";import i from"node:path";import a from"node:process";import{fileURLToPath as o}from"node:url";import{CAC as s}from"cac";import c from"node:http";import l from"node:https";import u from"consola";import d from"fast-glob";import f from"fontmin";import p from"p-limit";import m from"enquirer";async function h(e,t){return new Promise((n,i)=>{let a=e.startsWith(`https`)?l:c,o=r.createWriteStream(t);a.get(e,e=>{if(e.statusCode!==200){o.close(),r.unlink(t,()=>{}),i(Error(`下载失败: ${e.statusCode}`));return}e.pipe(o),o.on(`finish`,()=>{if(o.close(),r.statSync(t).size===0){r.unlink(t,()=>{}),i(Error(`下载的文件为空`));return}n()})}).on(`error`,e=>{o.close(),r.unlink(t,()=>{}),i(e)})})}async function g(e,t,n=!1){u.info(`下载字体文件...`),u.info(` 缓存目录: ${e}`),r.mkdirSync(e,{recursive:!0});let a=new Map;for(let o of t){let t=`${o.family}-${o.weight}.ttf`,s=i.join(e,t);if(n&&r.existsSync(s)&&r.unlinkSync(s),r.existsSync(s)){let e=r.statSync(s);if(e.size===0)r.unlinkSync(s);else{let t=(e.size/1024).toFixed(2);u.info(` ✓ ${o.family} ${o.name} (缓存, ${t} KB)`),a.set(`${o.family}-${o.weight}`,s);continue}}u.info(` ↓ ${o.family} ${o.name}...`);try{await h(o.url,s);let e=(r.statSync(s).size/1024).toFixed(2);u.success(` 完成 ${t} (${e} KB)`),a.set(`${o.family}-${o.weight}`,s)}catch(e){u.error(` 失败 ${o.family} ${o.name} 下载失败:`,e.message),r.existsSync(s)&&r.unlinkSync(s)}}return a}function _(e){return e.replace(/\s+/g,``)}function v(e,t,n,r){let{cos:i}=e;if(i.cdnUrl){if(!i.basePath)throw Error(`COS 配置缺少 basePath,请在 fonts.config.ts 中设置 cos.basePath`);let e=`${_(t.family)}-${t.weight}.${r}`,a=i.basePath.replace(`{version}`,n),o=a.startsWith(`/`)?a.slice(1):a;return`${i.cdnUrl}/${o}/${e}`.replace(/\/+/g,`/`)}return`./fonts/${_(t.family)}-${t.weight}.${r}`}function y(e,t,n,r,i=`normal`){return`@font-face {
1
+ import{t as e}from"./schema-BuMY-k7u.mjs";import{getVersion as t,loadConfig as n}from"./config/loader.mjs";import r from"node:fs";import i from"node:path";import a from"node:process";import{CAC as o}from"cac";import{fileURLToPath as s}from"node:url";import c from"node:http";import l from"node:https";import u from"consola";import d from"fast-glob";import f from"fontmin";import p from"p-limit";import m from"enquirer";async function h(e,t){return new Promise((n,i)=>{let a=e.startsWith(`https`)?l:c,o=r.createWriteStream(t);a.get(e,e=>{if(e.statusCode!==200){o.close(),r.unlink(t,()=>{}),i(Error(`下载失败: ${e.statusCode}`));return}e.pipe(o),o.on(`finish`,()=>{if(o.close(),r.statSync(t).size===0){r.unlink(t,()=>{}),i(Error(`下载的文件为空`));return}n()})}).on(`error`,e=>{o.close(),r.unlink(t,()=>{}),i(e)})})}async function g(e,t,n=!1){u.info(`下载字体文件...`),u.info(` 缓存目录: ${e}`),r.mkdirSync(e,{recursive:!0});let a=new Map;for(let o of t){let t=`${o.family}-${o.weight}.ttf`,s=i.join(e,t);if(n&&r.existsSync(s)&&r.unlinkSync(s),r.existsSync(s)){let e=r.statSync(s);if(e.size===0)r.unlinkSync(s);else{let t=(e.size/1024).toFixed(2);u.info(` ✓ ${o.family} ${o.name} (缓存, ${t} KB)`),a.set(`${o.family}-${o.weight}`,s);continue}}u.info(` ↓ ${o.family} ${o.name}...`);try{await h(o.url,s);let e=(r.statSync(s).size/1024).toFixed(2);u.success(` 完成 ${t} (${e} KB)`),a.set(`${o.family}-${o.weight}`,s)}catch(e){u.error(` 失败 ${o.family} ${o.name} 下载失败:`,e.message),r.existsSync(s)&&r.unlinkSync(s)}}return a}function _(e){return e.replace(/\s+/g,``)}function v(e,t,n,r){let{cos:i}=e;if(i.cdnUrl){if(!i.basePath)throw Error(`COS 配置缺少 basePath,请在 fonts.config.ts 中设置 cos.basePath`);let e=`${_(t.family)}-${t.weight}.${r}`,a=i.basePath.replace(`{version}`,n),o=a.startsWith(`/`)?a.slice(1):a;return`${i.cdnUrl}/${o}/${e}`.replace(/\/+/g,`/`)}return`./fonts/${_(t.family)}-${t.weight}.${r}`}function y(e,t,n,r,i=`normal`){return`@font-face {
2
2
  font-family: '${e}';
3
3
  src: url('${n}') format('${r}');
4
4
  font-display: swap;
@@ -29,4 +29,4 @@ import{t as e}from"./schema-BuMY-k7u.mjs";import{getVersion as t,loadConfig as n
29
29
 
30
30
  ${f.map(t=>x(`${e}/${t.replace(/\.(scss|css)$/,``)}`,n.output.styleFormat)).join(`
31
31
  `)}
32
- `,_=i.join(l,h);r.writeFileSync(_,g);let S=(p/1024).toFixed(2);u.success(`生成汇总文件: ${h} (${S} KB)`)}function C(e){return e.replace(/\/\/.*$/gm,``).replace(/\/\*[\s\S]*?\*\//g,``).replace(/\/\*\*[\s\S]*?\*\//g,``)}function w(e){return e.replace(/<!--[\s\S]*?-->/g,``)}function T(e){return e.replace(/\/\*[\s\S]*?\*\//g,``)}function E(e){let t=e;return t=t.replace(/<template[^>]*>([\s\S]*?)<\/template>/gi,(e,t)=>`<template>${w(t)}</template>`),t=t.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,(e,t)=>`<script>${C(t)}<\/script>`),t=t.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi,(e,t)=>`<style>${T(t)}</style>`),t}function D(e,t){switch(t){case`vue`:return E(e);case`tsx`:case`ts`:case`jsx`:case`js`:return C(e);case`scss`:case`css`:return T(e);default:return e}}function O(e,t){(e.match(/[\u4E00-\u9FA5]/g)||[]).forEach(e=>t.add(e)),(e.match(/\d/g)||[]).forEach(e=>t.add(e)),(e.match(/[a-z]/gi)||[]).forEach(e=>t.add(e)),(e.match(/[·.,;:!?@#$%^&*()_+\-=[\]{}|\\/"'<>,。;:!?、【】《》「」『』()]/g)||[]).forEach(e=>t.add(e))}function k(e){return e?Array.isArray(e)?e:[e]:[]}function A(e,t){for(let n of e)for(let e of n)e.trim()&&t.add(e)}async function j(e){u.info(`扫描项目文件...`);let t=await d(e.scan.srcDir.flatMap(t=>e.scan.extensions.map(e=>`${t}/**/*.${e}`)),{cwd:e.root,absolute:!1,onlyFiles:!0});u.info(` 找到 ${t.length} 个文件`);let n=new Set;for(let a of t)O(D(r.readFileSync(i.join(e.root,a),`utf-8`),a.split(`.`).pop()||``),n);A(k(e.scan.extraText),n),u.info(` 收集到 ${n.size} 个唯一字符`);for(let e of`0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.;:!?'",。;:!?`)n.add(e);return u.info(` 添加默认字符集后共 ${n.size} 个字符`),n}function M(e){return e?Array.isArray(e)?e:[e]:[]}function N(e,t){let n=new Set(e);for(let e of M(t))for(let t of e)t.trim()&&n.add(t);return Array.from(n).join(``)}async function P(e,t,n,a,o=`woff`){u.info(`生成字体子集...`);let s=new Map;for(let c of a){let a=e.get(`${c.family}-${c.weight}`);if(!a){u.warn(` 跳过 ${c.family} ${c.name} (未下载)`);continue}u.info(` 处理 ${c.family} ${c.name}...`);let l=c.format||o,d=N(n,c.extraText);try{let e=await new Promise((e,n)=>{let o=f().src(a).use(f.glyph({text:d,hinting:!1}));l===`woff2`?o.use(f.ttf2woff2()):l===`woff`&&o.use(f.ttf2woff()),o.dest(t).run((a,o)=>{if(a)n(a);else if(o&&o[0]){let n=o[0],a=l===`woff2`?`woff2`:l,s=`${_(c.family)}-${c.weight}.${a}`,u=i.join(t,s);n.path&&r.existsSync(n.path)&&r.renameSync(n.path,u),e({outputPath:u,size:r.statSync(u).size})}else n(Error(`No output file generated`))})}),n=(e.size/1024).toFixed(2);u.success(` 完成 ${c.family} ${c.name} (${n} KB)`),s.has(c.family)||s.set(c.family,[]),s.get(c.family).push({config:c,filePath:e.outputPath,size:e.size,format:l})}catch(e){u.error(` 失败 ${c.family} ${c.name}: ${e.message}`)}}return s}async function F(e,t){if(!t.basePath)throw Error(`COS 配置缺少 basePath,请在 fonts.config.ts 中设置 cos.basePath`);if(!t.bucket)throw Error(`COS 配置缺少 bucket,请在 fonts.config.ts 中设置 cos.bucket`);if(!t.region)throw Error(`COS 配置缺少 region,请在 fonts.config.ts 中设置 cos.region`);if(!t.cdnUrl)throw Error(`COS 配置缺少 cdnUrl,请在 fonts.config.ts 中设置 cos.cdnUrl`);let n=t.basePath.replace(`{version}`,e),r=[];r.push({type:`input`,name:`secretId`,message:`请输入腾讯云 SecretId:`,validate:e=>e.length>0||`SecretId 不能为空`}),r.push({type:`password`,name:`secretKey`,message:`请输入腾讯云 SecretKey:`,validate:e=>e.length>0||`SecretKey 不能为空`}),t.overwrite===void 0&&r.push({type:`confirm`,name:`overwrite`,message:`如果文件已存在,是否覆盖?`,initial:!1});let i=await m.prompt(r);return{secretId:i.secretId,secretKey:i.secretKey,bucket:t.bucket,region:t.region,uploadPath:n,cdnUrl:t.cdnUrl,overwrite:t.overwrite??i.overwrite??!1}}async function I(){try{let e=await import(`cos-nodejs-sdk-v5`);return e.default||e}catch{throw Error(`缺少依赖 cos-nodejs-sdk-v5,请在使用项目中安装该包后重试`)}}var L=class{validateConfig(e){if(!e.cos.basePath||!e.cos.bucket||!e.cos.region||!e.cos.cdnUrl)throw Error(`COS 上传配置不完整,请检查 fonts.config.ts 中的 cos 配置`)}async create(e,t){this.cosConfig=await F(e,t.cos),this.cos=new(await(I()))({SecretId:this.cosConfig.secretId,SecretKey:this.cosConfig.secretKey}),u.info(`开始上传到 COS...`),u.info(` Bucket: ${this.cosConfig.bucket}`),u.info(` 区域: ${this.cosConfig.region}`),u.info(` 路径: ${this.cosConfig.uploadPath}`)}async uploadFile(e){let t=`${this.cosConfig.uploadPath}/${e.fileName}`.replace(/\/+/g,`/`);if(!this.cosConfig.overwrite)try{return await this.cos.headObject({Bucket:this.cosConfig.bucket,Region:this.cosConfig.region,Key:t}),{status:`skipped`}}catch{}return await this.cos.putObject({Bucket:this.cosConfig.bucket,Region:this.cosConfig.region,Key:t,StorageClass:`STANDARD`,Body:r.createReadStream(e.filePath)}),{status:`uploaded`}}finalize(){u.success(`上传完成!`),u.info(` CDN 地址: ${this.cosConfig.cdnUrl}${this.cosConfig.uploadPath}/`)}};function R(e){switch(e.upload.provider||`cos`){case`cos`:return new L;default:throw Error(`不支持的上传 provider: ${e.upload.provider}`)}}async function z(e,t,n){let a=R(n);a.validateConfig(n);let o=e.filter(e=>r.existsSync(e)?r.statSync(e).size>0:!1).map(e=>{let t=r.statSync(e);return{filePath:e,fileName:i.basename(e),sizeKB:(t.size/1024).toFixed(2)}});if(o.length===0){u.error(`没有有效的文件可上传`);return}await a.create(t,n);let s=p(n.upload.concurrency??5);await Promise.all(o.map(e=>s(async()=>{try{(await a.uploadFile(e)).status===`skipped`?u.info(` 跳过 ${e.fileName} (远端已存在)`):u.success(` 完成 ${e.fileName} (${e.sizeKB} KB)`)}catch(t){u.error(` 失败 ${e.fileName} 上传失败:`,t.message)}}))),a.finalize()}const B=i.dirname((()=>{try{return r.realpathSync(o(import.meta.url))}catch{return o(import.meta.url)}})()),V=JSON.parse(r.readFileSync(i.resolve(B,`../package.json`),`utf-8`)),H=new s(`charbi`);H.version(V.version),H.option(`--mode <mode>`,`development 或 production`),H.command(``).action(U),H.command(`build`).option(`--no-cache`,`强制重新下载字体文件`).action(W),H.command(`upload`).action(G);async function U(e,t){await W({cache:!0},t),await G(void 0,t)}async function W(e,o){let s=o?.mode||`development`,c=await n(s),l=t(c.version);u.info(`charbi`),u.info(` 模式: ${s}`),u.info(` 版本基线: ${l}`),u.info(` 格式: ${c.output.format}`);let d=i.join(c.cacheDir,`subsets`);r.mkdirSync(d,{recursive:!0});for(let e of r.readdirSync(d))r.unlinkSync(i.join(d,e));let f=await g(c.cacheDir,c.fonts,!e.cache);f.size===0&&(u.error(`没有可用的字体文件,构建失败`),a.exit(1));let p=await P(f,d,await j(c),c.fonts,c.output.format),m=0,h=0;for(let e of p.values())for(let t of e)m+=t.size,h++;let _=(m/1024).toFixed(2);u.success(`字体子集生成完成!`),u.info(` 生成文件: ${h} 个`),u.info(` 总大小: ${_} KB`),u.info(` 输出目录: ${d}`);let v=l;u.info(` 字体版本: ${v}`),await S(p,c,v,c.output.format)}async function G(e,o){let s=o?.mode||`development`,c=await n(s),l=t(c.version);u.info(`charbi upload`),u.info(` 模式: ${s}`),u.info(` 版本: ${l}`);let d=i.join(c.cacheDir,`subsets`);r.existsSync(d)||(u.error(`没有找到字体文件目录: ${d}`),u.error(`请先执行 build 命令`),a.exit(1));let f=[];for(let e of r.readdirSync(d)){let t=i.join(d,e);r.statSync(t).isFile()&&f.push(t)}f.length===0&&(u.error(`没有找到字体文件,请先执行 build`),a.exit(1)),await z(f,l,c)}H.help(),H.parse();export{};
32
+ `,_=i.join(l,h);r.writeFileSync(_,g);let S=(p/1024).toFixed(2);u.success(`生成汇总文件: ${h} (${S} KB)`)}function C(e){return e.replace(/\/\/.*$/gm,``).replace(/\/\*[\s\S]*?\*\//g,``).replace(/\/\*\*[\s\S]*?\*\//g,``)}function w(e){return e.replace(/<!--[\s\S]*?-->/g,``)}function T(e){return e.replace(/\/\*[\s\S]*?\*\//g,``)}function E(e){let t=e;return t=t.replace(/<template[^>]*>([\s\S]*?)<\/template>/gi,(e,t)=>`<template>${w(t)}</template>`),t=t.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,(e,t)=>`<script>${C(t)}<\/script>`),t=t.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi,(e,t)=>`<style>${T(t)}</style>`),t}function D(e,t){switch(t){case`vue`:return E(e);case`tsx`:case`ts`:case`jsx`:case`js`:return C(e);case`scss`:case`css`:return T(e);default:return e}}function O(e,t){(e.match(/[\u4E00-\u9FA5]/g)||[]).forEach(e=>t.add(e)),(e.match(/\d/g)||[]).forEach(e=>t.add(e)),(e.match(/[a-z]/gi)||[]).forEach(e=>t.add(e)),(e.match(/[·.,;:!?@#$%^&*()_+\-=[\]{}|\\/"'<>,。;:!?、【】《》「」『』()]/g)||[]).forEach(e=>t.add(e))}function k(e){return e?Array.isArray(e)?e:[e]:[]}function A(e,t){for(let n of e)for(let e of n)e.trim()&&t.add(e)}async function j(e){u.info(`扫描项目文件...`);let t=await d(e.scan.srcDir.flatMap(t=>e.scan.extensions.map(e=>`${t}/**/*.${e}`)),{cwd:e.root,absolute:!1,onlyFiles:!0});u.info(` 找到 ${t.length} 个文件`);let n=new Set;for(let a of t)O(D(r.readFileSync(i.join(e.root,a),`utf-8`),a.split(`.`).pop()||``),n);A(k(e.scan.extraText),n),u.info(` 收集到 ${n.size} 个唯一字符`);for(let e of`0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.;:!?'",。;:!?`)n.add(e);return u.info(` 添加默认字符集后共 ${n.size} 个字符`),n}function M(e){return e?Array.isArray(e)?e:[e]:[]}function N(e,t){let n=new Set(e);for(let e of M(t))for(let t of e)t.trim()&&n.add(t);return Array.from(n).join(``)}async function P(e,t,n,a,o=`woff`){u.info(`生成字体子集...`);let s=new Map;for(let c of a){let a=e.get(`${c.family}-${c.weight}`);if(!a){u.warn(` 跳过 ${c.family} ${c.name} (未下载)`);continue}u.info(` 处理 ${c.family} ${c.name}...`);let l=c.format||o,d=N(n,c.extraText);try{let e=await new Promise((e,n)=>{let o=f().src(a).use(f.glyph({text:d,hinting:!1}));l===`woff2`?o.use(f.ttf2woff2()):l===`woff`&&o.use(f.ttf2woff()),o.dest(t).run((a,o)=>{if(a)n(a);else if(o&&o[0]){let n=o[0],a=l===`woff2`?`woff2`:l,s=`${_(c.family)}-${c.weight}.${a}`,u=i.join(t,s);n.path&&r.existsSync(n.path)&&r.renameSync(n.path,u),e({outputPath:u,size:r.statSync(u).size})}else n(Error(`No output file generated`))})}),n=(e.size/1024).toFixed(2);u.success(` 完成 ${c.family} ${c.name} (${n} KB)`),s.has(c.family)||s.set(c.family,[]),s.get(c.family).push({config:c,filePath:e.outputPath,size:e.size,format:l})}catch(e){u.error(` 失败 ${c.family} ${c.name}: ${e.message}`)}}return s}async function F(e,t){if(!t.basePath)throw Error(`COS 配置缺少 basePath,请在 fonts.config.ts 中设置 cos.basePath`);if(!t.bucket)throw Error(`COS 配置缺少 bucket,请在 fonts.config.ts 中设置 cos.bucket`);if(!t.region)throw Error(`COS 配置缺少 region,请在 fonts.config.ts 中设置 cos.region`);if(!t.cdnUrl)throw Error(`COS 配置缺少 cdnUrl,请在 fonts.config.ts 中设置 cos.cdnUrl`);let n=t.basePath.replace(`{version}`,e),r=[];r.push({type:`input`,name:`secretId`,message:`请输入腾讯云 SecretId:`,validate:e=>e.length>0||`SecretId 不能为空`}),r.push({type:`password`,name:`secretKey`,message:`请输入腾讯云 SecretKey:`,validate:e=>e.length>0||`SecretKey 不能为空`}),t.overwrite===void 0&&r.push({type:`confirm`,name:`overwrite`,message:`如果文件已存在,是否覆盖?`,initial:!1});let i=await m.prompt(r);return{secretId:i.secretId,secretKey:i.secretKey,bucket:t.bucket,region:t.region,uploadPath:n,cdnUrl:t.cdnUrl,overwrite:t.overwrite??i.overwrite??!1}}async function I(){try{let e=await import(`cos-nodejs-sdk-v5`);return e.default||e}catch{throw Error(`缺少依赖 cos-nodejs-sdk-v5,请在使用项目中安装该包后重试`)}}var L=class{validateConfig(e){if(!e.cos.basePath||!e.cos.bucket||!e.cos.region||!e.cos.cdnUrl)throw Error(`COS 上传配置不完整,请检查 fonts.config.ts 中的 cos 配置`)}async create(e,t){this.cosConfig=await F(e,t.cos),this.cos=new(await(I()))({SecretId:this.cosConfig.secretId,SecretKey:this.cosConfig.secretKey}),u.info(`开始上传到 COS...`),u.info(` Bucket: ${this.cosConfig.bucket}`),u.info(` 区域: ${this.cosConfig.region}`),u.info(` 路径: ${this.cosConfig.uploadPath}`)}async uploadFile(e){let t=`${this.cosConfig.uploadPath}/${e.fileName}`.replace(/\/+/g,`/`);if(!this.cosConfig.overwrite)try{return await this.cos.headObject({Bucket:this.cosConfig.bucket,Region:this.cosConfig.region,Key:t}),{status:`skipped`}}catch{}return await this.cos.putObject({Bucket:this.cosConfig.bucket,Region:this.cosConfig.region,Key:t,StorageClass:`STANDARD`,Body:r.createReadStream(e.filePath)}),{status:`uploaded`}}finalize(){u.success(`上传完成!`),u.info(` CDN 地址: ${this.cosConfig.cdnUrl}${this.cosConfig.uploadPath}/`)}};function R(e){switch(e.upload.provider||`cos`){case`cos`:return new L;default:throw Error(`不支持的上传 provider: ${e.upload.provider}`)}}async function z(e,t,n){let a=R(n);a.validateConfig(n);let o=e.filter(e=>r.existsSync(e)?r.statSync(e).size>0:!1).map(e=>{let t=r.statSync(e);return{filePath:e,fileName:i.basename(e),sizeKB:(t.size/1024).toFixed(2)}});if(o.length===0){u.error(`没有有效的文件可上传`);return}await a.create(t,n);let s=p(n.upload.concurrency??5);await Promise.all(o.map(e=>s(async()=>{try{(await a.uploadFile(e)).status===`skipped`?u.info(` 跳过 ${e.fileName} (远端已存在)`):u.success(` 完成 ${e.fileName} (${e.sizeKB} KB)`)}catch(t){u.error(` 失败 ${e.fileName} 上传失败:`,t.message)}}))),a.finalize()}const B=i.dirname((()=>{try{return r.realpathSync(s(import.meta.url))}catch{return s(import.meta.url)}})()),V=JSON.parse(r.readFileSync(i.resolve(B,`../package.json`),`utf-8`)),H=new o(`charbi`);H.version(V.version),H.option(`--mode <mode>`,`development 或 production`),H.command(``).action(U),H.command(`build`).option(`--no-cache`,`强制重新下载字体文件`).action(W),H.command(`upload`).action(G);async function U(e,t){await W({cache:!0},t),await G(void 0,t)}async function W(e,o){let s=o?.mode||`development`,c=await n(s),l=t(c.version);u.info(`charbi`),u.info(` 模式: ${s}`),u.info(` 版本基线: ${l}`),u.info(` 格式: ${c.output.format}`);let d=i.join(c.cacheDir,`subsets`);r.mkdirSync(d,{recursive:!0});for(let e of r.readdirSync(d))r.unlinkSync(i.join(d,e));let f=await g(c.cacheDir,c.fonts,!e.cache);f.size===0&&(u.error(`没有可用的字体文件,构建失败`),a.exit(1));let p=await P(f,d,await j(c),c.fonts,c.output.format),m=0,h=0;for(let e of p.values())for(let t of e)m+=t.size,h++;let _=(m/1024).toFixed(2);u.success(`字体子集生成完成!`),u.info(` 生成文件: ${h} 个`),u.info(` 总大小: ${_} KB`),u.info(` 输出目录: ${d}`);let v=l;u.info(` 字体版本: ${v}`),await S(p,c,v,c.output.format)}async function G(e,o){let s=o?.mode||`development`,c=await n(s),l=t(c.version);u.info(`charbi upload`),u.info(` 模式: ${s}`),u.info(` 版本: ${l}`);let d=i.join(c.cacheDir,`subsets`);r.existsSync(d)||(u.error(`没有找到字体文件目录: ${d}`),u.error(`请先执行 build 命令`),a.exit(1));let f=[];for(let e of r.readdirSync(d)){let t=i.join(d,e);r.statSync(t).isFile()&&f.push(t)}f.length===0&&(u.error(`没有找到字体文件,请先执行 build`),a.exit(1)),await z(f,l,c)}H.help(),H.parse();export{};
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../chunk-Bmb41Sf3.cjs`),t=require(`../schema-ClqJnGnv.cjs`);let n=require(`node:fs`);n=e.t(n);let r=require(`node:path`);r=e.t(r);let i=require(`node:process`);i=e.t(i);let a=require(`node:url`),o=require(`defu`),s=require(`unconfig`),c=require(`node:util`);const l=(0,o.createDefu)((e,t,n)=>{if(Array.isArray(e[t])&&Array.isArray(n))return e[t]=n,!0});function u(){let e=(0,a.fileURLToPath)(require(`url`).pathToFileURL(__filename).href),t=r.default.dirname(e);try{return n.default.realpathSync(t)}catch{return t}}const d=u();function f(){return i.default.cwd()}function p(e){if(e)return e;let t=r.default.join(f(),`package.json`);return JSON.parse(n.default.readFileSync(t,`utf-8`)).version||`0.0.1`}function m(e){return e?r.default.isAbsolute(e)?e:r.default.join(f(),e):r.default.join(d,`../../.cache/fonts`)}function h(e,t){let a=t?.[e]||{development:`.env.development`,production:`.env.production`}[e],o=r.default.join(f(),a);if(n.default.existsSync(o)){let e=(0,c.parseEnv)(n.default.readFileSync(o,`utf-8`));for(let[t,n]of Object.entries(e))i.default.env[t]=n}}async function g(e=`development`){let n=(await(0,s.loadConfig)({sources:[{files:`fonts.config`,extensions:[`ts`,`mts`,`cts`,`js`,`mjs`,`cjs`,`json`,`json5`]}],cwd:f(),defaults:{}})).config||{};h(e,n.build?.env);let r=l(n.build||{},t.n);if(!r.fonts||r.fonts.length===0)throw Error(`未配置字体,请在 fonts.config.ts 的 build.fonts 中添加字体配置`);return{scan:r.scan,fonts:r.fonts,output:r.output,upload:{provider:n.upload?.provider||`cos`,concurrency:n.upload?.concurrency??5},cos:n.cos||{},root:f(),cacheDir:m(n.build?.cacheDir),version:r.version,env:r.env||{},mode:e}}exports.getCacheDir=m,exports.getProjectRoot=f,exports.getVersion=p,exports.loadConfig=g,exports.loadEnvFile=h;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../chunk-Bmb41Sf3.cjs`),t=require(`../schema-ClqJnGnv.cjs`);let n=require(`node:fs`);n=e.t(n);let r=require(`node:path`);r=e.t(r);let i=require(`node:process`);i=e.t(i);let a=require(`defu`),o=require(`unconfig`),s=require(`node:util`);const c=(0,a.createDefu)((e,t,n)=>{if(Array.isArray(e[t])&&Array.isArray(n))return e[t]=n,!0});function l(){return i.default.cwd()}function u(e){if(e)return e;let t=r.default.join(l(),`package.json`);return JSON.parse(n.default.readFileSync(t,`utf-8`)).version||`0.0.1`}function d(e){if(e)return r.default.isAbsolute(e)?e:r.default.join(l(),e);let t=l(),n=r.default.join(t,`node_modules`,`charbi-font`);return r.default.join(n,`.cache/fonts`)}function f(e,t){let a=t?.[e]||{development:`.env.development`,production:`.env.production`}[e],o=r.default.join(l(),a);if(n.default.existsSync(o)){let e=(0,s.parseEnv)(n.default.readFileSync(o,`utf-8`));for(let[t,n]of Object.entries(e))i.default.env[t]=n}}async function p(e=`development`){let n=(await(0,o.loadConfig)({sources:[{files:`fonts.config`,extensions:[`ts`,`mts`,`cts`,`js`,`mjs`,`cjs`,`json`,`json5`]}],cwd:l(),defaults:{}})).config||{};f(e,n.build?.env);let r=c(n.build||{},t.n);if(!r.fonts||r.fonts.length===0)throw Error(`未配置字体,请在 fonts.config.ts 的 build.fonts 中添加字体配置`);return{scan:r.scan,fonts:r.fonts,output:r.output,upload:{provider:n.upload?.provider||`cos`,concurrency:n.upload?.concurrency??5},cos:n.cos||{},root:l(),cacheDir:d(n.build?.cacheDir),version:r.version,env:r.env||{},mode:e}}exports.getCacheDir=d,exports.getProjectRoot=l,exports.getVersion=u,exports.loadConfig=p,exports.loadEnvFile=f;
@@ -1 +1 @@
1
- import{n as e}from"../schema-BuMY-k7u.mjs";import t from"node:fs";import n from"node:path";import r from"node:process";import{fileURLToPath as i}from"node:url";import{createDefu as a}from"defu";import{loadConfig as o}from"unconfig";import{parseEnv as s}from"node:util";const c=a((e,t,n)=>{if(Array.isArray(e[t])&&Array.isArray(n))return e[t]=n,!0});function l(){let e=i(import.meta.url),r=n.dirname(e);try{return t.realpathSync(r)}catch{return r}}const u=l();function d(){return r.cwd()}function f(e){if(e)return e;let r=n.join(d(),`package.json`);return JSON.parse(t.readFileSync(r,`utf-8`)).version||`0.0.1`}function p(e){return e?n.isAbsolute(e)?e:n.join(d(),e):n.join(u,`../../.cache/fonts`)}function m(e,i){let a=i?.[e]||{development:`.env.development`,production:`.env.production`}[e],o=n.join(d(),a);if(t.existsSync(o)){let e=s(t.readFileSync(o,`utf-8`));for(let[t,n]of Object.entries(e))r.env[t]=n}}async function h(t=`development`){let n=(await o({sources:[{files:`fonts.config`,extensions:[`ts`,`mts`,`cts`,`js`,`mjs`,`cjs`,`json`,`json5`]}],cwd:d(),defaults:{}})).config||{};m(t,n.build?.env);let r=c(n.build||{},e);if(!r.fonts||r.fonts.length===0)throw Error(`未配置字体,请在 fonts.config.ts 的 build.fonts 中添加字体配置`);return{scan:r.scan,fonts:r.fonts,output:r.output,upload:{provider:n.upload?.provider||`cos`,concurrency:n.upload?.concurrency??5},cos:n.cos||{},root:d(),cacheDir:p(n.build?.cacheDir),version:r.version,env:r.env||{},mode:t}}export{p as getCacheDir,d as getProjectRoot,f as getVersion,h as loadConfig,m as loadEnvFile};
1
+ import{n as e}from"../schema-BuMY-k7u.mjs";import t from"node:fs";import n from"node:path";import r from"node:process";import{createDefu as i}from"defu";import{loadConfig as a}from"unconfig";import{parseEnv as o}from"node:util";const s=i((e,t,n)=>{if(Array.isArray(e[t])&&Array.isArray(n))return e[t]=n,!0});function c(){return r.cwd()}function l(e){if(e)return e;let r=n.join(c(),`package.json`);return JSON.parse(t.readFileSync(r,`utf-8`)).version||`0.0.1`}function u(e){if(e)return n.isAbsolute(e)?e:n.join(c(),e);let t=c(),r=n.join(t,`node_modules`,`charbi-font`);return n.join(r,`.cache/fonts`)}function d(e,i){let a=i?.[e]||{development:`.env.development`,production:`.env.production`}[e],s=n.join(c(),a);if(t.existsSync(s)){let e=o(t.readFileSync(s,`utf-8`));for(let[t,n]of Object.entries(e))r.env[t]=n}}async function f(t=`development`){let n=(await a({sources:[{files:`fonts.config`,extensions:[`ts`,`mts`,`cts`,`js`,`mjs`,`cjs`,`json`,`json5`]}],cwd:c(),defaults:{}})).config||{};d(t,n.build?.env);let r=s(n.build||{},e);if(!r.fonts||r.fonts.length===0)throw Error(`未配置字体,请在 fonts.config.ts 的 build.fonts 中添加字体配置`);return{scan:r.scan,fonts:r.fonts,output:r.output,upload:{provider:n.upload?.provider||`cos`,concurrency:n.upload?.concurrency??5},cos:n.cos||{},root:c(),cacheDir:u(n.build?.cacheDir),version:r.version,env:r.env||{},mode:t}}export{u as getCacheDir,c as getProjectRoot,l as getVersion,f as loadConfig,d as loadEnvFile};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "charbi-font",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "中文字体子集化工具,扫描代码提取字符,生成精简字体包",
5
5
  "keywords": [
6
6
  "builder",