import fs from "fs";
import path from "path";
import { glob } from "glob";
import os from "os";
// 全局随机参数变量,在插件初始化时设置
let randomParam = null;
// Windows兼容性常量
const WINDOWS_RESERVED_NAMES = [
"CON",
"PRN",
"AUX",
"NUL",
"COM1",
"COM2",
"COM3",
"COM4",
"COM5",
"COM6",
"COM7",
"COM8",
"COM9",
"LPT1",
"LPT2",
"LPT3",
"LPT4",
"LPT5",
"LPT6",
"LPT7",
"LPT8",
"LPT9",
];
const WINDOWS_INVALID_CHARS = /[<>:"|?*\x00-\x1f]/g;
const MAX_PATH_LENGTH = 260; // Windows MAX_PATH限制
const MAX_FILENAME_LENGTH = 255; // 大多数文件系统的文件名长度限制
/**
* Windows兼容性工具函数
*/
/**
* 检查是否为Windows系统
* @returns {boolean} 是否为Windows
*/
function isWindows() {
return os.platform() === "win32";
}
/**
* 规范化Windows文件名
* @param {string} filename - 原始文件名
* @returns {string} 规范化后的文件名
*/
function normalizeWindowsFilename(filename) {
if (!isWindows()) {
return filename;
}
// 移除无效字符
let normalized = filename.replace(WINDOWS_INVALID_CHARS, "_");
// 移除尾部的点和空格
normalized = normalized.replace(/[. ]+$/, "");
// 检查保留名称
const baseName = normalized.split(".")[0].toUpperCase();
if (WINDOWS_RESERVED_NAMES.includes(baseName)) {
normalized = `_${normalized}`;
}
// 限制文件名长度
if (normalized.length > MAX_FILENAME_LENGTH) {
const ext = path.extname(normalized);
const nameWithoutExt = path.basename(normalized, ext);
const maxNameLength = MAX_FILENAME_LENGTH - ext.length;
normalized = nameWithoutExt.substring(0, maxNameLength) + ext;
}
return normalized;
}
/**
* 检查路径长度是否超出Windows限制
* @param {string} filePath - 文件路径
* @returns {boolean} 是否超出限制
*/
function isPathTooLong(filePath) {
return isWindows() && filePath.length > MAX_PATH_LENGTH;
}
/**
* 生成Windows兼容的安全时间戳
* @returns {string} 安全的时间戳字符串
*/
function generateSafeTimestamp() {
const now = new Date();
const timestamp = now
.toISOString()
.replace(/[:.]/g, "-")
.replace("T", "_")
.substring(0, 19); // 移除毫秒部分
return normalizeWindowsFilename(timestamp);
}
/**
* 规范化glob模式以确保Windows兼容性
* @param {string} pattern - glob模式
* @returns {string} 规范化后的glob模式
*/
function normalizeGlobPattern(pattern) {
if (!pattern) return pattern;
// 在Windows系统中,确保glob模式使用正斜杠
// glob库在Windows中也期望使用正斜杠作为路径分隔符
if (isWindows()) {
return pattern.replace(/\\/g, "/");
}
return pattern;
}
/**
* 创建Windows兼容的glob选项
* @param {Object} baseOptions - 基础选项
* @returns {Object} 兼容的glob选项
*/
function createWindowsCompatibleGlobOptions(baseOptions = {}) {
const options = { ...baseOptions };
if (isWindows()) {
// Windows特定的glob选项
options.windowsPathsNoEscape = true;
options.nonull = false; // 避免返回无匹配的模式
options.dot = options.dot !== false; // 默认包含点文件
}
return options;
}
/**
* 规范化目录路径用于glob搜索
* @param {string} dirPath - 目录路径
* @returns {string} 规范化后的目录路径
*/
function normalizeGlobDirectory(dirPath) {
if (!dirPath) return dirPath;
// 规范化路径
let normalized = path.normalize(dirPath);
// 在Windows中,将反斜杠转换为正斜杠用于glob
if (isWindows()) {
normalized = normalized.replace(/\\/g, "/");
}
return normalized;
}
/**
* 规范化文件路径,确保Windows兼容性
* @param {string} filePath - 原始文件路径
* @returns {string} 规范化后的路径
*/
function normalizeFilePath(filePath) {
if (!filePath) return filePath;
// 使用path.normalize处理路径分隔符
let normalized = path.normalize(filePath);
if (isWindows()) {
// 处理文件名部分
const dir = path.dirname(normalized);
const filename = path.basename(normalized);
const normalizedFilename = normalizeWindowsFilename(filename);
normalized = path.join(dir, normalizedFilename);
}
return normalized;
}
/**
* 验证文件路径的Windows兼容性
* @param {string} filePath - 文件路径
* @param {Object} options - 配置选项
* @returns {Object} 验证结果 {valid: boolean, error?: string, normalized?: string}
*/
function validateFilePath(filePath, options = {}) {
const result = { valid: true };
try {
// 规范化路径
const normalized = normalizeFilePath(filePath);
result.normalized = normalized;
// 检查路径长度
if (isPathTooLong(normalized)) {
result.valid = false;
result.error = `路径长度超出限制 (${normalized.length} > ${MAX_PATH_LENGTH})`;
return result;
}
// 检查文件名
const filename = path.basename(normalized);
if (isWindows() && WINDOWS_INVALID_CHARS.test(filename)) {
result.valid = false;
result.error = `文件名包含Windows不支持的字符: ${filename}`;
return result;
}
// 检查保留名称
const baseName = path
.basename(filename, path.extname(filename))
.toUpperCase();
if (isWindows() && WINDOWS_RESERVED_NAMES.includes(baseName)) {
result.valid = false;
result.error = `文件名使用了Windows保留名称: ${baseName}`;
return result;
}
} catch (error) {
result.valid = false;
result.error = `路径验证失败: ${error.message}`;
}
return result;
}
/**
* 生成随机数参数
* @param {Object} options - 配置选项
* @returns {string} 随机数字符串
*/
function generateRandomParam(options = {}, isFirstSet = false) {
const timestamp = Date.now();
const randomStr = Math.random().toString(36).substring(2, 8);
if (
options.customGenerator &&
typeof options.customGenerator === "function"
) {
return options.customGenerator(timestamp, randomStr);
}
return `${timestamp}${isFirstSet ? `_${randomStr}` : ""}`;
}
/**
* 匹配文件中的CSS和JS引用
* @param {string} content - 文件内容
* @param {Object} options - 配置选项
* @returns {string} 处理后的内容
*/
function processFileContent(content, options = {}) {
let processedContent = content;
// 处理HTML中的link标签 (CSS)
processedContent = processedContent.replace(
/(]*href=["'])([^"']*\.css)([^"']*?)(["'][^>]*>)/gi,
(match, prefix, filePath, queryParams, suffix) => {
if (
filePath.startsWith("http://") ||
filePath.startsWith("https://") ||
filePath.startsWith("//")
) {
return options.includeExternal
? `${prefix}${filePath}${queryParams}${
queryParams.includes("?") || filePath.includes("?") ? "&" : "?"
}v=${randomParam}${suffix}`
: match;
}
return `${prefix}${filePath}${queryParams}${
queryParams.includes("?") || filePath.includes("?") ? "&" : "?"
}v=${randomParam}${suffix}`;
}
);
// 处理HTML中的script标签 (JS)
processedContent = processedContent.replace(
/(