vibe-cli-tool
Version:
VibeCLI v1.9 - 模板商店生态,智能部署生成,多语言支持,交互式学习系统,企业级Web全栈应用CLI工具
144 lines • 6.41 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import crypto from 'crypto';
/**
* Generate a random ID deterministic per project (hashed path).
*/
function getStableId(seed) {
return crypto.createHash('sha1').update(seed).digest('hex').slice(0, 10);
}
export async function generateDeploymentArtifacts(projectPath, config) {
switch (config.platform) {
case 'vercel':
return generateVercelArtifacts(projectPath, config);
case 'docker':
return generateDockerArtifacts(projectPath, config);
case 'netlify':
return generateNetlifyArtifacts(projectPath, config);
case 'cloudflare':
return generateCloudflareArtifacts(projectPath, config);
case 'aliyun':
return generateAliyunArtifacts(projectPath, config);
case 'aws':
default:
// Not yet implemented
return {
deploymentId: `deploy_${Date.now()}`,
};
}
}
async function generateVercelArtifacts(projectPath, config) {
const vercelDir = path.join(projectPath, '.vercel');
await fs.ensureDir(vercelDir);
const projectId = getStableId(projectPath);
const orgId = getStableId('org');
// Basic project.json
const projectJson = {
projectId,
orgId,
settings: {
framework: 'nextjs',
devCommand: 'next dev',
buildCommand: 'npm run build',
outputDirectory: 'out',
installCommand: 'npm install',
},
};
await fs.writeJson(path.join(vercelDir, 'project.json'), projectJson, {
spaces: 2,
});
// output.json for static export (if any)
const outputJsonPath = path.join(vercelDir, 'output.json');
if (!(await fs.pathExists(outputJsonPath))) {
await fs.writeJson(outputJsonPath, {
version: 3,
routes: [],
}, { spaces: 2 });
}
// migration for sqlite → blob (only if sqlite detected)
await maybeGenerateSqliteMigration(projectPath);
const deploymentId = `vercel_${Date.now()}`;
const url = `https://${projectId}-vibecli.vercel.app`;
await writeDeploymentMeta(projectPath, config, deploymentId, url);
return { deploymentId, url };
}
async function generateDockerArtifacts(projectPath, config) {
const dockerfilePath = path.join(projectPath, 'Dockerfile');
if (!(await fs.pathExists(dockerfilePath))) {
const dockerfileContent = `# Generated by VibeCLI\nFROM node:20-alpine AS builder\nWORKDIR /app\nCOPY . .\nRUN npm ci && npm run build\n\nFROM node:20-alpine\nWORKDIR /app\nCOPY --from=builder /app .\nEXPOSE 3000\nCMD [\"npm\", \"run\", \"start\"]\n`;
await fs.writeFile(dockerfilePath, dockerfileContent);
}
await maybeGenerateSqliteMigration(projectPath);
const deploymentId = `docker_${Date.now()}`;
await writeDeploymentMeta(projectPath, config, deploymentId);
return { deploymentId };
}
async function generateNetlifyArtifacts(projectPath, config) {
const netlifyConfigPath = path.join(projectPath, 'netlify.toml');
if (!(await fs.pathExists(netlifyConfigPath))) {
const tomlContent = `# Generated by VibeCLI\n[build]\n command = \"npm run build\"\n publish = \"out\"\n`;
await fs.writeFile(netlifyConfigPath, tomlContent);
}
await maybeGenerateSqliteMigration(projectPath);
const deploymentId = `netlify_${Date.now()}`;
await writeDeploymentMeta(projectPath, config, deploymentId);
return { deploymentId };
}
async function generateCloudflareArtifacts(projectPath, config) {
const wranglerPath = path.join(projectPath, 'wrangler.toml');
if (!(await fs.pathExists(wranglerPath))) {
const content = `# Generated by VibeCLI\nname = "${path.basename(projectPath)}"\naccount_id = "YOUR_CLOUDFLARE_ACCOUNT_ID"\ncompatibility_date = "${new Date().toISOString().slice(0, 10)}"\npages_build_output_dir = "out"\n`;
await fs.writeFile(wranglerPath, content);
}
// Static export expects output dir 'out'
await maybeGenerateSqliteMigration(projectPath);
const deploymentId = `cf_${Date.now()}`;
const url = `https://${path.basename(projectPath)}.pages.dev`;
await writeDeploymentMeta(projectPath, config, deploymentId, url);
return { deploymentId, url };
}
async function generateAliyunArtifacts(projectPath, config) {
const fcDir = path.join(projectPath, 'aliyun');
await fs.ensureDir(fcDir);
const readmePath = path.join(fcDir, 'README.md');
if (!(await fs.pathExists(readmePath))) {
const md = `# Aliyun 部署\n\n此目录包含使用 Aliyun Function Compute / 容器镜像服务部署的占位文件。\n\n> TODO: 根据项目需要自定义 serverless.yml 或 Dockerfile 并使用 \`aliyun cli\` 或 \`terraform\` 部署。`;
await fs.writeFile(readmePath, md);
}
await maybeGenerateSqliteMigration(projectPath);
const deploymentId = `aliyun_${Date.now()}`;
await writeDeploymentMeta(projectPath, config, deploymentId);
return { deploymentId };
}
async function maybeGenerateSqliteMigration(projectPath) {
try {
const schemaPath = path.join(projectPath, 'prisma', 'schema.prisma');
if (await fs.pathExists(schemaPath)) {
const schema = await fs.readFile(schemaPath, 'utf-8');
if (schema.includes('provider = "sqlite"')) {
const scriptPath = path.join(projectPath, 'scripts', 'migrate-sqlite-to-blob.js');
if (!(await fs.pathExists(scriptPath))) {
await fs.ensureDir(path.dirname(scriptPath));
const content = `#!/usr/bin/env node\n// Simple placeholder migration script\nconsole.log('TODO: migrate SQLite data to Vercel Blob');\n`;
await fs.writeFile(scriptPath, content);
await fs.chmod(scriptPath, 0o755);
}
}
}
}
catch {
// ignore
}
}
async function writeDeploymentMeta(projectPath, config, deploymentId, url) {
const metaPath = path.join(projectPath, 'deployment.json');
const meta = {
platform: config.platform,
environment: config.environment,
deploymentId,
url: url ?? null,
generatedAt: new Date().toISOString(),
};
await fs.writeJson(metaPath, meta, { spaces: 2 });
}
//# sourceMappingURL=deploy-generator.js.map