unplugin-version-injector 2.2.0 → 2.2.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/README.md CHANGED
@@ -17,6 +17,8 @@
17
17
  ✅ Supports Vite, Webpack 4/5, Rspack, Rollup, Rolldown
18
18
  ✅ Fully compatible with Multi-Page Applications (MPA)
19
19
  ✅ Customizable version, project name, date format, and theme-based console styling
20
+ ✅ Date format supports dayjs-style patterns (`YYYY-MM-DD HH:mm:ss`) with zero extra dependencies
21
+ ✅ Optional: attach a version header (`X-Client-Version`) to `fetch` / `XMLHttpRequest` requests to identify clients in backend logs
20
22
 
21
23
  ---
22
24
 
@@ -125,47 +127,148 @@ In your final HTML output:
125
127
  | `version` | `string` | Custom version number | Read from package.json |
126
128
  | `name` | `string` | Custom project name | Read from package.json |
127
129
  | `log` | `boolean` | Whether to inject the console log script | `true` |
128
- | `formatDate` | `(date: Date) => string` | Custom build time formatter | ISO 8601 format |
130
+ | `formatDate` | `string \| ((date: Date) => string)` | Custom build time format: a dayjs-style pattern (e.g. `'YYYY-MM-DD HH:mm:ss'`) or a function | ISO 8601 format |
129
131
  | `requestHeaders` | `boolean \| RequestHeadersOptions` | Attach version/build-time headers to outgoing requests | `false` |
130
132
 
131
133
  > `version` and `name` can be provided independently — whichever is missing falls back to the nearest `package.json`.
132
134
 
133
135
  ---
134
136
 
137
+ ## 📅 Formatting build time (`formatDate`)
138
+
139
+ `formatDate` accepts two forms, and applies to both the **console banner** and the **`X-Client-Build-Time` header**:
140
+
141
+ ```ts
142
+ // 1) dayjs-style pattern (built-in lightweight impl, no dayjs needed)
143
+ versionInjector({ formatDate: 'YYYY-MM-DD HH:mm:ss' }); // 2024-04-01 12:30:45
144
+
145
+ // 2) custom function
146
+ versionInjector({ formatDate: (date) => date.getTime().toString() }); // timestamp
147
+ ```
148
+
149
+ Supported tokens: `YYYY YY MMMM MMM MM M DD D dddd ddd dd d HH H hh h mm m ss s SSS SS S A a`.
150
+
151
+ ---
152
+
135
153
  ## 📡 Request Headers (identify clients in backend logs)
136
154
 
137
- Enable `requestHeaders` to patch `window.fetch` and `XMLHttpRequest` so every API request carries the client version — making it trivial to tell which client build produced a request in backend/API logs:
155
+ Enable `requestHeaders` to patch `window.fetch` and `XMLHttpRequest` so requests carry the client version and build time — making it trivial to tell which client build produced a request in backend/API logs. Two headers are injected by default:
156
+
157
+ ```
158
+ X-Client-Version: my-app/1.2.3
159
+ X-Client-Build-Time: 2024-04-01 12:00:00
160
+ ```
161
+
162
+ > Because it patches `fetch` / `XMLHttpRequest`, **axios and most HTTP clients work automatically**. `navigator.sendBeacon` and WebSocket connections cannot carry custom headers and are not covered.
163
+
164
+ ### `RequestHeadersOptions`
165
+
166
+ | Option | Type | Description | Default |
167
+ |---|---|---|---|
168
+ | `versionHeaderName` | `string` | Version header name; value is `${name}/${version}` | `'X-Client-Version'` |
169
+ | `buildTimeHeaderName` | `string` | Build-time header name; value is the `formatDate` output | `'X-Client-Build-Time'` |
170
+ | `include` | `(string \| RegExp)[]` | Extra **cross-origin** allowlist: string = URL prefix, RegExp = full-URL test. **Same-origin requests are always injected** | `[]` |
171
+
172
+ ### Scenario 1: same-origin (simplest)
173
+
174
+ Page and API share an origin, or you use a dev-server proxy (requests hit `/api`, which the browser treats as same-origin):
175
+
176
+ ```ts
177
+ versionInjector({ requestHeaders: true }); // true = defaults, same-origin only
178
+ ```
179
+
180
+ ### Scenario 2: custom header names
138
181
 
139
182
  ```ts
140
183
  versionInjector({
141
- requestHeaders: true, // same-origin requests only
184
+ requestHeaders: {
185
+ versionHeaderName: 'X-App-Version',
186
+ buildTimeHeaderName: 'X-App-Build',
187
+ },
142
188
  });
143
189
  ```
144
190
 
145
- Every same-origin request then includes:
191
+ ### Scenario 3: a single cross-origin API (most common in prod)
146
192
 
193
+ Front end and API are on different origins — add the API origin to `include` (string = URL prefix):
194
+
195
+ ```ts
196
+ versionInjector({
197
+ requestHeaders: { include: ['https://api.example.com'] },
198
+ });
147
199
  ```
148
- X-Client-Version: my-app/1.2.3
149
- X-Client-Build-Time: 2024-04-01T12:00:00.000Z
200
+
201
+ ### Scenario 4: multiple cross-origin APIs / RegExp
202
+
203
+ ```ts
204
+ versionInjector({
205
+ requestHeaders: {
206
+ include: [
207
+ 'https://api.example.com',
208
+ 'https://auth.example.com',
209
+ /^https:\/\/[^/]*\.example\.com\//, // any *.example.com subdomain
210
+ ],
211
+ },
212
+ });
150
213
  ```
151
214
 
152
- Full configuration:
215
+ ### Scenario 5: Monorepo + all cross-origin (the trickiest) ⭐
216
+
217
+ Many packages share one build config, and each app talks to cross-origin APIs that differ per environment (dev / sandbox / prod). Two keys:
218
+
219
+ **1. Write `include` once in the shared root config** — every package inherits it (put the plugin in the shared `configureWebpack` / `vite` config).
220
+
221
+ **2. Don't hardcode origins — build them from env vars.** Each app's `.env.*` usually already defines its API origins:
222
+
223
+ ```js
224
+ // shared root build config (webpack example)
225
+ const versionInjector = require('unplugin-version-injector/webpack');
226
+
227
+ // provided by each app / each environment's .env
228
+ const apiOrigins = [
229
+ process.env.VUE_APP_API_ORIGIN,
230
+ process.env.VUE_APP_SDK_API_ORIGIN,
231
+ process.env.VUE_APP_USER_API_ORIGIN,
232
+ ].filter(Boolean);
233
+
234
+ module.exports = {
235
+ configureWebpack: {
236
+ plugins: [
237
+ versionInjector({ requestHeaders: { include: apiOrigins } }),
238
+ ],
239
+ },
240
+ };
241
+ ```
242
+
243
+ dev / sandbox / prod each get the right origins automatically — no giant hardcoded list.
244
+
245
+ If all APIs live under a few fixed base domains, a single RegExp also works (new subdomains match automatically):
153
246
 
154
247
  ```ts
155
248
  versionInjector({
156
249
  requestHeaders: {
157
- versionHeaderName: 'X-Client-Version', // default
158
- buildTimeHeaderName: 'X-Client-Build-Time', // default
159
- // Cross-origin URLs to include (string = URL prefix, RegExp = full URL test).
160
- // Same-origin requests are always included.
161
- include: ['https://api.example.com/', /\.internal\.example\.com/],
250
+ include: [/^https:\/\/[^/]*\.(example\.io|example\.dev|sandbox-example\.com)\//],
162
251
  },
163
252
  });
164
253
  ```
165
254
 
166
- > ⚠️ **CORS**: custom headers on cross-origin requests trigger a preflight the server must allow them via `Access-Control-Allow-Headers`. That's why cross-origin injection is opt-in through `include`.
167
- >
168
- > Note: `navigator.sendBeacon` and WebSocket connections cannot carry custom headers; the patch covers `fetch` and `XMLHttpRequest` (which includes axios and most HTTP clients).
255
+ > Only include API origins you actually `fetch`/`XHR` **and** whose CORS you control. Don't add CDNs or third-party SDK script hosts that only triggers preflights and can break asset loading.
256
+
257
+ ### ⚠️ Cross-origin requires backend cooperation (CORS preflight)
258
+
259
+ Custom headers on cross-origin requests trigger an `OPTIONS` preflight. **Every** API service matched by `include` must allow the two headers, or the browser blocks the request:
260
+
261
+ ```
262
+ Access-Control-Allow-Headers: X-Client-Version, X-Client-Build-Time
263
+ ```
264
+
265
+ This is exactly why cross-origin injection is opt-in via `include` rather than on by default.
266
+
267
+ ### 🔍 Same-origin or cross-origin?
268
+
269
+ Open the browser Network tab and look at the request's **real URL**:
270
+ - `http://localhost:9040/api/...` (via dev proxy) → **same-origin**, `requestHeaders: true` is enough, no `include`.
271
+ - `https://api.xxx.com/...` (direct) → **cross-origin**, must be in `include` + backend must allow the headers.
169
272
 
170
273
  ---
171
274
 
package/README.zh-CN.md CHANGED
@@ -16,6 +16,8 @@
16
16
  ✅ 完美兼容多页面应用(MPA)
17
17
  ✅ 支持自定义版本号、项目名、时间格式,默认读取 `package.json`
18
18
  ✅ 控制台输出支持自动适配深/浅主题配色
19
+ ✅ 时间格式支持 dayjs 风格字符串(`YYYY-MM-DD HH:mm:ss`),零额外依赖
20
+ ✅ 可选:给 `fetch` / `XMLHttpRequest` 请求自动注入版本请求头(`X-Client-Version`),在后端日志中定位客户端版本
19
21
 
20
22
  ---
21
23
 
@@ -131,46 +133,148 @@ export default {
131
133
  | `version` | `string` | 自定义版本号 | 自动读取 package.json |
132
134
  | `name` | `string` | 自定义项目名 | 自动读取 package.json |
133
135
  | `log` | `boolean` | 是否输出控制台日志 | `true` |
134
- | `formatDate` | `(date: Date) => string` | 自定义时间格式函数 | ISO 格式 |
136
+ | `formatDate` | `string \| ((date: Date) => string)` | 自定义构建时间格式:支持 dayjs 风格字符串(如 `'YYYY-MM-DD HH:mm:ss'`)或函数 | ISO 格式 |
135
137
  | `requestHeaders` | `boolean \| RequestHeadersOptions` | 给发出的请求自动附加版本/构建时间请求头 | `false` |
136
138
 
137
139
  > `version` 和 `name` 可以单独传入,缺失的一项会自动从最近的 `package.json` 读取。
138
140
 
139
141
  ---
140
142
 
141
- ## **📡 请求头注入(后端日志定位客户端版本)**
143
+ ## **📅 构建时间格式化 `formatDate`**
142
144
 
143
- 开启 `requestHeaders` 后,插件会在页面最前面 patch `window.fetch` 和 `XMLHttpRequest`,让所有 API 请求自动带上版本信息——排查前后端请求日志时可以直接看出是哪个客户端、哪个版本发出的请求:
145
+ `formatDate` 支持两种写法,同时作用于**控制台构建时间**和 **`X-Client-Build-Time` 请求头**:
146
+
147
+ ```ts
148
+ // 1) dayjs 风格字符串(内置轻量实现,无需安装 dayjs)
149
+ versionInjector({ formatDate: 'YYYY-MM-DD HH:mm:ss' }); // 2024-04-01 12:30:45
150
+
151
+ // 2) 自定义函数
152
+ versionInjector({ formatDate: (date) => date.getTime().toString() }); // 时间戳
153
+ ```
154
+
155
+ 支持的 token:`YYYY YY MMMM MMM MM M DD D dddd ddd dd d HH H hh h mm m ss s SSS SS S A a`。
156
+
157
+ ---
158
+
159
+ ## **📡 请求头注入(在后端日志中定位客户端版本)**
160
+
161
+ 开启 `requestHeaders` 后,插件会在页面最前面 patch `window.fetch` 和 `XMLHttpRequest`,让请求自动带上版本与构建时间——排查前后端日志时,一眼就能看出是哪个客户端、哪个版本发出的请求。默认注入两个头:
162
+
163
+ ```
164
+ X-Client-Version: my-app/1.2.3
165
+ X-Client-Build-Time: 2024-04-01 12:00:00
166
+ ```
167
+
168
+ > 底层是 patch `fetch` / `XMLHttpRequest`,所以 **axios、umi-request 等主流库都自动生效**;`navigator.sendBeacon` 和 WebSocket 无法携带自定义头,不在覆盖范围。
169
+
170
+ ### 子配置 `RequestHeadersOptions`
171
+
172
+ | 选项 | 类型 | 说明 | 默认值 |
173
+ |---|---|---|---|
174
+ | `versionHeaderName` | `string` | 版本头名称,值为 `${name}/${version}` | `'X-Client-Version'` |
175
+ | `buildTimeHeaderName` | `string` | 构建时间头名称,值为 `formatDate` 的输出 | `'X-Client-Build-Time'` |
176
+ | `include` | `(string \| RegExp)[]` | 额外注入的**跨域**地址白名单:字符串按 URL 前缀匹配,正则按完整 URL 测试。**同源请求始终注入** | `[]` |
177
+
178
+ ### 场景 1:同源请求(最简单)
179
+
180
+ 页面与 API 同域,或本地走 dev-server 代理(请求发到 `/api`,浏览器视角是同源):
181
+
182
+ ```ts
183
+ versionInjector({ requestHeaders: true }); // true = 默认配置,仅同源
184
+ ```
185
+
186
+ ### 场景 2:自定义请求头名称
144
187
 
145
188
  ```ts
146
189
  versionInjector({
147
- requestHeaders: true, // 默认仅同源请求
190
+ requestHeaders: {
191
+ versionHeaderName: 'X-App-Version',
192
+ buildTimeHeaderName: 'X-App-Build',
193
+ },
148
194
  });
149
195
  ```
150
196
 
151
- 之后每个同源请求都会带上:
197
+ ### 场景 3:单个跨域 API(生产最常见)
152
198
 
199
+ 前端和 API 不同源,把 API 域名加进 `include`(字符串 = URL 前缀):
200
+
201
+ ```ts
202
+ versionInjector({
203
+ requestHeaders: { include: ['https://api.example.com'] },
204
+ });
153
205
  ```
154
- X-Client-Version: my-app/1.2.3
155
- X-Client-Build-Time: 2024-04-01T12:00:00.000Z
206
+
207
+ ### 场景 4:多个跨域 API / 正则批量匹配
208
+
209
+ ```ts
210
+ versionInjector({
211
+ requestHeaders: {
212
+ include: [
213
+ 'https://api.example.com',
214
+ 'https://auth.example.com',
215
+ /^https:\/\/[^/]*\.example\.com\//, // 匹配 *.example.com 所有子域
216
+ ],
217
+ },
218
+ });
156
219
  ```
157
220
 
158
- 完整配置:
221
+ ### 场景 5:Monorepo + 全跨域(最容易踩坑)⭐
222
+
223
+ 多包共用一份构建配置、每个子应用又连不同环境(dev / sandbox / prod)的跨域 API——这是最难配的场景。两个关键点:
224
+
225
+ **① `include` 只需在共享的根配置里写一次**,所有子包继承即可(把插件放进共享的 `configureWebpack` / `vite` 配置)。
226
+
227
+ **② 域名随环境变化,别硬编码——用环境变量动态拼**。每个子应用的 `.env.*` 里通常已有 API 域名变量,直接读:
228
+
229
+ ```js
230
+ // 共享的根构建配置(以 webpack 为例)
231
+ const versionInjector = require('unplugin-version-injector/webpack');
232
+
233
+ // 这些变量由各子应用 / 各环境的 .env 提供
234
+ const apiOrigins = [
235
+ process.env.VUE_APP_API_ORIGIN,
236
+ process.env.VUE_APP_SDK_API_ORIGIN,
237
+ process.env.VUE_APP_USER_API_ORIGIN,
238
+ ].filter(Boolean); // 去掉未定义的
239
+
240
+ module.exports = {
241
+ configureWebpack: {
242
+ plugins: [
243
+ versionInjector({ requestHeaders: { include: apiOrigins } }),
244
+ ],
245
+ },
246
+ };
247
+ ```
248
+
249
+ 这样 dev / sandbox / prod 各自带对应域名,不用维护一份大清单。
250
+
251
+ 若所有 API 都在固定的几个主域名下,也可以直接用一条正则(新增子域自动命中):
159
252
 
160
253
  ```ts
161
254
  versionInjector({
162
255
  requestHeaders: {
163
- versionHeaderName: 'X-Client-Version', // 默认值
164
- buildTimeHeaderName: 'X-Client-Build-Time', // 默认值
165
- // 跨域白名单:字符串按 URL 前缀匹配,正则按完整 URL 测试;同源请求始终注入
166
- include: ['https://api.example.com/', /\.internal\.example\.com/],
256
+ include: [/^https:\/\/[^/]*\.(example\.io|example\.dev|sandbox-example\.com)\//],
167
257
  },
168
258
  });
169
259
  ```
170
260
 
171
- > ⚠️ **CORS 注意**:给跨域请求加自定义头会触发预检(preflight),服务端必须在 `Access-Control-Allow-Headers` 中放行这两个头,否则请求会失败——所以跨域注入设计为通过 `include` 显式开启。
172
- >
173
- > 说明:`navigator.sendBeacon` WebSocket 无法携带自定义请求头;补丁覆盖 `fetch` 与 `XMLHttpRequest`(axios 等主流 HTTP 客户端底层都走这两条路)。
261
+ > 只把**你自己发 fetch/XHR、且能改 CORS API 域名**放进去。CDN、第三方 SDK 脚本域(你控制不了 CORS)不要加,否则只会触发预检导致资源加载失败。
262
+
263
+ ### ⚠️ 跨域必读:后端要放行(CORS 预检)
264
+
265
+ 给跨域请求加自定义头,浏览器会先发一个 `OPTIONS` 预检。`include` 命中的**每一个** API 服务都必须在响应里放行这两个头,否则请求会被浏览器拦掉:
266
+
267
+ ```
268
+ Access-Control-Allow-Headers: X-Client-Version, X-Client-Build-Time
269
+ ```
270
+
271
+ 多后端场景要逐个确认。这也是跨域注入必须通过 `include` 显式开启、而非默认全开的原因。
272
+
273
+ ### 🔍 怎么判断请求是同源还是跨域?
274
+
275
+ 打开浏览器 Network,看请求的**真实 URL**:
276
+ - `http://localhost:9040/api/...`(走 dev 代理)→ **同源**,`requestHeaders: true` 就够,不用 `include`;
277
+ - `https://api.xxx.com/...`(直连)→ **跨域**,必须加进 `include` + 后端放行。
174
278
 
175
279
  ---
176
280
 
package/dist/index.js CHANGED
@@ -102,18 +102,27 @@ function toScriptString(value) {
102
102
  var INJECTED_MARK = 'data-injected="unplugin-version-injector"';
103
103
  var HEADERS_INJECTED_MARK = 'data-injected="unplugin-version-injector-headers"';
104
104
  var HEADER_NAME_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
105
+ var DEFAULT_VERSION_HEADER = "X-Client-Version";
106
+ var DEFAULT_BUILD_TIME_HEADER = "X-Client-Build-Time";
105
107
  function normalizeRequestHeaders(opt) {
106
- var _a, _b, _c;
107
108
  if (!opt) return null;
108
109
  const o = typeof opt === "object" ? opt : {};
109
- const versionHeaderName = (_a = o.versionHeaderName) != null ? _a : "X-Client-Version";
110
- const buildTimeHeaderName = (_b = o.buildTimeHeaderName) != null ? _b : "X-Client-Build-Time";
111
- for (const h of [versionHeaderName, buildTimeHeaderName]) {
112
- if (!HEADER_NAME_RE.test(h)) {
113
- throw new Error(`[VersionInjector] invalid request header name: "${h}"`);
114
- }
115
- }
116
- return { versionHeaderName, buildTimeHeaderName, include: (_c = o.include) != null ? _c : [] };
110
+ const pickHeaderName = (value, fallback) => {
111
+ if (value == null) return fallback;
112
+ if (HEADER_NAME_RE.test(value)) return value;
113
+ console.warn(
114
+ `[VersionInjector] invalid request header name "${value}", falling back to "${fallback}"`
115
+ );
116
+ return fallback;
117
+ };
118
+ const include = (Array.isArray(o.include) ? o.include : []).filter(
119
+ (p) => typeof p === "string" || p instanceof RegExp
120
+ );
121
+ return {
122
+ versionHeaderName: pickHeaderName(o.versionHeaderName, DEFAULT_VERSION_HEADER),
123
+ buildTimeHeaderName: pickHeaderName(o.buildTimeHeaderName, DEFAULT_BUILD_TIME_HEADER),
124
+ include
125
+ };
117
126
  }
118
127
  function sanitizeHeaderValue(value) {
119
128
  return value.replace(/[^\t\x20-\x7E]/g, "").trim();
@@ -137,11 +146,13 @@ function createVersionInjector(options = {}) {
137
146
  const buildLogScript = (buildTime) => `
138
147
  <script ${INJECTED_MARK}>
139
148
  (function () {
140
- var isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
141
- var bg = isDark ? '#ffffff' : '#1e1e1e';
142
- var base = 'background: ' + bg + '; border-radius: 4px; padding: 4px; font-size: 12px;';
143
- console.log('%c' + ${toScriptString(` ${name}@${version} `)}, base + ' color: #00c853;');
144
- console.log('%c' + ${toScriptString(` Build Time: ${buildTime} `)}, base + ' color: #ffab00;');
149
+ try {
150
+ var isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
151
+ var bg = isDark ? '#ffffff' : '#1e1e1e';
152
+ var base = 'background: ' + bg + '; border-radius: 4px; padding: 4px; font-size: 12px;';
153
+ console.log('%c' + ${toScriptString(` ${name}@${version} `)}, base + ' color: #00c853;');
154
+ console.log('%c' + ${toScriptString(` Build Time: ${buildTime} `)}, base + ' color: #ffab00;');
155
+ } catch (e) {}
145
156
  })();
146
157
  </script>`;
147
158
  const buildHeaderScript = (buildTime, cfg) => {
@@ -150,6 +161,7 @@ function createVersionInjector(options = {}) {
150
161
  return `
151
162
  <script ${HEADERS_INJECTED_MARK}>
152
163
  (function () {
164
+ try {
153
165
  if (window.__UVI_HEADERS_PATCHED__) return;
154
166
  window.__UVI_HEADERS_PATCHED__ = true;
155
167
  var VERSION_HEADER = ${toScriptString(cfg.versionHeaderName)};
@@ -228,27 +240,34 @@ function createVersionInjector(options = {}) {
228
240
  };
229
241
  }
230
242
  }
243
+ } catch (e) {}
231
244
  })();
232
245
  </script>`;
233
246
  };
234
247
  const processHtml = function processHtml2(html) {
235
- const buildTime = formatDate2(/* @__PURE__ */ new Date());
236
- if (!html.includes('<meta name="version"')) {
237
- html = html.replace(/<head[^>]*>/i, (match) => `${match}
248
+ const original = html;
249
+ try {
250
+ const buildTime = formatDate2(/* @__PURE__ */ new Date());
251
+ if (!html.includes('<meta name="version"')) {
252
+ html = html.replace(/<head[^>]*>/i, (match) => `${match}
238
253
  ${metaTag}`);
239
- }
240
- if (headersConfig && !html.includes(HEADERS_INJECTED_MARK)) {
241
- const headerScript = buildHeaderScript(buildTime, headersConfig);
242
- html = html.replace(/<head[^>]*>/i, (match) => `${match}
254
+ }
255
+ if (headersConfig && !html.includes(HEADERS_INJECTED_MARK)) {
256
+ const headerScript = buildHeaderScript(buildTime, headersConfig);
257
+ html = html.replace(/<head[^>]*>/i, (match) => `${match}
243
258
  ${headerScript}
244
259
  `);
245
- }
246
- if (options.log !== false && !html.includes(INJECTED_MARK)) {
247
- const logScript = buildLogScript(buildTime);
248
- html = html.replace(/<\/body>/i, () => ` ${logScript}
260
+ }
261
+ if (options.log !== false && !html.includes(INJECTED_MARK)) {
262
+ const logScript = buildLogScript(buildTime);
263
+ html = html.replace(/<\/body>/i, () => ` ${logScript}
249
264
  </body>`);
265
+ }
266
+ return html;
267
+ } catch (err) {
268
+ console.warn("[VersionInjector] injection failed, returning original HTML unchanged:", err);
269
+ return original;
250
270
  }
251
- return html;
252
271
  };
253
272
  processHtml.resetBuildTime = () => {
254
273
  };
package/dist/index.mjs CHANGED
@@ -93,18 +93,27 @@ function toScriptString(value) {
93
93
  var INJECTED_MARK = 'data-injected="unplugin-version-injector"';
94
94
  var HEADERS_INJECTED_MARK = 'data-injected="unplugin-version-injector-headers"';
95
95
  var HEADER_NAME_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
96
+ var DEFAULT_VERSION_HEADER = "X-Client-Version";
97
+ var DEFAULT_BUILD_TIME_HEADER = "X-Client-Build-Time";
96
98
  function normalizeRequestHeaders(opt) {
97
- var _a, _b, _c;
98
99
  if (!opt) return null;
99
100
  const o = typeof opt === "object" ? opt : {};
100
- const versionHeaderName = (_a = o.versionHeaderName) != null ? _a : "X-Client-Version";
101
- const buildTimeHeaderName = (_b = o.buildTimeHeaderName) != null ? _b : "X-Client-Build-Time";
102
- for (const h of [versionHeaderName, buildTimeHeaderName]) {
103
- if (!HEADER_NAME_RE.test(h)) {
104
- throw new Error(`[VersionInjector] invalid request header name: "${h}"`);
105
- }
106
- }
107
- return { versionHeaderName, buildTimeHeaderName, include: (_c = o.include) != null ? _c : [] };
101
+ const pickHeaderName = (value, fallback) => {
102
+ if (value == null) return fallback;
103
+ if (HEADER_NAME_RE.test(value)) return value;
104
+ console.warn(
105
+ `[VersionInjector] invalid request header name "${value}", falling back to "${fallback}"`
106
+ );
107
+ return fallback;
108
+ };
109
+ const include = (Array.isArray(o.include) ? o.include : []).filter(
110
+ (p) => typeof p === "string" || p instanceof RegExp
111
+ );
112
+ return {
113
+ versionHeaderName: pickHeaderName(o.versionHeaderName, DEFAULT_VERSION_HEADER),
114
+ buildTimeHeaderName: pickHeaderName(o.buildTimeHeaderName, DEFAULT_BUILD_TIME_HEADER),
115
+ include
116
+ };
108
117
  }
109
118
  function sanitizeHeaderValue(value) {
110
119
  return value.replace(/[^\t\x20-\x7E]/g, "").trim();
@@ -128,11 +137,13 @@ function createVersionInjector(options = {}) {
128
137
  const buildLogScript = (buildTime) => `
129
138
  <script ${INJECTED_MARK}>
130
139
  (function () {
131
- var isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
132
- var bg = isDark ? '#ffffff' : '#1e1e1e';
133
- var base = 'background: ' + bg + '; border-radius: 4px; padding: 4px; font-size: 12px;';
134
- console.log('%c' + ${toScriptString(` ${name}@${version} `)}, base + ' color: #00c853;');
135
- console.log('%c' + ${toScriptString(` Build Time: ${buildTime} `)}, base + ' color: #ffab00;');
140
+ try {
141
+ var isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
142
+ var bg = isDark ? '#ffffff' : '#1e1e1e';
143
+ var base = 'background: ' + bg + '; border-radius: 4px; padding: 4px; font-size: 12px;';
144
+ console.log('%c' + ${toScriptString(` ${name}@${version} `)}, base + ' color: #00c853;');
145
+ console.log('%c' + ${toScriptString(` Build Time: ${buildTime} `)}, base + ' color: #ffab00;');
146
+ } catch (e) {}
136
147
  })();
137
148
  </script>`;
138
149
  const buildHeaderScript = (buildTime, cfg) => {
@@ -141,6 +152,7 @@ function createVersionInjector(options = {}) {
141
152
  return `
142
153
  <script ${HEADERS_INJECTED_MARK}>
143
154
  (function () {
155
+ try {
144
156
  if (window.__UVI_HEADERS_PATCHED__) return;
145
157
  window.__UVI_HEADERS_PATCHED__ = true;
146
158
  var VERSION_HEADER = ${toScriptString(cfg.versionHeaderName)};
@@ -219,27 +231,34 @@ function createVersionInjector(options = {}) {
219
231
  };
220
232
  }
221
233
  }
234
+ } catch (e) {}
222
235
  })();
223
236
  </script>`;
224
237
  };
225
238
  const processHtml = function processHtml2(html) {
226
- const buildTime = formatDate2(/* @__PURE__ */ new Date());
227
- if (!html.includes('<meta name="version"')) {
228
- html = html.replace(/<head[^>]*>/i, (match) => `${match}
239
+ const original = html;
240
+ try {
241
+ const buildTime = formatDate2(/* @__PURE__ */ new Date());
242
+ if (!html.includes('<meta name="version"')) {
243
+ html = html.replace(/<head[^>]*>/i, (match) => `${match}
229
244
  ${metaTag}`);
230
- }
231
- if (headersConfig && !html.includes(HEADERS_INJECTED_MARK)) {
232
- const headerScript = buildHeaderScript(buildTime, headersConfig);
233
- html = html.replace(/<head[^>]*>/i, (match) => `${match}
245
+ }
246
+ if (headersConfig && !html.includes(HEADERS_INJECTED_MARK)) {
247
+ const headerScript = buildHeaderScript(buildTime, headersConfig);
248
+ html = html.replace(/<head[^>]*>/i, (match) => `${match}
234
249
  ${headerScript}
235
250
  `);
236
- }
237
- if (options.log !== false && !html.includes(INJECTED_MARK)) {
238
- const logScript = buildLogScript(buildTime);
239
- html = html.replace(/<\/body>/i, () => ` ${logScript}
251
+ }
252
+ if (options.log !== false && !html.includes(INJECTED_MARK)) {
253
+ const logScript = buildLogScript(buildTime);
254
+ html = html.replace(/<\/body>/i, () => ` ${logScript}
240
255
  </body>`);
256
+ }
257
+ return html;
258
+ } catch (err) {
259
+ console.warn("[VersionInjector] injection failed, returning original HTML unchanged:", err);
260
+ return original;
241
261
  }
242
- return html;
243
262
  };
244
263
  processHtml.resetBuildTime = () => {
245
264
  };