281 lines
7.8 KiB
JavaScript
281 lines
7.8 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* npm 发布脚本
|
||
*
|
||
* 功能:
|
||
* 1. 构建项目(使用相对路径)
|
||
* 2. 在 dist 目录生成 npm 专用的 package.json
|
||
* 3. 发布到 npm
|
||
*
|
||
* 使用免费 CDN 访问:
|
||
* - unpkg: https://unpkg.com/aitu-app@版本号/index.html
|
||
* - jsdelivr: https://cdn.jsdelivr.net/npm/aitu-app@版本号/index.html
|
||
*
|
||
* 用法:
|
||
* node scripts/publish-npm.js [--dry-run] [--skip-build]
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const { execSync } = require('child_process');
|
||
|
||
// 配置
|
||
const CONFIG = {
|
||
// npm 包名(使用非 scoped 包名,便于公开访问)
|
||
packageName: 'aitu-app',
|
||
distDir: path.resolve(__dirname, '../dist/apps/web'),
|
||
rootPackageJson: path.resolve(__dirname, '../package.json'),
|
||
};
|
||
|
||
// 解析命令行参数
|
||
const args = process.argv.slice(2);
|
||
const isDryRun = args.includes('--dry-run');
|
||
const skipBuild = args.includes('--skip-build');
|
||
// 获取 OTP 参数 (--otp=123456)
|
||
const otpArg = args.find(arg => arg.startsWith('--otp='));
|
||
const otp = otpArg ? otpArg.split('=')[1] : null;
|
||
|
||
// 颜色输出
|
||
const colors = {
|
||
reset: '\x1b[0m',
|
||
green: '\x1b[32m',
|
||
yellow: '\x1b[33m',
|
||
blue: '\x1b[34m',
|
||
red: '\x1b[31m',
|
||
};
|
||
|
||
function log(message, color = 'reset') {
|
||
console.log(`${colors[color]}${message}${colors.reset}`);
|
||
}
|
||
|
||
function logStep(step, message) {
|
||
log(`\n[${step}] ${message}`, 'blue');
|
||
}
|
||
|
||
function logSuccess(message) {
|
||
log(`✓ ${message}`, 'green');
|
||
}
|
||
|
||
function logWarning(message) {
|
||
log(`⚠ ${message}`, 'yellow');
|
||
}
|
||
|
||
function logError(message) {
|
||
log(`✗ ${message}`, 'red');
|
||
}
|
||
|
||
// 执行命令
|
||
function exec(command, options = {}) {
|
||
log(` 执行: ${command}`, 'yellow');
|
||
try {
|
||
execSync(command, { stdio: 'inherit', ...options });
|
||
return true;
|
||
} catch (error) {
|
||
logError(`命令执行失败: ${command}`);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 读取根目录 package.json 获取版本号
|
||
function getVersion() {
|
||
const pkg = JSON.parse(fs.readFileSync(CONFIG.rootPackageJson, 'utf-8'));
|
||
return pkg.version;
|
||
}
|
||
|
||
// 生成 npm 发布用的 package.json
|
||
function generateNpmPackageJson(version) {
|
||
const npmPackage = {
|
||
name: CONFIG.packageName,
|
||
version: version,
|
||
description: 'Opentu static assets for CDN (HTML not included)',
|
||
keywords: [
|
||
'aitu',
|
||
'whiteboard',
|
||
'mindmap',
|
||
'flowchart',
|
||
'drawing',
|
||
'AI',
|
||
'image-generation',
|
||
'video-generation'
|
||
],
|
||
homepage: 'https://opentu.ai',
|
||
repository: {
|
||
type: 'git',
|
||
url: 'https://github.com/ljquan/aitu.git'
|
||
},
|
||
bugs: {
|
||
url: 'https://github.com/ljquan/aitu/issues'
|
||
},
|
||
license: 'MIT',
|
||
author: 'ljquan',
|
||
files: [
|
||
'assets/**',
|
||
'version.json',
|
||
'icons/**',
|
||
'logo/**',
|
||
'favicon.ico',
|
||
'manifest.json'
|
||
],
|
||
publishConfig: {
|
||
access: 'public'
|
||
}
|
||
};
|
||
|
||
return npmPackage;
|
||
}
|
||
|
||
// 生成 README
|
||
function generateReadme(version) {
|
||
return `# Opentu (开图) - AI 图片视频创作工具
|
||
|
||
[](https://www.npmjs.com/package/${CONFIG.packageName})
|
||
[](https://opensource.org/licenses/MIT)
|
||
|
||
## 在线访问
|
||
|
||
通过免费 CDN 直接访问:
|
||
|
||
### unpkg (推荐)
|
||
- **最新版**: [https://unpkg.com/${CONFIG.packageName}/index.html](https://unpkg.com/${CONFIG.packageName}/index.html)
|
||
- **指定版本**: [https://unpkg.com/${CONFIG.packageName}@${version}/index.html](https://unpkg.com/${CONFIG.packageName}@${version}/index.html)
|
||
|
||
### jsDelivr
|
||
- **最新版**: [https://cdn.jsdelivr.net/npm/${CONFIG.packageName}/index.html](https://cdn.jsdelivr.net/npm/${CONFIG.packageName}/index.html)
|
||
- **指定版本**: [https://cdn.jsdelivr.net/npm/${CONFIG.packageName}@${version}/index.html](https://cdn.jsdelivr.net/npm/${CONFIG.packageName}@${version}/index.html)
|
||
|
||
## 功能特性
|
||
|
||
- 🎨 **AI 图片生成** - 通过 Gemini 生成精美图片
|
||
- 🎬 **AI 视频生成** - 支持 Veo3、Sora-2 等模型
|
||
- 🧠 **思维导图** - 快速整理思路
|
||
- 📊 **流程图** - 可视化流程设计
|
||
- ✏️ **自由绘画** - 手绘风格绘图
|
||
- 💾 **自动保存** - 本地数据持久化
|
||
- 📱 **PWA 支持** - 可安装为桌面应用
|
||
|
||
## 本地部署
|
||
|
||
1. 下载此 npm 包的内容
|
||
2. 使用任意静态文件服务器托管
|
||
|
||
\`\`\`bash
|
||
# 使用 npx serve
|
||
npx serve ./node_modules/${CONFIG.packageName}
|
||
|
||
# 或使用 http-server
|
||
npx http-server ./node_modules/${CONFIG.packageName}
|
||
\`\`\`
|
||
|
||
## 源代码
|
||
|
||
GitHub: [https://github.com/ljquan/aitu](https://github.com/ljquan/aitu)
|
||
|
||
## 许可证
|
||
|
||
MIT License
|
||
`;
|
||
}
|
||
|
||
// 主流程
|
||
async function main() {
|
||
log('\n🚀 Opentu npm 发布脚本\n', 'blue');
|
||
|
||
if (isDryRun) {
|
||
logWarning('DRY RUN 模式 - 不会实际发布');
|
||
}
|
||
|
||
// 步骤 1: 构建项目
|
||
if (!skipBuild) {
|
||
logStep('1/4', '构建项目');
|
||
if (!exec('npm run build:web')) {
|
||
logError('构建失败');
|
||
process.exit(1);
|
||
}
|
||
logSuccess('构建完成');
|
||
} else {
|
||
logStep('1/4', '跳过构建(使用现有构建产物)');
|
||
}
|
||
|
||
// 步骤 2: 检查 dist 目录
|
||
logStep('2/4', '检查构建产物');
|
||
if (!fs.existsSync(CONFIG.distDir)) {
|
||
logError(`构建目录不存在: ${CONFIG.distDir}`);
|
||
process.exit(1);
|
||
}
|
||
|
||
const indexHtml = path.join(CONFIG.distDir, 'index.html');
|
||
if (!fs.existsSync(indexHtml)) {
|
||
logError('index.html 不存在');
|
||
process.exit(1);
|
||
}
|
||
logSuccess('构建产物检查通过');
|
||
|
||
// 步骤 3: 生成 npm package.json 和 README
|
||
logStep('3/4', '生成 npm 发布文件');
|
||
const version = getVersion();
|
||
log(` 版本号: ${version}`);
|
||
|
||
const npmPackageJson = generateNpmPackageJson(version);
|
||
const npmPackageJsonPath = path.join(CONFIG.distDir, 'package.json');
|
||
fs.writeFileSync(npmPackageJsonPath, JSON.stringify(npmPackageJson, null, 2));
|
||
logSuccess(`生成 package.json`);
|
||
|
||
const readme = generateReadme(version);
|
||
const readmePath = path.join(CONFIG.distDir, 'README.md');
|
||
fs.writeFileSync(readmePath, readme);
|
||
logSuccess(`生成 README.md`);
|
||
|
||
// 移除不需要发布的文件
|
||
const filesToRemove = ['stats.html', 'sw.js.map'];
|
||
filesToRemove.forEach(file => {
|
||
const filePath = path.join(CONFIG.distDir, file);
|
||
if (fs.existsSync(filePath)) {
|
||
fs.unlinkSync(filePath);
|
||
log(` 移除: ${file}`);
|
||
}
|
||
});
|
||
|
||
// 步骤 4: 发布到 npm
|
||
logStep('4/4', '发布到 npm');
|
||
|
||
if (isDryRun) {
|
||
log('\n📦 DRY RUN - 将要发布的内容:', 'yellow');
|
||
exec(`ls -la "${CONFIG.distDir}"`);
|
||
log('\n📄 package.json 内容:', 'yellow');
|
||
console.log(JSON.stringify(npmPackageJson, null, 2));
|
||
logWarning('\n使用 --dry-run 模式,未实际发布');
|
||
} else {
|
||
// 切换到 dist 目录并发布(使用 cd 确保在正确目录)
|
||
let publishCmd = `cd "${CONFIG.distDir}" && npm publish --access public --registry https://registry.npmjs.org`;
|
||
|
||
// 如果提供了 OTP,添加到命令中
|
||
if (otp) {
|
||
publishCmd += ` --otp=${otp}`;
|
||
log(` 使用 OTP: ${otp.slice(0, 2)}****`);
|
||
}
|
||
|
||
if (!exec(publishCmd)) {
|
||
logError('发布失败');
|
||
if (!otp) {
|
||
log('\n💡 提示:如果启用了 2FA,请使用 --otp=123456 参数', 'yellow');
|
||
log(' 例如: pnpm run npm:publish --skip-build --otp=123456', 'yellow');
|
||
}
|
||
process.exit(1);
|
||
}
|
||
logSuccess('发布成功!');
|
||
}
|
||
|
||
// 输出访问链接
|
||
log('\n🎉 完成!', 'green');
|
||
log('\n📌 CDN 访问链接:', 'blue');
|
||
log(` unpkg: https://unpkg.com/${CONFIG.packageName}@${version}/index.html`);
|
||
log(` jsdelivr: https://cdn.jsdelivr.net/npm/${CONFIG.packageName}@${version}/index.html`);
|
||
log(` 最新版: https://unpkg.com/${CONFIG.packageName}/index.html`);
|
||
}
|
||
|
||
main().catch(error => {
|
||
logError(`脚本执行失败: ${error.message}`);
|
||
process.exit(1);
|
||
});
|