@diagramers/cli
Version:
Diagramers CLI - Command-line tools for managing Diagramers projects
409 lines (385 loc) ⢠15.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProjectInitializer = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const glob = __importStar(require("glob"));
const chalk_1 = __importDefault(require("chalk"));
const child_process_1 = require("child_process");
class ProjectInitializer {
constructor() {
this.templateFiles = [
'scripts',
'src/**/*',
'scripts/**/*',
'package.json',
'tsconfig.json',
'webpack.config.js',
'main.ts',
'certs/**/*',
'.env.example'
];
// No need for template path as we'll use npm package
}
async initialize(projectName, options) {
const projectPath = path.resolve(process.cwd(), projectName);
const templateType = options.template || 'api';
const targetVersion = options.version || (templateType === 'admin' ? '3.0.17' : 'latest');
// Check if project directory already exists
if (await fs.pathExists(projectPath)) {
throw new Error(`Project directory ${projectName} already exists`);
}
// Create project directory
await fs.ensureDir(projectPath);
if (templateType === 'admin') {
// Handle admin projects differently
await this.initializeAdminProject(projectPath, projectName, targetVersion, options);
}
else {
// Handle API projects (existing logic)
await this.initializeApiProject(projectPath, projectName, templateType, targetVersion, options);
}
console.log(chalk_1.default.green(`ā
Project structure created successfully`));
}
async initializeAdminProject(projectPath, projectName, targetVersion, options) {
console.log(chalk_1.default.blue(`š Initializing admin project: ${projectName}`));
try {
// Use the admin CLI to initialize the project
const adminCommand = `npx @diagramers/admin@${targetVersion} init ${projectName}`;
// Add options if provided
const commandOptions = [];
if (options.yes)
commandOptions.push('--yes');
const fullCommand = `${adminCommand} ${commandOptions.join(' ')}`;
console.log(chalk_1.default.gray(`Executing: ${fullCommand}`));
(0, child_process_1.execSync)(fullCommand, {
stdio: 'inherit',
cwd: path.dirname(projectPath)
});
console.log(chalk_1.default.green(`ā
Admin project '${projectName}' initialized successfully!`));
}
catch (error) {
throw new Error(`Failed to initialize admin project: ${error.message}`);
}
}
async initializeApiProject(projectPath, projectName, templateType, targetVersion, options) {
// Install the appropriate template package
await this.installTemplatePackage(projectPath, templateType, targetVersion);
// Copy template files from the installed package
await this.copyTemplateFiles(projectPath, templateType);
// Update package.json with project name
await this.updatePackageJson(projectPath, projectName, templateType);
// Create .gitignore
await this.createGitignore(projectPath);
// Create README
await this.createReadme(projectPath, projectName, templateType);
// Make scripts executable
await this.makeScriptsExecutable(projectPath);
// Process template for project name (update database names, etc.)
await this.processTemplate(projectPath, projectName);
// Ensure .env.example is synced with the latest from the template package
await this.syncEnvExampleFromTemplate(projectPath, projectName, templateType);
// Clean up the temporary installation
await this.cleanup(projectPath);
}
async installTemplatePackage(projectPath, templateType, targetVersion) {
const packageName = `@diagramers/${templateType}`;
console.log(chalk_1.default.blue(`š¦ Installing ${packageName} template...`));
try {
// Create a temporary package.json to enable npm install
const tempPackageJson = {
name: 'temp-project',
version: '1.0.0',
private: true
};
await fs.writeFile(path.join(projectPath, 'package.json'), JSON.stringify(tempPackageJson, null, 2));
// Install the template package temporarily
(0, child_process_1.execSync)(`npm install ${packageName}@${targetVersion} --no-save`, {
cwd: projectPath,
stdio: 'inherit'
});
}
catch (error) {
throw new Error(`Failed to install ${packageName} package: ${error.message}`);
}
}
async copyTemplateFiles(projectPath, templateType) {
const nodeModulesPath = path.join(projectPath, 'node_modules', '@diagramers', templateType);
if (!await fs.pathExists(nodeModulesPath)) {
throw new Error(`@diagramers/${templateType} package not found in node_modules`);
}
for (const pattern of this.templateFiles) {
try {
const files = glob.sync(pattern, {
cwd: nodeModulesPath,
ignore: ['node_modules/**', 'dist/**', '.git/**'],
nodir: false
});
for (const file of files) {
const sourcePath = path.join(nodeModulesPath, file);
const destPath = path.join(projectPath, file);
if (await fs.pathExists(sourcePath)) {
const stat = await fs.stat(sourcePath);
if (stat.isDirectory()) {
// Copy directory
await fs.ensureDir(destPath);
await fs.copy(sourcePath, destPath);
}
else {
// Copy file
await fs.ensureDir(path.dirname(destPath));
await fs.copy(sourcePath, destPath);
}
}
}
}
catch (error) {
// Log warning but continue with other files
console.log(chalk_1.default.yellow(`ā ļø Warning: Could not copy pattern '${pattern}': ${error.message}`));
}
}
}
async cleanup(projectPath) {
// Remove the temporary node_modules
const nodeModulesPath = path.join(projectPath, 'node_modules');
if (await fs.pathExists(nodeModulesPath)) {
await fs.remove(nodeModulesPath);
}
// Remove package-lock.json if it exists
const packageLockPath = path.join(projectPath, 'package-lock.json');
if (await fs.pathExists(packageLockPath)) {
await fs.remove(packageLockPath);
}
}
async updatePackageJson(projectPath, projectName, templateType) {
const packageJsonPath = path.join(projectPath, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);
packageJson.name = projectName;
packageJson.description = `${templateType.toUpperCase()} project: ${projectName}`;
// Remove the bin field if it exists (this is for CLI packages)
if (packageJson.bin) {
delete packageJson.bin;
}
// Ensure CLI scripts are included
if (!packageJson.scripts) {
packageJson.scripts = {};
}
// Add CLI scripts if they don't exist
if (!packageJson.scripts['generate:module']) {
packageJson.scripts['generate:module'] = './scripts/generate-module.sh';
}
if (!packageJson.scripts['cli']) {
packageJson.scripts['cli'] = './scripts/cli-commands.sh';
}
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
}
}
async createGitignore(projectPath) {
const gitignoreContent = `
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build outputs
dist/
build/
*.tsbuildinfo
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# Firebase
.firebase/
firebase-debug.log
firestore-debug.log
ui-debug.log
# Temporary folders
tmp/
temp/
`.trim();
await fs.writeFile(path.join(projectPath, '.gitignore'), gitignoreContent);
}
async createReadme(projectPath, projectName, templateType) {
const templateTitle = templateType.toUpperCase();
const readmeContent = `# ${projectName}
A Node.js ${templateType} project built with TypeScript and Firebase Functions.
## Features
- Express.js server with TypeScript
- Firebase Functions integration
- Socket.io for real-time communication
- MongoDB with Mongoose
- Authentication system
- Email notifications
- Cron jobs
- Audit logging
## Getting Started
1. Install dependencies:
\`\`\`bash
npm install
\`\`\`
2. Set up environment variables:
- Copy \`.env.example\` to \`.env.development\`
- Set \`NODE_ENV=development\` in \`.env.development\`
- Update the values according to your configuration
3. Start development server:
\`\`\`bash
npm start
\`\`\`
## Available Scripts
- \`npm start\` - Start development server
- \`npm run build:dev\` - Build for development
- \`npm run build:prod\` - Build for production
- \`npm run deploy\` - Deploy to Firebase
## Project Structure
\`\`\`
src/
āāā config/ # Configuration files
āāā controllers/ # Route controllers
āāā entities/ # Database entities
āāā helpers/ # Utility functions
āāā routes/ # API routes
āāā schemas/ # Validation schemas
āāā server/ # Server setup
āāā services/ # Business logic
\`\`\`
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test thoroughly
5. Submit a pull request
## License
MIT
`;
await fs.writeFile(path.join(projectPath, 'README.md'), readmeContent);
}
async makeScriptsExecutable(projectPath) {
const scriptsDir = path.join(projectPath, 'scripts');
if (await fs.pathExists(scriptsDir)) {
try {
const scripts = await fs.readdir(scriptsDir);
for (const script of scripts) {
if (script.endsWith('.sh')) {
const scriptPath = path.join(scriptsDir, script);
// Make script executable (chmod 755)
await fs.chmod(scriptPath, 0o755);
console.log(chalk_1.default.blue(`š§ Made executable: ${script}`));
}
}
}
catch (error) {
console.log(chalk_1.default.yellow(`ā ļø Warning: Could not make scripts executable: ${error.message}`));
}
}
}
async processTemplate(projectPath, projectName) {
try {
console.log(chalk_1.default.blue(`š§ Processing template for project: ${projectName}`));
// Change to project directory
const originalCwd = process.cwd();
process.chdir(projectPath);
// Run the template processor
(0, child_process_1.execSync)(`npm run cli process:template ${projectName}`, {
stdio: 'inherit'
});
// Change back to original directory
process.chdir(originalCwd);
console.log(chalk_1.default.green(`ā
Template processed successfully`));
}
catch (error) {
console.log(chalk_1.default.yellow(`ā ļø Warning: Could not process template: ${error.message}`));
console.log(chalk_1.default.yellow(` You can manually run: npm run cli process:template ${projectName}`));
}
}
/**
* Sync the generated project's .env.example with the latest one from the installed template package
* and replace placeholders with project-specific values.
*/
async syncEnvExampleFromTemplate(projectPath, projectName, templateType) {
try {
const templatePath = path.join(projectPath, 'node_modules', '@diagramers', templateType);
const sourceEnvPath = path.join(templatePath, '.env.example');
const destEnvPath = path.join(projectPath, '.env.example');
if (await fs.pathExists(sourceEnvPath)) {
let envContent = await fs.readFile(sourceEnvPath, 'utf8');
const dbName = this.deriveDbName(projectName);
envContent = envContent.replace(/DB_NAME_PLACEHOLDER/g, dbName);
await fs.writeFile(destEnvPath, envContent);
console.log(chalk_1.default.blue('š Synchronized .env.example from @diagramers/api template'));
}
else {
console.log(chalk_1.default.gray('ā¹ļø No .env.example found in template package; keeping generated one'));
}
}
catch (error) {
console.log(chalk_1.default.yellow(`ā ļø Warning: Could not sync .env.example from template: ${error.message}`));
}
}
deriveDbName(projectName) {
return projectName
.toLowerCase()
.replace(/[^a-zA-Z0-9]/g, '_')
.replace(/_+/g, '_')
.replace(/^_|_$/g, '');
}
}
exports.ProjectInitializer = ProjectInitializer;
//# sourceMappingURL=project-initializer.js.map