NapCatQQ source snapshot
This commit is contained in:
commit
f954252a34
1111 files changed
+139867
No files matched your search
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "napcat-vite",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"_build": "vite build"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./*"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true
|
||||
},
|
||||
"include": [
|
||||
"*.js",
|
||||
"**/*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
export function autoIncludeTSPlugin (options) {
|
||||
// options: { entries: [{ entry: 'napcat.ts', dir: './utils' }, ...] }
|
||||
const { entries } = options;
|
||||
let tsFilesMap = {};
|
||||
|
||||
return {
|
||||
name: 'vite-auto-include',
|
||||
|
||||
async buildStart () {
|
||||
tsFilesMap = {};
|
||||
for (const { entry, dir } of entries) {
|
||||
if (!tsFilesMap[entry]) {
|
||||
tsFilesMap[entry] = [];
|
||||
}
|
||||
const fullDir = path.resolve(dir);
|
||||
const allTsFiles = await findTSFiles(fullDir);
|
||||
|
||||
const validFiles = [];
|
||||
allTsFiles.forEach((filePath) => {
|
||||
try {
|
||||
const source = fs.readFileSync(filePath, 'utf-8');
|
||||
if (source && source.trim() !== '') {
|
||||
validFiles.push(filePath);
|
||||
} else {
|
||||
// Skipping empty file: ${filePath}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading file: ${filePath}`, error);
|
||||
}
|
||||
});
|
||||
|
||||
tsFilesMap[entry].push(...validFiles);
|
||||
}
|
||||
},
|
||||
|
||||
transform (code, id) {
|
||||
for (const [entry, tsFiles] of Object.entries(tsFilesMap)) {
|
||||
const isMatch = id.endsWith(entry) || id.includes(entry);
|
||||
if (isMatch && tsFiles.length > 0) {
|
||||
const imports = tsFiles.map(filePath => {
|
||||
const relativePath = path.relative(path.dirname(id), filePath).replace(/\\/g, '/');
|
||||
return `import './${relativePath}';`;
|
||||
}).join('\n');
|
||||
|
||||
const transformedCode = imports + '\n' + code;
|
||||
|
||||
return {
|
||||
code: transformedCode,
|
||||
map: { mappings: '' }, // 空映射即可,VSCode 可以在 TS 上断点
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
// 辅助函数:查找所有 .ts 文件
|
||||
async function findTSFiles (dir) {
|
||||
const files = [];
|
||||
const items = await fs.promises.readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dir, item.name);
|
||||
if (item.isDirectory()) {
|
||||
files.push(...await findTSFiles(fullPath)); // 递归查找子目录
|
||||
} else if (item.isFile() && fullPath.endsWith('.ts')) {
|
||||
files.push(fullPath); // 收集 .ts 文件
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import https from 'https';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
/**
|
||||
* SemVer 2.0 正则表达式
|
||||
* 格式: 主版本号.次版本号.修订号[-先行版本号][+版本编译信息]
|
||||
* 参考: https://semver.org/lang/zh-CN/
|
||||
*/
|
||||
const SEMVER_REGEX = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
||||
|
||||
/**
|
||||
* Validate version format according to SemVer 2.0 specification
|
||||
* @param {string} version - The version string to validate (with or without 'v' prefix)
|
||||
* @returns {{ valid: boolean, normalized: string, major: number, minor: number, patch: number, prerelease: string|null, buildmetadata: string|null }}
|
||||
*/
|
||||
function validateVersion (version) {
|
||||
if (!version || typeof version !== 'string') {
|
||||
return { valid: false, normalized: '1.0.0-dev', major: 1, minor: 0, patch: 0, prerelease: 'dev', buildmetadata: null };
|
||||
}
|
||||
|
||||
const match = version.trim().match(SEMVER_REGEX);
|
||||
if (match) {
|
||||
const major = parseInt(match[1], 10);
|
||||
const minor = parseInt(match[2], 10);
|
||||
const patch = parseInt(match[3], 10);
|
||||
const prerelease = match[4] || null;
|
||||
const buildmetadata = match[5] || null;
|
||||
|
||||
// 构建标准化版本号(不带 v 前缀)
|
||||
let normalized = `${major}.${minor}.${patch}`;
|
||||
if (prerelease) normalized += `-${prerelease}`;
|
||||
if (buildmetadata) normalized += `+${buildmetadata}`;
|
||||
|
||||
return { valid: true, normalized, major, minor, patch, prerelease, buildmetadata };
|
||||
}
|
||||
return { valid: false, normalized: '1.0.0-dev', major: 1, minor: 0, patch: 0, prerelease: 'dev', buildmetadata: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* NapCat Vite Plugin: fetches latest GitHub tag (not release) and injects into import.meta.env
|
||||
*
|
||||
* 版本号来源优先级:
|
||||
* 1. 环境变量 NAPCAT_VERSION (用于 CI 构建)
|
||||
* 2. 缓存的 GitHub tag
|
||||
* 3. 从 GitHub API 获取最新公开 tag(不携带 token)
|
||||
* 4. 兜底版本号: 1.0.0-dev
|
||||
*/
|
||||
export default function vitePluginNapcatVersion () {
|
||||
const pluginDir = path.resolve(__dirname, 'dist');
|
||||
const cacheFile = path.join(pluginDir, '.napcat-version.json');
|
||||
const owner = 'NapNeko';
|
||||
const repo = 'NapCatQQ';
|
||||
const maxAgeMs = 24 * 60 * 60 * 1000; // cache 1 day
|
||||
// CI 构建时可通过环境变量直接指定版本号
|
||||
const envVersion = process.env.NAPCAT_VERSION;
|
||||
const fallbackVersion = '1.0.0-dev';
|
||||
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
|
||||
function readCache () {
|
||||
try {
|
||||
const stat = fs.statSync(cacheFile);
|
||||
if (Date.now() - stat.mtimeMs < maxAgeMs) {
|
||||
const data = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
||||
if (data?.tag) return data.tag;
|
||||
}
|
||||
} catch { }
|
||||
return null;
|
||||
}
|
||||
|
||||
function writeCache (tag) {
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
cacheFile,
|
||||
JSON.stringify({ tag, time: new Date().toISOString() }, null, 2)
|
||||
);
|
||||
} catch { }
|
||||
}
|
||||
|
||||
async function fetchLatestTag () {
|
||||
const url = `https://api.github.com/repos/${owner}/${repo}/tags`;
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = https.get(
|
||||
url,
|
||||
{
|
||||
headers: {
|
||||
'User-Agent': 'vite-plugin-napcat-version',
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
},
|
||||
},
|
||||
(res) => {
|
||||
let data = '';
|
||||
res.on('data', (c) => (data += c));
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
if (Array.isArray(json) && json[0]?.name) {
|
||||
const tagName = json[0].name;
|
||||
const { valid, normalized } = validateVersion(tagName);
|
||||
if (valid) {
|
||||
resolve(normalized);
|
||||
} else {
|
||||
console.warn(`[vite-plugin-napcat-version] Invalid tag format: ${tagName}, expected vX.X.X`);
|
||||
reject(new Error(`Invalid tag format: ${tagName}, expected vX.X.X`));
|
||||
}
|
||||
} else reject(new Error('Invalid GitHub tag response'));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
req.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function getVersion () {
|
||||
// 优先使用环境变量指定的版本号 (CI 构建)
|
||||
if (envVersion) {
|
||||
const { valid, normalized } = validateVersion(envVersion);
|
||||
if (valid) {
|
||||
console.log(`[vite-plugin-napcat-version] Using version from NAPCAT_VERSION env: ${normalized}`);
|
||||
return normalized;
|
||||
} else {
|
||||
console.warn(`[vite-plugin-napcat-version] Invalid NAPCAT_VERSION format: ${envVersion}, falling back to fetch`);
|
||||
}
|
||||
}
|
||||
|
||||
const cached = readCache();
|
||||
if (cached) return cached;
|
||||
try {
|
||||
const tag = await fetchLatestTag();
|
||||
writeCache(tag);
|
||||
return tag;
|
||||
} catch (e) {
|
||||
console.warn('[vite-plugin-napcat-version] Failed to fetch tag:', e.message);
|
||||
return cached ?? fallbackVersion;
|
||||
}
|
||||
}
|
||||
|
||||
let lastTag = null;
|
||||
|
||||
return {
|
||||
name: 'vite-plugin-napcat-version',
|
||||
enforce: 'pre',
|
||||
|
||||
async config (userConfig) {
|
||||
const tag = await getVersion();
|
||||
console.log(`[vite-plugin-napcat-version] Using version: ${tag}`);
|
||||
lastTag = tag;
|
||||
return {
|
||||
define: {
|
||||
...(userConfig.define || {}),
|
||||
'import.meta.env.VITE_NAPCAT_VERSION': JSON.stringify(tag),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
handleHotUpdate (ctx) {
|
||||
if (path.resolve(ctx.file) === cacheFile) {
|
||||
try {
|
||||
const json = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
||||
const tag = json?.tag;
|
||||
if (tag && tag !== lastTag) {
|
||||
lastTag = tag;
|
||||
ctx.server?.ws.send({ type: 'full-reload' });
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Export validateVersion for external use
|
||||
export { validateVersion };
|
||||
Reference in new issue
Block a user