mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 20:56:42 +02:00
refactor: restructure
monorepo with apps/ and services/ directories
This commit is contained in:
parent
25824ed0ac
commit
ff80aeec1f
4062 changed files with 2592 additions and 1278 deletions
130
apps/picture/packages/mobile-ui/cli/src/commands/add.ts
Normal file
130
apps/picture/packages/mobile-ui/cli/src/commands/add.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import prompts from 'prompts';
|
||||
import { getComponent, resolveDependencies } from '../utils/registry';
|
||||
import { copyComponent, componentExists } from '../utils/files';
|
||||
import { getDestinationPath } from '../utils/paths';
|
||||
|
||||
export async function addCommand(componentKey: string, options: { yes?: boolean }) {
|
||||
const spinner = ora();
|
||||
|
||||
try {
|
||||
// Load component info
|
||||
spinner.start('Loading component info...');
|
||||
const item = await getComponent(componentKey);
|
||||
|
||||
if (!item) {
|
||||
spinner.fail(chalk.red(`Component "${componentKey}" not found`));
|
||||
console.log(chalk.dim('\nRun `memoro-ui list` to see available components'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { component, category } = item;
|
||||
spinner.succeed(`Found component: ${chalk.cyan(component.name)}`);
|
||||
|
||||
// Get destination path
|
||||
const destination = getDestinationPath();
|
||||
console.log(chalk.dim(`Destination: ${destination}`));
|
||||
|
||||
// Check if component already exists
|
||||
const exists = await componentExists(component, category, destination);
|
||||
if (exists && !options.yes) {
|
||||
const { overwrite } = await prompts({
|
||||
type: 'confirm',
|
||||
name: 'overwrite',
|
||||
message: `Component ${component.name} already exists. Overwrite?`,
|
||||
initial: false,
|
||||
});
|
||||
|
||||
if (!overwrite) {
|
||||
console.log(chalk.yellow('Cancelled'));
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve dependencies
|
||||
spinner.start('Resolving dependencies...');
|
||||
const dependencies = await resolveDependencies(componentKey);
|
||||
spinner.succeed(
|
||||
dependencies.length > 0
|
||||
? `Dependencies: ${chalk.cyan(dependencies.join(', '))}`
|
||||
: 'No dependencies'
|
||||
);
|
||||
|
||||
// Ask to install dependencies
|
||||
const componentsToInstall = [componentKey, ...dependencies];
|
||||
const newComponents = [];
|
||||
|
||||
for (const dep of dependencies) {
|
||||
const depItem = await getComponent(dep);
|
||||
if (depItem) {
|
||||
const depExists = await componentExists(depItem.component, depItem.category, destination);
|
||||
if (!depExists) {
|
||||
newComponents.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newComponents.length > 0 && !options.yes) {
|
||||
console.log(
|
||||
chalk.yellow(`\nThe following dependencies will also be installed:\n`) +
|
||||
newComponents.map(c => ` - ${c}`).join('\n')
|
||||
);
|
||||
|
||||
const { installDeps } = await prompts({
|
||||
type: 'confirm',
|
||||
name: 'installDeps',
|
||||
message: 'Install dependencies?',
|
||||
initial: true,
|
||||
});
|
||||
|
||||
if (!installDeps) {
|
||||
console.log(chalk.yellow('Cancelled'));
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy dependencies first
|
||||
for (const dep of dependencies) {
|
||||
const depItem = await getComponent(dep);
|
||||
if (!depItem) continue;
|
||||
|
||||
const depExists = await componentExists(depItem.component, depItem.category, destination);
|
||||
if (depExists) {
|
||||
console.log(chalk.dim(` ✓ ${depItem.component.name} (already exists)`));
|
||||
continue;
|
||||
}
|
||||
|
||||
spinner.start(`Installing dependency: ${depItem.component.name}...`);
|
||||
await copyComponent(depItem.component, depItem.category, destination);
|
||||
spinner.succeed(`Installed ${chalk.green(depItem.component.name)}`);
|
||||
}
|
||||
|
||||
// Copy main component
|
||||
spinner.start(`Installing ${component.name}...`);
|
||||
const copiedFiles = await copyComponent(component, category, destination);
|
||||
spinner.succeed(`Installed ${chalk.green(component.name)}`);
|
||||
|
||||
// Show success message
|
||||
console.log(chalk.green('\n✅ Success!\n'));
|
||||
console.log(`${chalk.bold('Files added:')}`);
|
||||
copiedFiles.forEach(file => {
|
||||
const relativePath = file.replace(process.cwd() + '/', '');
|
||||
console.log(chalk.dim(` ${relativePath}`));
|
||||
});
|
||||
|
||||
console.log(`\n${chalk.bold('Import:')}`);
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
` import { ${component.name} } from '@/components/${category}/${component.name}';`
|
||||
)
|
||||
);
|
||||
|
||||
console.log(`\n${chalk.bold('Usage:')}`);
|
||||
console.log(chalk.dim(` See components/${category}/${component.name}/README.md for examples`));
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Failed to add component'));
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
63
apps/picture/packages/mobile-ui/cli/src/commands/list.ts
Normal file
63
apps/picture/packages/mobile-ui/cli/src/commands/list.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import chalk from 'chalk';
|
||||
import { getAllComponents } from '../utils/registry';
|
||||
import { componentExists } from '../utils/files';
|
||||
import { getDestinationPath } from '../utils/paths';
|
||||
|
||||
export async function listCommand(options: { category?: string }) {
|
||||
try {
|
||||
const components = await getAllComponents();
|
||||
const destination = getDestinationPath();
|
||||
|
||||
// Filter by category if specified
|
||||
const filtered = options.category
|
||||
? components.filter(c => c.category === options.category)
|
||||
: components;
|
||||
|
||||
if (filtered.length === 0) {
|
||||
console.log(chalk.yellow('No components found'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Group by category
|
||||
const grouped = filtered.reduce((acc, item) => {
|
||||
if (!acc[item.category]) {
|
||||
acc[item.category] = [];
|
||||
}
|
||||
acc[item.category].push(item);
|
||||
return acc;
|
||||
}, {} as Record<string, typeof filtered>);
|
||||
|
||||
console.log(chalk.bold.cyan('\n📦 Available Components\n'));
|
||||
|
||||
for (const [category, items] of Object.entries(grouped)) {
|
||||
console.log(chalk.bold(`${category.toUpperCase()}:`));
|
||||
|
||||
for (const { key, component } of items) {
|
||||
const exists = await componentExists(component, category, destination);
|
||||
const status = exists ? chalk.green('✓') : chalk.dim('○');
|
||||
const name = exists ? chalk.green(key) : chalk.white(key);
|
||||
const desc = chalk.dim(component.description);
|
||||
|
||||
console.log(` ${status} ${name}`);
|
||||
console.log(` ${desc}`);
|
||||
|
||||
if (component.dependencies.length > 0) {
|
||||
console.log(
|
||||
chalk.dim(` Dependencies: ${component.dependencies.join(', ')}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
console.log(chalk.dim('Legend:'));
|
||||
console.log(chalk.dim(` ${chalk.green('✓')} = Installed in this project`));
|
||||
console.log(chalk.dim(` ${chalk.dim('○')} = Not installed`));
|
||||
console.log('');
|
||||
console.log(chalk.dim(`Run ${chalk.cyan('memoro-ui add <component>')} to install a component`));
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Failed to list components:'), error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
35
apps/picture/packages/mobile-ui/cli/src/index.ts
Normal file
35
apps/picture/packages/mobile-ui/cli/src/index.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import { addCommand } from './commands/add';
|
||||
import { listCommand } from './commands/list';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('memoro-ui')
|
||||
.description('CLI tool for copying UI components into your app (shadcn-style)')
|
||||
.version('0.1.0');
|
||||
|
||||
// Add command
|
||||
program
|
||||
.command('add <component>')
|
||||
.description('Add a component to your project')
|
||||
.option('-y, --yes', 'Skip confirmation prompts')
|
||||
.action(addCommand);
|
||||
|
||||
// List command
|
||||
program
|
||||
.command('list')
|
||||
.description('List all available components')
|
||||
.option('-c, --category <category>', 'Filter by category (ui, navigation)')
|
||||
.action(listCommand);
|
||||
|
||||
// Parse arguments
|
||||
program.parse();
|
||||
|
||||
// Show help if no command provided
|
||||
if (!process.argv.slice(2).length) {
|
||||
program.outputHelp();
|
||||
}
|
||||
23
apps/picture/packages/mobile-ui/cli/src/types.ts
Normal file
23
apps/picture/packages/mobile-ui/cli/src/types.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
export type ComponentRegistry = {
|
||||
$schema?: string;
|
||||
name: string;
|
||||
version: string;
|
||||
components: {
|
||||
[category: string]: {
|
||||
[key: string]: ComponentInfo;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type ComponentInfo = {
|
||||
name: string;
|
||||
files: string[];
|
||||
category: string;
|
||||
dependencies: string[];
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type Config = {
|
||||
componentsPath: string;
|
||||
registryPath: string;
|
||||
};
|
||||
73
apps/picture/packages/mobile-ui/cli/src/utils/files.ts
Normal file
73
apps/picture/packages/mobile-ui/cli/src/utils/files.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { ComponentInfo } from '../types';
|
||||
import { getComponentsPath, ensureDir } from './paths';
|
||||
|
||||
/**
|
||||
* Copy a component's files to the destination
|
||||
*/
|
||||
export async function copyComponent(
|
||||
component: ComponentInfo,
|
||||
category: string,
|
||||
destinationBase: string
|
||||
): Promise<string[]> {
|
||||
const sourcePath = path.join(getComponentsPath(), category, component.name);
|
||||
const destPath = path.join(destinationBase, category, component.name);
|
||||
|
||||
// Ensure destination directory exists
|
||||
await ensureDir(destPath);
|
||||
|
||||
const copiedFiles: string[] = [];
|
||||
|
||||
// Copy each file
|
||||
for (const file of component.files) {
|
||||
const sourceFile = path.join(sourcePath, file);
|
||||
const destFile = path.join(destPath, file);
|
||||
|
||||
if (!await fs.pathExists(sourceFile)) {
|
||||
throw new Error(`Source file not found: ${sourceFile}`);
|
||||
}
|
||||
|
||||
await fs.copy(sourceFile, destFile, { overwrite: true });
|
||||
copiedFiles.push(destFile);
|
||||
}
|
||||
|
||||
// Also copy index.ts if it exists
|
||||
const indexFile = path.join(sourcePath, 'index.ts');
|
||||
if (await fs.pathExists(indexFile)) {
|
||||
const destIndex = path.join(destPath, 'index.ts');
|
||||
await fs.copy(indexFile, destIndex, { overwrite: true });
|
||||
copiedFiles.push(destIndex);
|
||||
}
|
||||
|
||||
return copiedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a component already exists at the destination
|
||||
*/
|
||||
export async function componentExists(
|
||||
component: ComponentInfo,
|
||||
category: string,
|
||||
destinationBase: string
|
||||
): Promise<boolean> {
|
||||
const destPath = path.join(destinationBase, category, component.name);
|
||||
return await fs.pathExists(destPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of files that would be copied
|
||||
*/
|
||||
export function getComponentFiles(
|
||||
component: ComponentInfo,
|
||||
category: string,
|
||||
destinationBase: string
|
||||
): string[] {
|
||||
const destPath = path.join(destinationBase, category, component.name);
|
||||
const files = component.files.map(file => path.join(destPath, file));
|
||||
|
||||
// Add index.ts
|
||||
files.push(path.join(destPath, 'index.ts'));
|
||||
|
||||
return files;
|
||||
}
|
||||
53
apps/picture/packages/mobile-ui/cli/src/utils/paths.ts
Normal file
53
apps/picture/packages/mobile-ui/cli/src/utils/paths.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
/**
|
||||
* Get the root directory of the memoro-ui package
|
||||
*/
|
||||
export function getPackageRoot(): string {
|
||||
// CLI is in packages/memoro-ui/cli/
|
||||
// Package root is packages/memoro-ui/
|
||||
return path.resolve(__dirname, '../../../');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the registry.json file
|
||||
*/
|
||||
export function getRegistryPath(): string {
|
||||
return path.join(getPackageRoot(), 'registry.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the components directory
|
||||
*/
|
||||
export function getComponentsPath(): string {
|
||||
return path.join(getPackageRoot(), 'components');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the destination path for components in the target app
|
||||
*/
|
||||
export function getDestinationPath(cwd: string = process.cwd()): string {
|
||||
// Check if we're in an app directory with a components folder
|
||||
const possiblePaths = [
|
||||
path.join(cwd, 'components'),
|
||||
path.join(cwd, 'app', 'components'),
|
||||
path.join(cwd, 'src', 'components'),
|
||||
];
|
||||
|
||||
for (const p of possiblePaths) {
|
||||
if (fs.existsSync(p)) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to components/ in current directory
|
||||
return path.join(cwd, 'components');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a directory exists, create if not
|
||||
*/
|
||||
export async function ensureDir(dirPath: string): Promise<void> {
|
||||
await fs.ensureDir(dirPath);
|
||||
}
|
||||
85
apps/picture/packages/mobile-ui/cli/src/utils/registry.ts
Normal file
85
apps/picture/packages/mobile-ui/cli/src/utils/registry.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import fs from 'fs-extra';
|
||||
import { ComponentRegistry, ComponentInfo } from '../types';
|
||||
import { getRegistryPath } from './paths';
|
||||
|
||||
/**
|
||||
* Load the component registry
|
||||
*/
|
||||
export async function loadRegistry(): Promise<ComponentRegistry> {
|
||||
const registryPath = getRegistryPath();
|
||||
|
||||
if (!await fs.pathExists(registryPath)) {
|
||||
throw new Error(`Registry not found at ${registryPath}`);
|
||||
}
|
||||
|
||||
const content = await fs.readFile(registryPath, 'utf-8');
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific component from the registry
|
||||
*/
|
||||
export async function getComponent(
|
||||
componentKey: string
|
||||
): Promise<{ key: string; component: ComponentInfo; category: string } | null> {
|
||||
const registry = await loadRegistry();
|
||||
|
||||
for (const [category, components] of Object.entries(registry.components)) {
|
||||
if (componentKey in components) {
|
||||
return {
|
||||
key: componentKey,
|
||||
component: components[componentKey],
|
||||
category,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all components from the registry
|
||||
*/
|
||||
export async function getAllComponents(): Promise<
|
||||
Array<{ key: string; component: ComponentInfo; category: string }>
|
||||
> {
|
||||
const registry = await loadRegistry();
|
||||
const components: Array<{ key: string; component: ComponentInfo; category: string }> = [];
|
||||
|
||||
for (const [category, categoryComponents] of Object.entries(registry.components)) {
|
||||
for (const [key, component] of Object.entries(categoryComponents)) {
|
||||
components.push({ key, component, category });
|
||||
}
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve dependencies for a component recursively
|
||||
*/
|
||||
export async function resolveDependencies(
|
||||
componentKey: string,
|
||||
visited = new Set<string>()
|
||||
): Promise<string[]> {
|
||||
if (visited.has(componentKey)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
visited.add(componentKey);
|
||||
|
||||
const item = await getComponent(componentKey);
|
||||
if (!item) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const deps = item.component.dependencies || [];
|
||||
const allDeps = [...deps];
|
||||
|
||||
for (const dep of deps) {
|
||||
const subDeps = await resolveDependencies(dep, visited);
|
||||
allDeps.push(...subDeps);
|
||||
}
|
||||
|
||||
return [...new Set(allDeps)];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue