unplugin-version-injector 2.1.1 → 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.
@@ -0,0 +1,26 @@
1
+ interface RequestHeadersOptions {
2
+ /** 版本头名称,值为 `${name}/${version}`,默认 'X-Client-Version' */
3
+ versionHeaderName?: string;
4
+ /** 构建时间头名称,值为 formatDate 的输出,默认 'X-Client-Build-Time' */
5
+ buildTimeHeaderName?: string;
6
+ /**
7
+ * 额外注入的跨域地址白名单:字符串按 URL 前缀匹配,正则按完整 URL 测试。
8
+ * 同源请求始终注入。注意:跨域自定义头会触发 CORS 预检,
9
+ * 服务端需在 Access-Control-Allow-Headers 中放行这两个头。
10
+ */
11
+ include?: (string | RegExp)[];
12
+ }
13
+ type DateFormatter = (date: Date) => string;
14
+ interface VersionInjectorOptions {
15
+ version?: string;
16
+ name?: string;
17
+ log?: boolean;
18
+ formatDate?: string | DateFormatter;
19
+ /**
20
+ * 给页面发出的 fetch / XMLHttpRequest 请求自动附加版本与构建时间请求头,
21
+ * 便于在后端日志中定位客户端版本。默认关闭;true 使用默认配置(仅同源)。
22
+ */
23
+ requestHeaders?: boolean | RequestHeadersOptions;
24
+ }
25
+
26
+ export type { RequestHeadersOptions as R, VersionInjectorOptions as V };
@@ -0,0 +1,26 @@
1
+ interface RequestHeadersOptions {
2
+ /** 版本头名称,值为 `${name}/${version}`,默认 'X-Client-Version' */
3
+ versionHeaderName?: string;
4
+ /** 构建时间头名称,值为 formatDate 的输出,默认 'X-Client-Build-Time' */
5
+ buildTimeHeaderName?: string;
6
+ /**
7
+ * 额外注入的跨域地址白名单:字符串按 URL 前缀匹配,正则按完整 URL 测试。
8
+ * 同源请求始终注入。注意:跨域自定义头会触发 CORS 预检,
9
+ * 服务端需在 Access-Control-Allow-Headers 中放行这两个头。
10
+ */
11
+ include?: (string | RegExp)[];
12
+ }
13
+ type DateFormatter = (date: Date) => string;
14
+ interface VersionInjectorOptions {
15
+ version?: string;
16
+ name?: string;
17
+ log?: boolean;
18
+ formatDate?: string | DateFormatter;
19
+ /**
20
+ * 给页面发出的 fetch / XMLHttpRequest 请求自动附加版本与构建时间请求头,
21
+ * 便于在后端日志中定位客户端版本。默认关闭;true 使用默认配置(仅同源)。
22
+ */
23
+ requestHeaders?: boolean | RequestHeadersOptions;
24
+ }
25
+
26
+ export type { RequestHeadersOptions as R, VersionInjectorOptions as V };
package/dist/vite.d.mts CHANGED
@@ -1,5 +1,6 @@
1
- import { Plugin } from 'vite';
1
+ import * as vite from 'vite';
2
+ import { V as VersionInjectorOptions } from './types-Cc-nzIS0.mjs';
2
3
 
3
- declare function versionInjectorPlugin(options?: {}): Plugin;
4
+ declare const _default: (options?: VersionInjectorOptions | undefined) => vite.Plugin<any> | vite.Plugin<any>[];
4
5
 
5
- export { versionInjectorPlugin as default };
6
+ export { _default as default };
package/dist/vite.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { Plugin } from 'vite';
1
+ import * as vite from 'vite';
2
+ import { V as VersionInjectorOptions } from './types-Cc-nzIS0.js';
2
3
 
3
- declare function versionInjectorPlugin(options?: {}): Plugin;
4
+ declare const _default: (options?: VersionInjectorOptions | undefined) => vite.Plugin<any> | vite.Plugin<any>[];
4
5
 
5
- export { versionInjectorPlugin as default };
6
+ export { _default as default };
package/dist/vite.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var unplugin = require('unplugin');
4
+ var module$1 = require('module');
3
5
  var fs = require('fs');
4
6
  var path = require('path');
5
7
 
@@ -8,73 +10,343 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
10
  var fs__default = /*#__PURE__*/_interopDefault(fs);
9
11
  var path__default = /*#__PURE__*/_interopDefault(path);
10
12
 
11
- // src/shared/utils.ts
13
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
14
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
15
+ }) : x)(function(x) {
16
+ if (typeof require !== "undefined") return require.apply(this, arguments);
17
+ throw Error('Dynamic require of "' + x + '" is not supported');
18
+ });
19
+ var cachedPkg = null;
12
20
  function getPackageVersion(startDir) {
21
+ if (cachedPkg) return cachedPkg;
13
22
  try {
14
23
  let dir = startDir || process.cwd();
15
- while (dir !== path__default.default.parse(dir).root) {
24
+ while (true) {
16
25
  const pkgPath = path__default.default.join(dir, "package.json");
17
26
  if (fs__default.default.existsSync(pkgPath)) {
18
27
  const pkg = JSON.parse(fs__default.default.readFileSync(pkgPath, "utf-8"));
19
- return { version: pkg.version || "0.0.0", name: pkg.name || "unknown" };
28
+ cachedPkg = { version: pkg.version || "0.0.0", name: pkg.name || "unknown" };
29
+ return cachedPkg;
20
30
  }
21
- dir = path__default.default.dirname(dir);
31
+ const parent = path__default.default.dirname(dir);
32
+ if (parent === dir) break;
33
+ dir = parent;
22
34
  }
23
35
  console.warn("[VersionInjector] package.json not found");
24
- return { version: "0.0.0", name: "unknown" };
36
+ cachedPkg = { version: "0.0.0", name: "unknown" };
37
+ return cachedPkg;
25
38
  } catch (err) {
26
39
  console.warn("[VersionInjector] Failed to read package.json:", err);
27
- return { version: "0.0.0", name: "unknown" };
40
+ cachedPkg = { version: "0.0.0", name: "unknown" };
41
+ return cachedPkg;
28
42
  }
29
43
  }
30
44
  function defaultFormatDate(date) {
31
45
  return date.toISOString();
32
46
  }
47
+ var WEEKDAYS_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
48
+ var WEEKDAYS_FULL = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
49
+ var MONTHS_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
50
+ var MONTHS_FULL = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
51
+ function pad(num) {
52
+ return num < 10 ? "0" + num : String(num);
53
+ }
54
+ function formatDate(date, format) {
55
+ const year = date.getFullYear();
56
+ const month = date.getMonth();
57
+ const day = date.getDate();
58
+ const hours = date.getHours();
59
+ const minutes = date.getMinutes();
60
+ const seconds = date.getSeconds();
61
+ const ms = date.getMilliseconds();
62
+ const dayOfWeek = date.getDay();
63
+ const tokens = {
64
+ YYYY: String(year),
65
+ YY: String(year).slice(-2),
66
+ MMMM: MONTHS_FULL[month],
67
+ MMM: MONTHS_SHORT[month],
68
+ MM: pad(month + 1),
69
+ M: String(month + 1),
70
+ DD: pad(day),
71
+ D: String(day),
72
+ dddd: WEEKDAYS_FULL[dayOfWeek],
73
+ ddd: WEEKDAYS_SHORT[dayOfWeek],
74
+ dd: WEEKDAYS_SHORT[dayOfWeek].slice(0, 2),
75
+ d: String(dayOfWeek),
76
+ HH: pad(hours),
77
+ H: String(hours),
78
+ hh: pad(hours % 12 || 12),
79
+ h: String(hours % 12 || 12),
80
+ mm: pad(minutes),
81
+ m: String(minutes),
82
+ ss: pad(seconds),
83
+ s: String(seconds),
84
+ SSS: pad(ms),
85
+ SS: pad(Math.floor(ms / 10)),
86
+ S: String(Math.floor(ms / 100)),
87
+ A: hours < 12 ? "AM" : "PM",
88
+ a: hours < 12 ? "am" : "pm"
89
+ };
90
+ return format.replace(/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/g, (match) => tokens[match]);
91
+ }
92
+ function escapeHtml(value) {
93
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
94
+ }
95
+ function toScriptString(value) {
96
+ return JSON.stringify(value).replace(/</g, "\\u003C");
97
+ }
33
98
 
34
99
  // src/core.ts
100
+ var INJECTED_MARK = 'data-injected="unplugin-version-injector"';
101
+ var HEADERS_INJECTED_MARK = 'data-injected="unplugin-version-injector-headers"';
102
+ var HEADER_NAME_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
103
+ var DEFAULT_VERSION_HEADER = "X-Client-Version";
104
+ var DEFAULT_BUILD_TIME_HEADER = "X-Client-Build-Time";
105
+ function normalizeRequestHeaders(opt) {
106
+ if (!opt) return null;
107
+ const o = typeof opt === "object" ? opt : {};
108
+ const pickHeaderName = (value, fallback) => {
109
+ if (value == null) return fallback;
110
+ if (HEADER_NAME_RE.test(value)) return value;
111
+ console.warn(
112
+ `[VersionInjector] invalid request header name "${value}", falling back to "${fallback}"`
113
+ );
114
+ return fallback;
115
+ };
116
+ const include = (Array.isArray(o.include) ? o.include : []).filter(
117
+ (p) => typeof p === "string" || p instanceof RegExp
118
+ );
119
+ return {
120
+ versionHeaderName: pickHeaderName(o.versionHeaderName, DEFAULT_VERSION_HEADER),
121
+ buildTimeHeaderName: pickHeaderName(o.buildTimeHeaderName, DEFAULT_BUILD_TIME_HEADER),
122
+ include
123
+ };
124
+ }
125
+ function sanitizeHeaderValue(value) {
126
+ return value.replace(/[^\t\x20-\x7E]/g, "").trim();
127
+ }
128
+ function serializeInclude(patterns) {
129
+ const items = patterns.map(
130
+ (p) => typeof p === "string" ? toScriptString(p) : `new RegExp(${toScriptString(p.source)}, ${toScriptString(p.flags)})`
131
+ );
132
+ return `[${items.join(", ")}]`;
133
+ }
35
134
  function createVersionInjector(options = {}) {
36
- var _a;
37
- const { version, name } = options.version && options.name ? options : getPackageVersion();
38
- const buildTime = ((_a = options.formatDate) != null ? _a : defaultFormatDate)(/* @__PURE__ */ new Date());
39
- const metaTag = `<meta name="version" content="${version}">
40
- <meta name="project" content="${name}">
135
+ const pkg = options.version && options.name ? { version: options.version, name: options.name } : getPackageVersion();
136
+ const version = options.version || pkg.version;
137
+ const name = options.name || pkg.name;
138
+ const formatDateOpt = options.formatDate;
139
+ const formatDate2 = typeof formatDateOpt === "string" ? (date) => formatDate(date, formatDateOpt) : formatDateOpt != null ? formatDateOpt : defaultFormatDate;
140
+ const headersConfig = normalizeRequestHeaders(options.requestHeaders);
141
+ const metaTag = `<meta name="version" content="${escapeHtml(version)}">
142
+ <meta name="project" content="${escapeHtml(name)}">
41
143
  `;
42
- const logScript = `
43
- <script data-injected="unplugin-version-injector">
144
+ const buildLogScript = (buildTime) => `
145
+ <script ${INJECTED_MARK}>
44
146
  (function () {
45
- var isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
46
- var bg = isDark ? '#ffffff' : '#1e1e1e' ;
47
- var border = 'border-radius: 4px; padding: 4px; font-size: 12px;';
48
- var styles = {
49
- version: \`background: \${bg}; color: #00c853; \${border}\`,
50
- time: \`background: \${bg}; color: #ffab00; \${border}\`,
51
- };
52
- console.log("%c ${name}@${version} ", styles.version);
53
- console.log("%c Build Time: ${buildTime} ", styles.time);
147
+ try {
148
+ var isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
149
+ var bg = isDark ? '#ffffff' : '#1e1e1e';
150
+ var base = 'background: ' + bg + '; border-radius: 4px; padding: 4px; font-size: 12px;';
151
+ console.log('%c' + ${toScriptString(` ${name}@${version} `)}, base + ' color: #00c853;');
152
+ console.log('%c' + ${toScriptString(` Build Time: ${buildTime} `)}, base + ' color: #ffab00;');
153
+ } catch (e) {}
54
154
  })();
55
155
  </script>`;
56
- return function processHtml(html) {
57
- if (!html.includes('<meta name="version"')) {
58
- html = html.replace(/<head>/i, `<head>
59
- ${metaTag}`);
156
+ const buildHeaderScript = (buildTime, cfg) => {
157
+ const versionValue = sanitizeHeaderValue(`${name}/${version}`);
158
+ const buildValue = sanitizeHeaderValue(buildTime);
159
+ return `
160
+ <script ${HEADERS_INJECTED_MARK}>
161
+ (function () {
162
+ try {
163
+ if (window.__UVI_HEADERS_PATCHED__) return;
164
+ window.__UVI_HEADERS_PATCHED__ = true;
165
+ var VERSION_HEADER = ${toScriptString(cfg.versionHeaderName)};
166
+ var BUILD_HEADER = ${toScriptString(cfg.buildTimeHeaderName)};
167
+ var VERSION_VALUE = ${toScriptString(versionValue)};
168
+ var BUILD_VALUE = ${toScriptString(buildValue)};
169
+ var INCLUDE = ${serializeInclude(cfg.include)};
170
+
171
+ function resolveUrl(url) {
172
+ try {
173
+ if (typeof URL === 'function') return new URL(url, location.href);
174
+ } catch (e) {
175
+ return null;
176
+ }
177
+ try {
178
+ var a = document.createElement('a');
179
+ a.href = url;
180
+ return { protocol: a.protocol, host: a.host, href: a.href };
181
+ } catch (e2) {
182
+ return null;
183
+ }
184
+ }
185
+
186
+ function shouldInject(url) {
187
+ var u = resolveUrl(url == null ? '' : String(url));
188
+ if (!u || (u.protocol !== 'http:' && u.protocol !== 'https:')) return false;
189
+ if (u.protocol === location.protocol && u.host === location.host) return true;
190
+ for (var i = 0; i < INCLUDE.length; i++) {
191
+ var p = INCLUDE[i];
192
+ if (typeof p === 'string' ? u.href.indexOf(p) === 0 : p.test(u.href)) return true;
193
+ }
194
+ return false;
195
+ }
196
+
197
+ if (typeof window.fetch === 'function' && typeof Headers === 'function') {
198
+ var originalFetch = window.fetch;
199
+ window.fetch = function (input, init) {
200
+ try {
201
+ var isRequest = typeof Request === 'function' && input instanceof Request;
202
+ var url = isRequest ? input.url : String(input);
203
+ if (shouldInject(url)) {
204
+ var headers = new Headers(
205
+ (init && init.headers) || (isRequest ? input.headers : undefined)
206
+ );
207
+ headers.set(VERSION_HEADER, VERSION_VALUE);
208
+ headers.set(BUILD_HEADER, BUILD_VALUE);
209
+ var copy = {};
210
+ if (init) for (var k in init) copy[k] = init[k];
211
+ copy.headers = headers;
212
+ init = copy;
213
+ }
214
+ } catch (e) {}
215
+ return originalFetch.call(this, input, init);
216
+ };
60
217
  }
61
- if (options.log !== false && !html.includes('data-injected="unplugin-version-injector"')) {
62
- html = html.replace(/<\/body>/i, ` ${logScript}
218
+
219
+ if (window.XMLHttpRequest && XMLHttpRequest.prototype) {
220
+ var proto = XMLHttpRequest.prototype;
221
+ var originalOpen = proto.open;
222
+ var originalSend = proto.send;
223
+ if (originalOpen && originalSend) {
224
+ proto.open = function (method, url) {
225
+ try {
226
+ this.__uviInject = shouldInject(url);
227
+ } catch (e) {}
228
+ return originalOpen.apply(this, arguments);
229
+ };
230
+ proto.send = function () {
231
+ if (this.__uviInject) {
232
+ try {
233
+ this.setRequestHeader(VERSION_HEADER, VERSION_VALUE);
234
+ this.setRequestHeader(BUILD_HEADER, BUILD_VALUE);
235
+ } catch (e) {}
236
+ }
237
+ return originalSend.apply(this, arguments);
238
+ };
239
+ }
240
+ }
241
+ } catch (e) {}
242
+ })();
243
+ </script>`;
244
+ };
245
+ const processHtml = function processHtml2(html) {
246
+ const original = html;
247
+ try {
248
+ const buildTime = formatDate2(/* @__PURE__ */ new Date());
249
+ if (!html.includes('<meta name="version"')) {
250
+ html = html.replace(/<head[^>]*>/i, (match) => `${match}
251
+ ${metaTag}`);
252
+ }
253
+ if (headersConfig && !html.includes(HEADERS_INJECTED_MARK)) {
254
+ const headerScript = buildHeaderScript(buildTime, headersConfig);
255
+ html = html.replace(/<head[^>]*>/i, (match) => `${match}
256
+ ${headerScript}
257
+ `);
258
+ }
259
+ if (options.log !== false && !html.includes(INJECTED_MARK)) {
260
+ const logScript = buildLogScript(buildTime);
261
+ html = html.replace(/<\/body>/i, () => ` ${logScript}
63
262
  </body>`);
263
+ }
264
+ return html;
265
+ } catch (err) {
266
+ console.warn("[VersionInjector] injection failed, returning original HTML unchanged:", err);
267
+ return original;
64
268
  }
65
- return html;
66
269
  };
270
+ processHtml.resetBuildTime = () => {
271
+ };
272
+ return processHtml;
67
273
  }
68
274
 
69
- // src/vite.ts
70
- function versionInjectorPlugin(options = {}) {
275
+ // src/index.ts
276
+ var import_meta = {};
277
+ var PLUGIN_NAME = "unplugin-version-injector";
278
+ function injectBundleHtml(bundle, inject) {
279
+ for (const file of Object.values(bundle)) {
280
+ if (file.type === "asset" && file.fileName.endsWith(".html")) {
281
+ const source = typeof file.source === "string" ? file.source : Buffer.from(file.source).toString("utf-8");
282
+ file.source = inject(source);
283
+ }
284
+ }
285
+ }
286
+ function applyWebpackLike(compiler, inject) {
287
+ var _a;
288
+ const api = (_a = compiler.webpack) != null ? _a : compiler.rspack;
289
+ if ((api == null ? void 0 : api.Compilation) && (api == null ? void 0 : api.sources)) {
290
+ compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
291
+ compilation.hooks.processAssets.tap(
292
+ {
293
+ name: PLUGIN_NAME,
294
+ // html-webpack-plugin 在 OPTIMIZE_INLINE 阶段产出 HTML,SUMMARIZE 保证跑在它之后
295
+ stage: api.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
296
+ },
297
+ (assets) => {
298
+ for (const name of Object.keys(assets)) {
299
+ if (name.endsWith(".html")) {
300
+ const html = assets[name].source().toString();
301
+ compilation.updateAsset(name, new api.sources.RawSource(inject(html)));
302
+ }
303
+ }
304
+ }
305
+ );
306
+ });
307
+ } else {
308
+ const requireFn = typeof __require === "function" ? __require : module$1.createRequire(import_meta.url);
309
+ const { RawSource } = requireFn("webpack-sources");
310
+ compiler.hooks.emit.tapAsync(PLUGIN_NAME, (compilation, callback) => {
311
+ for (const name of Object.keys(compilation.assets)) {
312
+ if (name.endsWith(".html")) {
313
+ const html = compilation.assets[name].source().toString();
314
+ compilation.assets[name] = new RawSource(inject(html));
315
+ }
316
+ }
317
+ callback();
318
+ });
319
+ }
320
+ }
321
+ var unpluginFactory = (options = {}) => {
71
322
  const inject = createVersionInjector(options);
72
323
  return {
73
- name: "vite-version-injector",
74
- transformIndexHtml(html) {
75
- return inject(html);
324
+ name: PLUGIN_NAME,
325
+ vite: {
326
+ transformIndexHtml(html) {
327
+ return inject(html);
328
+ }
329
+ },
330
+ rollup: {
331
+ generateBundle(_outputOptions, bundle) {
332
+ injectBundleHtml(bundle, inject);
333
+ }
334
+ },
335
+ rolldown: {
336
+ generateBundle(_outputOptions, bundle) {
337
+ injectBundleHtml(bundle, inject);
338
+ }
339
+ },
340
+ webpack(compiler) {
341
+ applyWebpackLike(compiler, inject);
342
+ },
343
+ rspack(compiler) {
344
+ applyWebpackLike(compiler, inject);
76
345
  }
77
346
  };
78
- }
347
+ };
348
+
349
+ // src/vite.ts
350
+ var vite_default = unplugin.createVitePlugin(unpluginFactory);
79
351
 
80
- module.exports = versionInjectorPlugin;
352
+ module.exports = vite_default;