miridev-cli
Version:
Official CLI tool for deploying static sites to miri.dev - Deploy your websites in seconds
262 lines (218 loc) • 6.59 kB
JavaScript
const chalk = require('chalk');
const path = require('path');
const fs = require('fs-extra');
const readline = require('readline');
const { createConfig } = require('../utils/files');
/**
* Check if we're in an interactive environment
*/
function isInteractive() {
return process.stdin.isTTY && process.stdout.isTTY && !process.env.CI;
}
/**
* Get user input safely
*/
function getUserInput(question, defaultValue = '') {
return new Promise((resolve) => {
if (!isInteractive()) {
// Non-interactive environment, use default values
resolve(defaultValue);
return;
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const promptText = defaultValue
? `${question} (${defaultValue}): `
: `${question}: `;
rl.question(promptText, (answer) => {
rl.close();
resolve(answer.trim() || defaultValue);
});
});
}
/**
* Get confirmation safely
*/
function getConfirmation(question, defaultValue = false) {
return new Promise((resolve) => {
if (!isInteractive()) {
// Non-interactive environment, use default
resolve(defaultValue);
return;
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const defaultText = defaultValue ? 'Y/n' : 'y/N';
rl.question(`${question} (${defaultText}): `, (answer) => {
rl.close();
const response = answer.trim().toLowerCase();
if (!response) {
resolve(defaultValue);
} else {
resolve(response === 'y' || response === 'yes');
}
});
});
}
/**
* 초기 설정 명령어
*/
async function init(options) {
console.log(chalk.blue.bold('\n🛠️ Initializing miri.dev configuration\n'));
try {
const configPath = path.resolve('miri.config.js');
// 기존 설정 파일 확인
if (await fs.pathExists(configPath) && !options.force) {
const overwrite = await getConfirmation('Configuration file already exists. Overwrite?', false);
if (!overwrite) {
console.log(chalk.gray('Configuration initialization cancelled'));
return;
}
}
// 프로젝트 정보 수집
console.log(chalk.cyan('📋 Let\'s set up your project configuration:\n'));
const siteName = await getUserInput('Site name (optional)', path.basename(process.cwd()));
const customDomain = await getUserInput('Custom domain (optional)', '');
const needsBuild = await getConfirmation('Does your project need a build step?', false);
let buildConfig = {};
if (needsBuild) {
const buildCommand = await getUserInput('Build command', 'npm run build');
const buildDirectory = await getUserInput('Build output directory', detectBuildDirectory());
buildConfig = {
command: buildCommand,
directory: buildDirectory
};
}
// 고급 설정
const advancedConfig = await getConfirmation('Configure advanced deployment options?', false);
let deployConfig = {};
if (advancedConfig) {
const includeHidden = await getConfirmation('Include hidden files (starting with .)?', false);
const maxFileSize = await getUserInput('Maximum file size limit (10MB/25MB/50MB/100MB)', '25MB');
deployConfig = {
ignore: ['*.map', 'src/**', 'tests/**', 'docs/**', '*.md'], // Default ignore patterns
includeHidden,
maxFileSize
};
}
// 설정 파일 생성
const config = {
site: {
name: siteName || null,
customDomain: customDomain || null
},
build: needsBuild
? buildConfig
: {
command: null,
directory: '.'
},
deploy: {
ignore: deployConfig.ignore || [],
includeHidden: deployConfig.includeHidden || false,
maxFileSize: deployConfig.maxFileSize || '25MB'
}
};
await createConfig(configPath, config);
// .miriignore 파일 생성 제안
const ignoreFile = path.resolve('.miriignore');
if (!(await fs.pathExists(ignoreFile))) {
const createIgnore = await getConfirmation('Create .miriignore file with common patterns?', true);
if (createIgnore) {
await createIgnoreFile(ignoreFile);
}
}
console.log(chalk.green.bold('\n✨ Configuration setup complete!\n'));
console.log(chalk.gray('Next steps:'));
console.log(chalk.gray(' 1. Review and edit miri.config.js if needed'));
console.log(chalk.gray(' 2. Run "miridev deploy" to deploy your site'));
console.log(chalk.gray(' 3. Optional: Run "miridev login" for extended hosting'));
} catch (error) {
console.error(chalk.red.bold('\n✗ Initialization failed:'));
console.error(chalk.red(error.message));
process.exit(1);
}
}
/**
* 빌드 디렉토리 자동 감지
*/
function detectBuildDirectory() {
const commonBuildDirs = ['dist', 'build', 'public', 'out', '_site'];
for (const dir of commonBuildDirs) {
if (fs.existsSync(dir)) {
return dir;
}
}
// package.json에서 빌드 스크립트 확인
try {
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const buildScript = packageJson.scripts?.build;
if (buildScript) {
// Next.js
if (buildScript.includes('next build')) { return 'out'; }
// Nuxt.js
if (buildScript.includes('nuxt generate')) { return 'dist'; }
// Vue CLI
if (buildScript.includes('vue-cli-service build')) { return 'dist'; }
// Create React App
if (buildScript.includes('react-scripts build')) { return 'build'; }
// Vite
if (buildScript.includes('vite build')) { return 'dist'; }
}
} catch (error) {
// package.json이 없거나 파싱 실패
}
return 'dist';
}
/**
* .miriignore 파일 생성
*/
async function createIgnoreFile(filePath) {
const ignoreContent = `# miri.dev deployment ignore file
# Add patterns for files/directories to exclude from deployment
# Development files
src/
tests/
test/
__tests__/
*.test.js
*.spec.js
# Documentation
README.md
CHANGELOG.md
LICENSE
docs/
# Configuration files
*.config.js
.eslintrc*
.prettierrc*
tsconfig.json
jsconfig.json
# Source maps
*.map
# Logs
*.log
npm-debug.log*
yarn-debug.log*
# Editor directories and files
.vscode/
.idea/
*.swp
*.swo
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
`;
await fs.writeFile(filePath, ignoreContent, 'utf8');
console.log(chalk.green('✓ Created .miriignore file'));
}
module.exports = init;