@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
348 lines (338 loc) • 13.4 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.createProject = createProject;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const prompts_1 = __importDefault(require("prompts"));
const chalk_1 = __importDefault(require("chalk"));
const framework_1 = require("../utils/framework");
const monorepo_1 = require("../utils/monorepo");
const react_1 = require("../templates/frontend/react");
const vue_1 = require("../templates/frontend/vue");
const svelte_1 = require("../templates/frontend/svelte");
const spinner_1 = require("../utils/spinner");
/**
* Creates a new Re-Shell project or workspace
*
* @param name - Name of the project/workspace
* @param options - Additional options for project creation
* @version 0.2.5
*/
async function createProject(name, options) {
// Check if we're in a monorepo
const monorepoRoot = await (0, monorepo_1.findMonorepoRoot)();
const inMonorepo = !!monorepoRoot;
// Determine if this is a monorepo project creation or workspace creation
const isWorkspaceCreation = inMonorepo || options.type;
if (isWorkspaceCreation) {
await createWorkspace(name, options, monorepoRoot);
}
else {
await createMonorepoProject(name, options);
}
}
/**
* Creates a new workspace (app/package/lib/tool) in an existing monorepo
*/
async function createWorkspace(name, options, monorepoRoot) {
const { team, org = 're-shell', description, framework, packageManager = 'pnpm', type = 'app', port = '5173', route, spinner, } = options;
const normalizedName = name.toLowerCase().replace(/\s+/g, '-');
const rootPath = monorepoRoot || process.cwd();
console.log(chalk_1.default.cyan(`Creating ${type} "${normalizedName}"...`));
// Stop spinner for interactive prompts
if (spinner) {
spinner.stop();
}
// Interactive prompts for missing options
const responses = await (0, prompts_1.default)([
{
type: !framework ? 'select' : null,
name: 'framework',
message: 'Select a framework:',
choices: (0, framework_1.getFrameworkChoices)(),
initial: 1, // Default to react-ts
},
{
type: type === 'app' && !port ? 'text' : null,
name: 'port',
message: 'Development server port:',
initial: '5173',
validate: (value) => {
const num = parseInt(value);
return num > 0 && num < 65536 ? true : 'Port must be between 1 and 65535';
},
},
{
type: type === 'app' && !route ? 'text' : null,
name: 'route',
message: 'Route path:',
initial: `/${normalizedName}`,
validate: (value) => (value.startsWith('/') ? true : 'Route must start with /'),
},
]);
// Restart spinner for file operations
if (spinner) {
spinner.start();
spinner.setText(`Creating ${type} files...`);
(0, spinner_1.flushOutput)();
}
// Merge responses with options
const finalFramework = framework || responses.framework || 'react-ts';
const finalPort = port || responses.port || '5173';
const finalRoute = route || responses.route || `/${normalizedName}`;
// Validate framework
if (!(0, framework_1.validateFramework)(finalFramework)) {
throw new Error(`Unsupported framework: ${finalFramework}`);
}
// Determine workspace path based on type
const typeDir = type === 'app' ? 'apps' : type === 'package' ? 'packages' : type === 'lib' ? 'libs' : 'tools';
const workspacePath = path.join(rootPath, typeDir, normalizedName);
// Check if directory already exists and handle it gracefully
if (fs.existsSync(workspacePath)) {
if (spinner)
spinner.stop();
const { action } = await (0, prompts_1.default)({
type: 'select',
name: 'action',
message: `Directory "${normalizedName}" already exists in ${typeDir}/. What would you like to do?`,
choices: [
{ title: 'Overwrite existing directory', value: 'overwrite' },
{ title: 'Cancel', value: 'cancel' },
],
initial: 0,
});
if (action === 'cancel') {
console.log(chalk_1.default.yellow('Operation cancelled.'));
return;
}
if (action === 'overwrite') {
if (spinner) {
spinner.start();
spinner.setText('Removing existing directory...');
(0, spinner_1.flushOutput)();
}
await fs.remove(workspacePath);
}
if (spinner) {
spinner.start();
spinner.setText(`Creating ${type} files...`);
(0, spinner_1.flushOutput)();
}
}
// Get framework configuration
const frameworkConfig = (0, framework_1.getFrameworkConfig)(finalFramework);
// Create template context
const templateContext = {
name,
normalizedName,
framework: finalFramework,
hasTypeScript: frameworkConfig.hasTypeScript || false,
port: finalPort,
route: type === 'app' ? finalRoute : undefined,
org,
team,
description: description || `${name} - A ${frameworkConfig.displayName} ${type}`,
packageManager,
};
// Generate files using appropriate template
const template = createTemplate(frameworkConfig, templateContext);
const files = await template.generateFiles();
// Create workspace directory
await fs.ensureDir(workspacePath);
// Write all generated files
for (const file of files) {
const filePath = path.join(workspacePath, file.path);
await fs.ensureDir(path.dirname(filePath));
await fs.writeFile(filePath, file.content);
if (file.executable) {
await fs.chmod(filePath, '755');
}
}
console.log(chalk_1.default.green(`✓ ${type.charAt(0).toUpperCase() + type.slice(1)} "${normalizedName}" created successfully!`));
console.log(chalk_1.default.gray(`Path: ${path.relative(process.cwd(), workspacePath)}`));
console.log('\nNext steps:');
console.log(` 1. cd ${path.relative(process.cwd(), workspacePath)}`);
console.log(` 2. ${packageManager} install`);
console.log(` 3. ${packageManager} run dev`);
}
/**
* Creates a new monorepo project (legacy function for backward compatibility)
*/
async function createMonorepoProject(name, options) {
const { team, org = 're-shell', description = `${name} - A Re-Shell microfrontend project`, spinner, } = options;
// Normalize name to kebab-case for consistency
const normalizedName = name.toLowerCase().replace(/\s+/g, '-');
console.log(chalk_1.default.cyan(`Creating Re-Shell project "${normalizedName}"...`));
// Stop spinner for interactive prompts
if (spinner) {
spinner.stop();
}
// Ask for additional information if not provided
const responses = await (0, prompts_1.default)([
{
type: options.template ? null : 'select',
name: 'template',
message: 'Select a template:',
choices: [
{ title: 'React', value: 'react' },
{ title: 'React with TypeScript', value: 'react-ts' },
],
initial: 1, // Default to react-ts
},
{
type: options.packageManager ? null : 'select',
name: 'packageManager',
message: 'Select a package manager:',
choices: [
{ title: 'npm', value: 'npm' },
{ title: 'yarn', value: 'yarn' },
{ title: 'pnpm', value: 'pnpm' },
],
initial: 2, // Default to pnpm
},
]);
// Merge responses with options
const finalOptions = {
...options,
template: options.template || responses.template,
packageManager: options.packageManager || responses.packageManager,
};
// Restart spinner for file operations
if (spinner) {
spinner.start();
spinner.setText('Creating project structure...');
(0, spinner_1.flushOutput)();
}
// Create project structure
const projectPath = path.resolve(process.cwd(), normalizedName);
// Check if directory already exists
if (fs.existsSync(projectPath)) {
throw new Error(`Directory already exists: ${projectPath}`);
}
// Create directory structure
fs.mkdirSync(projectPath);
fs.mkdirSync(path.join(projectPath, 'apps'));
fs.mkdirSync(path.join(projectPath, 'packages'));
fs.mkdirSync(path.join(projectPath, 'docs'));
// Create package.json for the project
const packageJson = {
name: normalizedName,
version: '0.1.0',
description,
private: true,
workspaces: ['apps/*', 'packages/*'],
scripts: {
dev: `${finalOptions.packageManager} run --parallel -r dev`,
build: `${finalOptions.packageManager} run --parallel -r build`,
lint: `${finalOptions.packageManager} run --parallel -r lint`,
test: `${finalOptions.packageManager} run --parallel -r test`,
clean: `${finalOptions.packageManager} run --parallel -r clean`,
},
author: team || org,
license: 'MIT',
};
fs.writeFileSync(path.join(projectPath, 'package.json'), JSON.stringify(packageJson, null, 2));
// Create workspace config
if (finalOptions.packageManager === 'pnpm') {
fs.writeFileSync(path.join(projectPath, 'pnpm-workspace.yaml'), `packages:\n - 'apps/*'\n - 'packages/*'\n`);
}
// Create README.md
const readmeContent = `# ${normalizedName}
## Overview
A microfrontend project created with Re-Shell CLI.
## Project Structure
\`\`\`
${normalizedName}/
├── apps/ # Microfrontend applications
│ └── shell/ # Main shell application
├── packages/ # Shared libraries
└── docs/ # Documentation
\`\`\`
## Getting Started
### Installation
\`\`\`bash
# Install dependencies
${finalOptions.packageManager} install
\`\`\`
### Development
\`\`\`bash
# Start all applications in development mode
${finalOptions.packageManager} run dev
\`\`\`
### Building
\`\`\`bash
# Build all applications
${finalOptions.packageManager} run build
\`\`\`
## Adding Microfrontends
To add a new microfrontend to this project:
\`\`\`bash
re-shell add my-feature
\`\`\`
## Documentation
For more information, see the [Re-Shell documentation](https://github.com/your-org/re-shell)
`;
fs.writeFileSync(path.join(projectPath, 'README.md'), readmeContent);
console.log(chalk_1.default.green(`\nRe-Shell project "${normalizedName}" created successfully at ${projectPath}`));
console.log('\nNext steps:');
console.log(` 1. cd ${normalizedName}`);
console.log(` 2. ${finalOptions.packageManager} install`);
console.log(` 3. ${finalOptions.packageManager} run dev`);
console.log(` 4. re-shell add my-feature (to add your first microfrontend)`);
}
/**
* Creates appropriate template instance based on framework
*/
function createTemplate(framework, context) {
switch (framework.name) {
case 'react':
case 'react-ts':
return new react_1.ReactTemplate(framework, context);
case 'vue':
case 'vue-ts':
return new vue_1.VueTemplate(framework, context);
case 'svelte':
case 'svelte-ts':
return new svelte_1.SvelteTemplate(framework, context);
// Add more frameworks as templates are implemented
default:
// Fallback to React template for unsupported frameworks
return new react_1.ReactTemplate(framework, context);
}
}