diff --git a/README.md b/README.md index da46a7e..97a5b13 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Use it to prototype architectures, onboard teams faster, or spin up reproducible - ๐Ÿ›  Interactive wizard (or fully non-interactive via flags) - ๐Ÿ” Post-init extensibility: add services & plugins anytime - ๐Ÿ“ฆ Shared package (`packages/shared`) for cross-service JS utilities +- ๐Ÿ“š **Language-specific shared libraries** (Python packages, Go modules, Java libraries) - ๐Ÿงช Vitest test setup for the CLI itself - ๐ŸŒˆ Colorized dev logs & health probing for Node/frontend services - ๐Ÿ”ฅ Unified hot reload aggregator (`create-polyglot hot`) for Node, Next.js, Python (uvicorn), Go, and Java (Spring Boot) @@ -167,8 +168,11 @@ create-polyglot dev --docker | `create-polyglot init ` | Scaffold a new workspace (root invocation without `init` is deprecated). | | `create-polyglot add service ` | Add a service after init (`--type`, `--port`, `--yes`). | | `create-polyglot add plugin ` | Create plugin skeleton under `plugins/`. | +| `create-polyglot add lib ` | Create a shared library (`--type python|go|java`, `--yes`). | | `create-polyglot remove service ` | Remove a service from the workspace (`--keep-files`, `--yes`). | | `create-polyglot remove plugin ` | Remove a plugin from the workspace (`--keep-files`, `--yes`). | +| `create-polyglot remove lib ` | Remove a shared library from the workspace (`--keep-files`, `--yes`). | +| `create-polyglot libraries` / `libs` | List all shared libraries (`--json`). | | `create-polyglot dev [--docker]` | Run Node & frontend services locally or all via compose. | ## Init Options @@ -224,26 +228,52 @@ my-org/ ## Development Workflow 1. Scaffold with `init`. -2. (Optional) Add more services or plugins. +2. (Optional) Add more services, plugins, or shared libraries. 3. Run `create-polyglot dev` (local) or `create-polyglot dev --docker`. 4. Edit services under `services/`. 5. Extend infra / databases inside `compose.yaml`. -## Managing Services & Plugins +## Managing Services, Plugins & Shared Libraries ### Adding Components -Add services after initial scaffolding: +Add services, plugins, and shared libraries after initial scaffolding: ```bash +# Add services create-polyglot add service payments --type node --port 4100 + +# Add plugins create-polyglot add plugin analytics + +# Add shared libraries +create-polyglot add lib common-utils --type python +create-polyglot add lib shared-models --type go +create-polyglot add lib data-types --type java +``` + +### Listing Components +```bash +# List all services +create-polyglot services + +# List all shared libraries +create-polyglot libraries + +# Get JSON output +create-polyglot libs --json ``` ### Removing Components -Clean removal of services and plugins: +Clean removal of services, plugins, and shared libraries: ```bash # Remove a service (including files and configuration) create-polyglot remove service payments --yes +# Remove a shared library +create-polyglot remove lib common-utils + +# Keep files but remove from configuration +create-polyglot remove lib common-utils --keep-files + # Remove a plugin create-polyglot remove plugin analytics --yes @@ -305,22 +335,40 @@ If `--frontend-generator create-next-app` is supplied, the tool shells out to `n Example: ```jsonc { - "name": "my-org", + "name": "my-org", "preset": "none", "packageManager": "npm", "services": [ { "name": "node", "type": "node", "port": 3001, "path": "services/node" } - ] + ], + "sharedLibs": [ + { "name": "common-utils", "type": "python", "path": "packages/libs/common-utils", "createdAt": "2024-01-15T10:30:00.000Z" } + ], + "plugins": {} } ``` -Used by `add service` to assert uniqueness and regenerate `compose.yaml`. +Used by `add service` and `add lib` to assert uniqueness and regenerate `compose.yaml`. ## Plugins `create-polyglot add plugin ` scaffolds `plugins//index.js` with a hook skeleton (`afterInit`). Future releases will execute hooks automatically during lifecycle events. -## Shared Package +## Shared Libraries `packages/shared` shows cross-service Node utilities. Extend or add per-language shared modules. +For language-specific shared libraries: +```bash +# Create Python package +create-polyglot add lib utils --type python + +# Create Go module +create-polyglot add lib common --type go + +# Create Java library +create-polyglot add lib models --type java +``` + +See [Shared Libraries Guide](./docs/guide/shared-libraries.md) for detailed usage. + ## Force Overwrite If the target directory already exists, the CLI aborts unless `--force` is passed. Use with caution. @@ -335,8 +383,9 @@ Generates ESLint + Prettier base configs at the root. Extend rules per service i - Healthchecks and depends_on in `compose.yaml` - Additional generators (Remix, Astro, SvelteKit) - Automatic test harness & CI workflow template -- Language-specific shared libs (Python package, Go module) - Hot reload integration aggregator +- Service dependency management +- Cross-language testing utilities ## Contributing Contributions welcome! See `CONTRIBUTING.md` for guidelines. Please run tests before submitting a PR: diff --git a/bin/index.js b/bin/index.js index 3116051..e246f88 100755 --- a/bin/index.js +++ b/bin/index.js @@ -52,10 +52,10 @@ program // Additional commands must be registered before final parse. program .command('add') - .description('Add a new service or plugin') - .argument('', 'service | plugin') - .argument('', 'Name of the service or plugin') - .option('--type ', 'Service type (node|python|go|java|frontend|remix|astro|sveltekit)') + .description('Add a new service, plugin, or shared library') + .argument('', 'service | plugin | lib') + .argument('', 'Name of the service, plugin, or library') + .option('--type ', 'Service type (node|python|go|java|frontend|remix|astro|sveltekit) or Library type (python|go)') .option('--lang ', '(Deprecated) Alias of --type') .option('--port ', 'Service port') .option('--yes', 'Non-interactive defaults') @@ -92,8 +92,24 @@ program await addService(projectDir, { type, name, port }, opts); } else if (entity === 'plugin') { await scaffoldPlugin(projectDir, name); + } else if (entity === 'lib') { + let type = opts.type || opts.lang; + if (!opts.yes) { + const promptsMod = await import('prompts'); + const p = promptsMod.default; + if (!type) { + const ans = await p({ type: 'select', name: 'type', message: 'Library type:', choices: [ + { title: 'Python Package', value: 'python' }, + { title: 'Go Module', value: 'go' } + ] }); + type = ans.type; + } + } + if (!type) throw new Error('Library type required'); + const { scaffoldSharedLibrary } = await import('./lib/scaffold.js'); + await scaffoldSharedLibrary(projectDir, { type, name }, opts); } else { - console.error(chalk.red(`Unknown entity '${entity}'. Use service or plugin.`)); + console.error(chalk.red(`Unknown entity '${entity}'. Use service, plugin, or lib.`)); process.exit(1); } } catch (e) { @@ -104,10 +120,10 @@ program program .command('remove') - .description('Remove a service or plugin') - .argument('', 'service | plugin') - .argument('', 'Name of the service or plugin') - .option('--keep-files', 'Keep service files, only remove from configuration') + .description('Remove a service, plugin, or shared library') + .argument('', 'service | plugin | lib') + .argument('', 'Name of the service, plugin, or library') + .option('--keep-files', 'Keep service/library files, only remove from configuration') .option('--yes', 'Skip confirmation prompt') .action(async (entity, name, opts) => { const projectDir = process.cwd(); @@ -116,8 +132,11 @@ program await removeService(projectDir, name, opts); } else if (entity === 'plugin') { await removePlugin(projectDir, name, opts); + } else if (entity === 'lib') { + const { removeSharedLibrary } = await import('./lib/scaffold.js'); + await removeSharedLibrary(projectDir, name, opts); } else { - console.error(chalk.red(`Unknown entity '${entity}'. Use service or plugin.`)); + console.error(chalk.red(`Unknown entity '${entity}'. Use service, plugin, or lib.`)); process.exit(1); } } catch (e) { @@ -158,6 +177,37 @@ program } }); +program + .command('libraries') + .alias('libs') + .description('List shared libraries in the current workspace (table)') + .option('--json', 'Output raw JSON instead of table') + .action(async (opts) => { + try { + const cwd = process.cwd(); + const cfgPath = path.join(cwd, 'polyglot.json'); + if (!fs.existsSync(cfgPath)) { + console.log(chalk.red('polyglot.json not found. Run inside a generated workspace.')); + process.exit(1); + } + const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8')); + const libs = cfg.sharedLibs || []; + if (opts.json) { + console.log(JSON.stringify(libs, null, 2)); + } else { + if (libs.length === 0) { + console.log(chalk.yellow('No shared libraries found.')); + } else { + const { renderLibrariesTable } = await import('./lib/ui.js'); + renderLibrariesTable(libs, { title: 'Shared Libraries' }); + } + } + } catch (e) { + console.error(chalk.red('Failed to list libraries:'), e.message); + process.exit(1); + } + }); + program .command('admin') .description('Launch admin dashboard to monitor service status') diff --git a/bin/lib/scaffold.js b/bin/lib/scaffold.js index 39e3206..0c7129f 100644 --- a/bin/lib/scaffold.js +++ b/bin/lib/scaffold.js @@ -451,6 +451,11 @@ export async function scaffoldMonorepo(projectNameArg, options) { await fs.writeFile(path.join(sharedDir, 'src/index.js'), 'export function greet(name){return `Hello, ${name}`;}'); } + // Create shared libraries directory + const libsDir = path.join(projectDir, 'packages/libs'); + await fs.mkdirp(libsDir); + await fs.writeFile(path.join(libsDir, '.gitkeep'), '# Shared libraries directory\n# This directory contains language-specific shared libraries\n# Generated by create-polyglot\n'); + await fs.writeFile(path.join(projectDir, '.eslintrc.cjs'), 'module.exports={root:true,env:{node:true,es2022:true},extends:[\'eslint:recommended\',\'plugin:import/recommended\',\'prettier\'],parserOptions:{ecmaVersion:\'latest\',sourceType:\'module\'},rules:{}};\n'); await fs.writeJSON(path.join(projectDir, '.prettierrc'), { singleQuote: true, semi: true, trailingComma: 'es5' }, { spaces: 2 }); @@ -579,6 +584,7 @@ export async function scaffoldMonorepo(projectNameArg, options) { preset: options.preset || 'none', packageManager: options.packageManager, services: services.map(s => ({ name: s.name, type: s.type, port: s.port, path: `services/${s.name}` })), + sharedLibs: [], plugins: {} }; await fs.writeJSON(path.join(projectDir, 'polyglot.json'), polyglotConfig, { spaces: 2 }); @@ -1307,3 +1313,279 @@ export async function removePlugin(projectDir, pluginName, options = {}) { console.log(chalk.yellow('\n๐Ÿ“ญ No plugins remaining in the workspace')); } } + +// Shared Library Management Functions + +export async function scaffoldSharedLibrary(projectDir, { type, name }, options = {}) { + const configPath = path.join(projectDir, 'polyglot.json'); + if (!fs.existsSync(configPath)) { + throw new Error('polyglot.json not found. Are you in a create-polyglot project?'); + } + + const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); + + // Validate library name + if (!/^[a-zA-Z0-9._-]+$/.test(name)) { + throw new Error('Library name must contain only alphanumerics, dash, underscore, or dot'); + } + + // Check for existing library with same name + const existingLibs = config.sharedLibs || []; + if (existingLibs.find(lib => lib.name === name)) { + throw new Error(`Shared library '${name}' already exists`); + } + + const validTypes = ['python', 'go']; + if (!validTypes.includes(type)) { + throw new Error(`Invalid library type '${type}'. Must be one of: ${validTypes.join(', ')}`); + } + + console.log(chalk.cyanBright(`\n๐Ÿ“ฆ Creating ${type} shared library '${name}'...\n`)); + + // Call before:lib:create hook + await callHook('before:lib:create', { + name, + type, + projectDir, + config, + options + }); + + const libDir = path.join(projectDir, 'packages', 'libs', name); + await fs.mkdirp(libDir); + + const templateDir = path.join(__dirname, '..', '..', 'templates', 'libs', type); + + // Copy template files + const templateFiles = await fs.readdir(templateDir, { withFileTypes: true }); + for (const file of templateFiles) { + if (file.isFile()) { + let content = await fs.readFile(path.join(templateDir, file.name), 'utf-8'); + + // Replace template variables with appropriate naming conventions + const packageName = type === 'go' ? name.replace(/-/g, '_') : name; + content = content.replace(/\{\{name\}\}/g, packageName); + + // Handle special file name replacements + let targetFileName = file.name; + if (file.name.includes('{{name}}')) { + targetFileName = file.name.replace(/\{\{name\}\}/g, name); + } + + await fs.writeFile(path.join(libDir, targetFileName), content); + } else if (file.isDirectory()) { + // Recursively copy directories + await fs.copy(path.join(templateDir, file.name), path.join(libDir, file.name)); + } + } + + // Create README.md + const readmeContent = `# ${name} + +A ${type} shared library for the ${config.name} monorepo. + +## Description + +This shared library provides common utilities, models, and functions that can be used across multiple services in the monorepo. + +## Usage + +### ${type === 'python' ? 'Python' : type === 'go' ? 'Go' : 'Java'} + +${getUsageExample(type, name)} + +## Development + +${getDevelopmentInstructions(type)} + +## Generated by create-polyglot + +This library was generated using create-polyglot. To add more shared libraries: + +\`\`\`bash +npx create-polyglot add lib --type ${type} +\`\`\` +`; + + await fs.writeFile(path.join(libDir, 'README.md'), readmeContent); + + // Update polyglot.json + const newLib = { + name, + type, + path: `packages/libs/${name}`, + createdAt: new Date().toISOString() + }; + + config.sharedLibs = config.sharedLibs || []; + config.sharedLibs.push(newLib); + + await fs.writeJSON(configPath, config, { spaces: 2 }); + + // Call after:lib:create hook + await callHook('after:lib:create', { + name, + type, + projectDir, + libDir, + config, + library: newLib, + options + }); + + console.log(chalk.green(`\nโœ… Shared library '${name}' created successfully!`)); + console.log(chalk.cyan(`๐Ÿ“ Location: packages/libs/${name}`)); + + // Show usage suggestions + console.log(chalk.blue('\n๐Ÿ’ก Next steps:')); + console.log(chalk.gray(' โ€ข Add your shared code to the library')); + console.log(chalk.gray(' โ€ข Update services to import from this library')); + console.log(chalk.gray(` โ€ข Run 'npx create-polyglot libs' to see all libraries`)); +} + +function getUsageExample(type, name) { + switch (type) { + case 'python': + return `\`\`\`python +# Import utilities from the shared library +from ${name}.utils import format_response, validate_config +from ${name}.models import ServiceHealth, ErrorResponse + +# Use in your service +response = format_response({"message": "Hello"}, "success") +health = ServiceHealth("my-service", "healthy") +\`\`\``; + + case 'go': + return `\`\`\`go +// Import from the shared library +import "${name}" + +// Use in your service +response := ${name}.FormatResponse(data, "success", nil) +health := ${name}.NewServiceHealth("my-service", "healthy") +\`\`\``; + + default: + return '// Usage example not available for this type'; + } +} + +function getDevelopmentInstructions(type) { + switch (type) { + case 'python': + return `\`\`\`bash +# Install in editable mode +pip install -e . + +# Install development dependencies +pip install -e .[dev] + +# Run tests +pytest + +# Format code +black . + +# Type check +mypy . +\`\`\``; + + case 'go': + return `\`\`\`bash +# Initialize module (if needed) +go mod tidy + +# Run tests +go test ./... + +# Format code +go fmt ./... + +# Lint +golangci-lint run +\`\`\``; + + default: + return '// Development instructions not available for this type'; + } +} + +export async function removeSharedLibrary(projectDir, name, options = {}) { + const configPath = path.join(projectDir, 'polyglot.json'); + if (!fs.existsSync(configPath)) { + throw new Error('polyglot.json not found. Are you in a create-polyglot project?'); + } + + const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); + const libs = config.sharedLibs || []; + const libToRemove = libs.find(lib => lib.name === name); + + if (!libToRemove) { + throw new Error(`Shared library '${name}' not found`); + } + + // Confirmation prompt unless --yes + if (!options.yes) { + const { default: prompts } = await import('prompts'); + const response = await prompts({ + type: 'confirm', + name: 'confirm', + message: `Remove shared library '${name}' (${libToRemove.type})?`, + initial: false + }); + + if (!response.confirm) { + console.log(chalk.yellow('โŒ Cancelled')); + return; + } + } + + console.log(chalk.cyanBright(`\n๐Ÿ—‘๏ธ Removing shared library '${name}'...`)); + + // Call before:lib:remove hook + await callHook('before:lib:remove', { + name, + library: libToRemove, + projectDir, + config, + options + }); + + // Remove from config + config.sharedLibs = libs.filter(lib => lib.name !== name); + await fs.writeJSON(configPath, config, { spaces: 2 }); + + // Remove files unless --keep-files + if (!options.keepFiles) { + const libDir = path.join(projectDir, libToRemove.path); + if (await fs.pathExists(libDir)) { + await fs.remove(libDir); + console.log(chalk.gray(`๐Ÿ“ Removed directory: ${libToRemove.path}`)); + } + } else { + console.log(chalk.yellow(`๐Ÿ“ Kept files: ${libToRemove.path} (use --keep-files=false to remove)`)); + } + + // Call after:lib:remove hook + await callHook('after:lib:remove', { + name, + library: libToRemove, + projectDir, + config, + options + }); + + console.log(chalk.green(`โœ… Shared library '${name}' removed successfully`)); + + // Show remaining libraries + const remainingLibs = config.sharedLibs || []; + if (remainingLibs.length > 0) { + console.log(chalk.blue('\n๐Ÿ“š Remaining libraries:')); + for (const lib of remainingLibs) { + console.log(` ${chalk.bold(lib.name)} (${lib.type}) - ${chalk.gray(lib.path)}`); + } + } else { + console.log(chalk.yellow('\n๐Ÿ“ญ No shared libraries remaining in the workspace')); + } +} diff --git a/bin/lib/ui.js b/bin/lib/ui.js index 7bf4266..44de86f 100644 --- a/bin/lib/ui.js +++ b/bin/lib/ui.js @@ -48,6 +48,57 @@ export function renderServicesTable(services, { title = 'Services', showHeader = console.log(chalk.gray(`Total: ${services.length}`)); } +export function renderLibrariesTable(libraries, { title = 'Shared Libraries', showHeader = true } = {}) { + if (!libraries || !libraries.length) { + console.log(chalk.yellow('No shared libraries to display.')); + return; + } + const cols = [ + { key: 'name', label: 'Name' }, + { key: 'type', label: 'Type' }, + { key: 'path', label: 'Path' }, + { key: 'createdAt', label: 'Created' } + ]; + const rows = libraries.map(lib => ({ + name: chalk.bold.cyan(lib.name), + type: colorLibType(lib.type)(lib.type), + path: chalk.dim(lib.path), + createdAt: lib.createdAt ? chalk.gray(new Date(lib.createdAt).toLocaleDateString()) : chalk.gray('Unknown') + })); + + const termWidth = process.stdout.columns || 80; + const minWidthNeeded = cols.reduce((a,c)=>a + c.label.length + 5, 0); + if (termWidth < minWidthNeeded) { + console.table(libraries.map(lib => ({ + name: lib.name, + type: lib.type, + path: lib.path, + created: lib.createdAt ? new Date(lib.createdAt).toLocaleDateString() : 'Unknown' + }))); + return; + } + + const colWidths = cols.map(c => Math.max(c.label.length, ...rows.map(r => strip(r[c.key]).length)) + 2); + const totalWidth = colWidths.reduce((a,b)=>a+b,0) + cols.length + 1; + const top = 'โ”Œ' + colWidths.map(w => 'โ”€'.repeat(w)).join('โ”ฌ') + 'โ”'; + const sep = 'โ”œ' + colWidths.map(w => 'โ”€'.repeat(w)).join('โ”ผ') + 'โ”ค'; + const bottom = 'โ””' + colWidths.map(w => 'โ”€'.repeat(w)).join('โ”ด') + 'โ”˜'; + + console.log(chalk.magentaBright(`\n${title}`)); + console.log(top); + if (showHeader) { + const header = 'โ”‚' + cols.map((c,i)=>pad(chalk.bold.white(c.label), colWidths[i])).join('โ”‚') + 'โ”‚'; + console.log(header); + console.log(sep); + } + for (const r of rows) { + const line = 'โ”‚' + cols.map((c,i)=>pad(r[c.key], colWidths[i])).join('โ”‚') + 'โ”‚'; + console.log(line); + } + console.log(bottom); + console.log(chalk.gray(`Total: ${libraries.length}`)); +} + function pad(str, width) { const raw = strip(str); const diff = width - raw.length; @@ -69,6 +120,14 @@ function colorType(type) { } } +function colorLibType(type) { + switch(type) { + case 'python': return chalk.yellow.bold; + case 'go': return chalk.cyan.bold; + default: return chalk.white.bold; + } +} + export function printBoxMessage(lines, { color = chalk.blueBright } = {}) { const clean = lines.filter(Boolean); const width = Math.min(Math.max(...clean.map(l => l.length))+4, process.stdout.columns || 100); diff --git a/docs/guide/shared-libraries.md b/docs/guide/shared-libraries.md new file mode 100644 index 0000000..14f6cdb --- /dev/null +++ b/docs/guide/shared-libraries.md @@ -0,0 +1,283 @@ +# Shared Libraries in create-polyglot + +create-polyglot now supports generating and managing shared libraries across different programming languages. This feature enables code reuse and modularity across your polyglot monorepo services. + +## Overview + +Shared libraries allow you to create common utilities, models, and functions that can be used across multiple services in your monorepo. create-polyglot supports generating libraries for: + +- **Python packages** - Installable Python packages with setuptools/pip +- **Go modules** - Go modules that can be imported by Go services +- **Java libraries** - Maven-based JAR libraries for Java services + +## Quick Start + +### Creating a Shared Library + +```bash +# Create a Python shared library +npx create-polyglot add lib my-utils --type python + +# Create a Go shared library +npx create-polyglot add lib common --type go + +# Create a Java shared library +npx create-polyglot add lib shared-models --type java +``` + +### Listing Shared Libraries + +```bash +# List all shared libraries in a table +npx create-polyglot libraries + +# Get JSON output +npx create-polyglot libs --json +``` + +### Removing a Shared Library + +```bash +# Remove library (with confirmation) +npx create-polyglot remove lib my-utils + +# Remove without confirmation +npx create-polyglot remove lib my-utils --yes + +# Remove from config but keep files +npx create-polyglot remove lib my-utils --keep-files +``` + +## Language-Specific Usage + +### Python Libraries + +Python libraries are generated as installable packages using modern `pyproject.toml` configuration. + +**Structure:** +``` +packages/libs/my-utils/ +โ”œโ”€โ”€ pyproject.toml # Package configuration +โ”œโ”€โ”€ README.md # Documentation +โ”œโ”€โ”€ __init__.py # Package initialization +โ”œโ”€โ”€ utils.py # Utility functions +โ””โ”€โ”€ models.py # Data models +``` + +**Using in Services:** +```python +# Install the shared library (from service directory) +pip install -e ../packages/libs/my-utils + +# Import and use in your service +from my_utils.utils import format_response, validate_config +from my_utils.models import ServiceHealth + +response = format_response({"message": "Hello"}, "success") +health = ServiceHealth("my-service", "healthy") +``` + +**Development Commands:** +```bash +cd packages/libs/my-utils + +# Install in editable mode +pip install -e . + +# Install with dev dependencies +pip install -e .[dev] + +# Run tests +pytest + +# Format code +black . + +# Type checking +mypy . +``` + +### Go Libraries + +Go libraries are generated as Go modules that can be imported by Go services. + +**Structure:** +``` +packages/libs/common/ +โ”œโ”€โ”€ go.mod # Module definition +โ”œโ”€โ”€ README.md # Documentation +โ””โ”€โ”€ common.go # Library code with types and functions +``` + +**Using in Services:** +```go +// Add to your service's go.mod +go mod edit -require=common@v0.1.0 +go mod edit -replace=common=../packages/libs/common + +// Import and use in your service +import "common" + +response := common.FormatResponse(data, "success", nil) +health := common.NewServiceHealth("my-service", "healthy") +``` + +**Development Commands:** +```bash +cd packages/libs/common + +# Install dependencies +go mod tidy + +# Run tests +go test ./... + +# Format code +go fmt ./... +``` + +### Java Libraries + +Java libraries are generated as Maven projects that compile to JAR files. + +**Structure:** +``` +packages/libs/shared-models/ +โ”œโ”€โ”€ pom.xml # Maven configuration +โ”œโ”€โ”€ README.md # Documentation +โ””โ”€โ”€ src/main/java/com/polyglot/shared/ + โ”œโ”€โ”€ models/ + โ”‚ โ”œโ”€โ”€ Response.java # Response model + โ”‚ โ””โ”€โ”€ ServiceHealth.java # Health model + โ””โ”€โ”€ utils/ + โ””โ”€โ”€ SharedUtils.java # Utility functions +``` + +**Using in Services:** +```xml + + + com.polyglot + shared-models + 0.1.0 + +``` + +```java +// Import and use in your service +import com.polyglot.shared.models.Response; +import com.polyglot.shared.utils.SharedUtils; + +Response response = SharedUtils.formatResponse("Hello", "success", null); +``` + +**Development Commands:** +```bash +cd packages/libs/shared-models + +# Compile +mvn compile + +# Run tests +mvn test + +# Package as JAR +mvn package + +# Install to local repository +mvn install +``` + +## Configuration + +Shared libraries are tracked in your `polyglot.json` configuration: + +```json +{ + "name": "my-project", + "preset": "none", + "packageManager": "npm", + "services": [...], + "sharedLibs": [ + { + "name": "my-utils", + "type": "python", + "path": "packages/libs/my-utils", + "createdAt": "2024-01-15T10:30:00.000Z" + }, + { + "name": "common", + "type": "go", + "path": "packages/libs/common", + "createdAt": "2024-01-15T11:00:00.000Z" + } + ], + "plugins": {} +} +``` + +## Best Practices + +### Library Design + +1. **Keep libraries focused** - Each library should have a single responsibility +2. **Version your libraries** - Use semantic versioning for library releases +3. **Document your APIs** - Include comprehensive README and code documentation +4. **Write tests** - Include unit tests for library functionality +5. **Use consistent naming** - Follow language conventions for naming + +### Cross-Service Integration + +1. **Define clear interfaces** - Use consistent data models across services +2. **Handle errors gracefully** - Implement proper error handling and fallbacks +3. **Mock for testing** - Create mockable interfaces for easier service testing +4. **Version compatibility** - Ensure library changes don't break existing services + +### Development Workflow + +1. **Create library first** - Design shared functionality before duplicating code +2. **Test in isolation** - Verify library functionality independently +3. **Integrate incrementally** - Add library usage to services one at a time +4. **Monitor dependencies** - Track which services depend on which libraries + +## CLI Commands Reference + +| Command | Description | Example | +|---------|-------------|---------| +| `add lib --type ` | Create a new shared library | `add lib utils --type python` | +| `libraries` / `libs` | List all shared libraries | `libraries --json` | +| `remove lib ` | Remove a shared library | `remove lib utils --yes` | + +## Plugin Hooks + +Shared library operations trigger plugin hooks for extensibility: + +- `before:lib:create` - Before creating a library +- `after:lib:create` - After creating a library +- `before:lib:remove` - Before removing a library +- `after:lib:remove` - After removing a library + +## Troubleshooting + +### Common Issues + +**Library not found when importing:** +- Ensure the library is properly installed/linked +- Check import paths and module names +- Verify the library was built successfully + +**Build errors in services:** +- Check that library dependencies are satisfied +- Ensure compatible language/framework versions +- Verify library code compiles independently + +**Import conflicts:** +- Use specific import statements +- Check for naming conflicts with other libraries +- Verify correct module paths + +### Getting Help + +- Run `npx create-polyglot libs` to see current libraries +- Check library README files for usage instructions +- Verify library builds independently before using in services \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5a1320f..6ab0b4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "create-polyglot", - "version": "1.18.0", + "version": "1.19.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-polyglot", - "version": "1.18.0", + "version": "1.19.0", "license": "MIT", "dependencies": { "chalk": "^5.6.2", @@ -337,6 +337,74 @@ } } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.21.5", "cpu": [ @@ -352,6 +420,312 @@ "node": ">=12" } }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@iconify-json/simple-icons": { "version": "1.2.54", "dev": true, @@ -381,6 +755,34 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", + "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", + "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.52.4", "cpu": [ @@ -393,6 +795,272 @@ "darwin" ] }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", + "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", + "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", + "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", + "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", + "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", + "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", + "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", + "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", + "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", + "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", + "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", + "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", + "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", + "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", + "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", + "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", + "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", + "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", + "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "license": "MIT" diff --git a/package.json b/package.json index 14315f0..4fe2546 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "create-polyglot", - "version": "1.18.0", + "version": "1.19.0", "description": "Scaffold polyglot microservice monorepos with built-in templates for Node, Python, Go, and more.", "main": "bin/index.js", "scripts": { diff --git a/templates/libs/go/README.md b/templates/libs/go/README.md new file mode 100644 index 0000000..d33a412 --- /dev/null +++ b/templates/libs/go/README.md @@ -0,0 +1,103 @@ +# {{name}} + +A shared Go module generated by create-polyglot for cross-service utilities and models. + +## Installation + +### Add to Go Module + +```bash +# From your service directory +go mod edit -require={{name}}@v0.1.0 +go mod edit -replace={{name}}=../packages/{{name}} +``` + +### Usage in Services + +```go +package main + +import ( + "fmt" + "{{name}}" +) + +func main() { + // Use shared utilities + response := {{name}}.FormatResponse(map[string]string{"key": "value"}, "success", nil) + + // Use shared models + health := {{name}}.NewServiceHealth("api", "healthy") + + // Use utility functions + config := map[string]interface{}{ + "database_url": "localhost:5432", + "api_key": "secret", + } + isValid := {{name}}.ValidateConfig(config, []string{"database_url", "api_key"}) + newID := {{name}}.GenerateID() + + fmt.Printf("Response: %+v\n", response) + fmt.Printf("Health: %s\n", health) + fmt.Printf("Config Valid: %v\n", isValid) + fmt.Printf("Generated ID: %s\n", newID) +} +``` + +## Development + +### Install Dependencies + +```bash +go mod tidy +``` + +### Running Tests + +```bash +go test ./... +``` + +### Build + +```bash +go build +``` + +## Structure + +- `go.mod` - Go module definition +- `{{name}}.go` - Main package with shared utilities and models +- `README.md` - This file + +## Adding to Services + +To use this shared module in your Go services: + +1. Add the module dependency: + ```bash + go mod edit -require={{name}}@v0.1.0 + go mod edit -replace={{name}}=../packages/{{name}} + ``` + +2. Import and use in your service code: + ```go + import "{{name}}" + ``` + +## Types and Functions + +### Types + +- `Response` - Standardized API response structure +- `ServiceHealth` - Service health status model +- `ErrorResponse` - Standardized error response structure + +### Functions + +- `FormatResponse(data, status, message)` - Create standardized API response +- `ValidateConfig(config, requiredKeys)` - Validate configuration keys +- `SafeJSONUnmarshal(data, v, defaultValue)` - Safe JSON unmarshaling with fallback +- `GenerateID()` - Generate new UUID string +- `NewServiceHealth(serviceName, status)` - Create new ServiceHealth instance +- `NewErrorResponse(code, message, details)` - Create new ErrorResponse instance \ No newline at end of file diff --git a/templates/libs/go/go.mod b/templates/libs/go/go.mod new file mode 100644 index 0000000..3a13e68 --- /dev/null +++ b/templates/libs/go/go.mod @@ -0,0 +1,7 @@ +module {{name}} + +go 1.21 + +require ( + github.com/google/uuid v1.4.0 +) \ No newline at end of file diff --git a/templates/libs/go/{{name}}.go b/templates/libs/go/{{name}}.go new file mode 100644 index 0000000..0775a0c --- /dev/null +++ b/templates/libs/go/{{name}}.go @@ -0,0 +1,103 @@ +// Package {{name}} provides shared utilities and models for create-polyglot monorepo +package {{name}} + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/google/uuid" +) + +// Response represents a standardized API response +type Response struct { + Status string `json:"status"` + Timestamp string `json:"timestamp"` + Data interface{} `json:"data"` + Message *string `json:"message,omitempty"` +} + +// ServiceHealth represents the health status of a service +type ServiceHealth struct { + ID string `json:"id"` + ServiceName string `json:"service_name"` + Status string `json:"status"` // "healthy", "degraded", "unhealthy" + Version *string `json:"version,omitempty"` + Uptime *float64 `json:"uptime,omitempty"` + LastCheck *time.Time `json:"last_check,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// ErrorResponse represents a standardized error response +type ErrorResponse struct { + ID string `json:"id"` + ErrorCode string `json:"error_code"` + ErrorMessage string `json:"error_message"` + Details map[string]interface{} `json:"details,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// FormatResponse creates a standardized API response +func FormatResponse(data interface{}, status string, message *string) Response { + return Response{ + Status: status, + Timestamp: time.Now().UTC().Format(time.RFC3339), + Data: data, + Message: message, + } +} + +// ValidateConfig checks that all required keys exist in the configuration map +func ValidateConfig(config map[string]interface{}, requiredKeys []string) bool { + for _, key := range requiredKeys { + if _, exists := config[key]; !exists { + return false + } + } + return true +} + +// SafeJSONUnmarshal safely unmarshals JSON with fallback to default value +func SafeJSONUnmarshal(data []byte, v interface{}, defaultValue interface{}) interface{} { + if err := json.Unmarshal(data, v); err != nil { + return defaultValue + } + return v +} + +// GenerateID generates a new UUID string +func GenerateID() string { + return uuid.New().String() +} + +// NewServiceHealth creates a new ServiceHealth instance +func NewServiceHealth(serviceName, status string) ServiceHealth { + now := time.Now().UTC() + return ServiceHealth{ + ID: GenerateID(), + ServiceName: serviceName, + Status: status, + CreatedAt: now, + UpdatedAt: now, + } +} + +// NewErrorResponse creates a new ErrorResponse instance +func NewErrorResponse(errorCode, errorMessage string, details map[string]interface{}) ErrorResponse { + now := time.Now().UTC() + return ErrorResponse{ + ID: GenerateID(), + ErrorCode: errorCode, + ErrorMessage: errorMessage, + Details: details, + CreatedAt: now, + UpdatedAt: now, + } +} + +// String returns a string representation of the ServiceHealth +func (sh ServiceHealth) String() string { + return fmt.Sprintf("ServiceHealth{ID: %s, Service: %s, Status: %s}", sh.ID, sh.ServiceName, sh.Status) +} \ No newline at end of file diff --git a/templates/libs/python/README.md b/templates/libs/python/README.md new file mode 100644 index 0000000..ebe1b54 --- /dev/null +++ b/templates/libs/python/README.md @@ -0,0 +1,74 @@ +# {{name}} + +A shared Python library generated by create-polyglot for cross-service utilities and models. + +## Installation + +### Development Install + +```bash +# From the monorepo root +pip install -e packages/{{name}} +``` + +### Usage in Services + +```python +from {{name}} import format_response, ServiceHealth +from {{name}}.utils import validate_config, generate_id + +# Use shared utilities +response = format_response({"key": "value"}, "success", "Operation completed") + +# Use shared models +health = ServiceHealth(service_name="api", status="healthy", version="1.0.0") + +# Use utility functions +config_valid = validate_config(config_dict, ["database_url", "api_key"]) +new_id = generate_id() +``` + +## Development + +### Install Development Dependencies + +```bash +pip install -e ".[dev]" +``` + +### Running Tests + +```bash +pytest +``` + +### Code Formatting + +```bash +black . +flake8 . +mypy . +``` + +## Structure + +- `{{name}}/` - Main package directory + - `__init__.py` - Package initialization and exports + - `utils.py` - Shared utility functions + - `models.py` - Shared data models and classes +- `pyproject.toml` - Package configuration +- `README.md` - This file + +## Adding to Services + +To use this shared library in your Python services: + +1. Add the dependency to your service's `requirements.txt`: + ``` + -e ../packages/{{name}} + ``` + +2. Import and use in your service code: + ```python + from {{name}} import format_response + ``` \ No newline at end of file diff --git a/templates/libs/python/__init__.py b/templates/libs/python/__init__.py new file mode 100644 index 0000000..3c1b1ca --- /dev/null +++ b/templates/libs/python/__init__.py @@ -0,0 +1,8 @@ +""" +{{name}} - A shared Python library for create-polyglot monorepo +""" + +__version__ = "0.1.0" + +from .utils import * +from .models import * \ No newline at end of file diff --git a/templates/libs/python/models.py b/templates/libs/python/models.py new file mode 100644 index 0000000..19130a7 --- /dev/null +++ b/templates/libs/python/models.py @@ -0,0 +1,39 @@ +""" +Shared data models for {{name}} +""" + +from typing import Optional +from dataclasses import dataclass +import datetime + + +@dataclass +class BaseModel: + """Base model with common fields.""" + id: Optional[str] = None + created_at: Optional[datetime.datetime] = None + updated_at: Optional[datetime.datetime] = None + + def __post_init__(self): + if self.created_at is None: + self.created_at = datetime.datetime.utcnow() + if self.updated_at is None: + self.updated_at = datetime.datetime.utcnow() + + +@dataclass +class ServiceHealth(BaseModel): + """Model for service health status.""" + service_name: str = "" + status: str = "unknown" # "healthy", "degraded", "unhealthy" + version: Optional[str] = None + uptime: Optional[float] = None + last_check: Optional[datetime.datetime] = None + + +@dataclass +class ErrorResponse(BaseModel): + """Model for standardized error responses.""" + error_code: str = "" + error_message: str = "" + details: Optional[dict] = None \ No newline at end of file diff --git a/templates/libs/python/pyproject.toml b/templates/libs/python/pyproject.toml new file mode 100644 index 0000000..bd8dbd6 --- /dev/null +++ b/templates/libs/python/pyproject.toml @@ -0,0 +1,48 @@ +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "{{name}}" +version = "0.1.0" +description = "A shared Python library for create-polyglot monorepo" +readme = "README.md" +requires-python = ">=3.8" +authors = [ + {name = "Generated by create-polyglot"} +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] +dependencies = [ + "typing-extensions>=4.0.0" +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", + "black>=22.0.0", + "flake8>=4.0.0", + "mypy>=0.950" +] + +[tool.setuptools.packages.find] +where = ["."] +include = ["{{name}}*"] + +[tool.black] +line-length = 88 +target-version = ["py38"] + +[tool.mypy] +python_version = "3.8" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true \ No newline at end of file diff --git a/templates/libs/python/utils.py b/templates/libs/python/utils.py new file mode 100644 index 0000000..d020b8d --- /dev/null +++ b/templates/libs/python/utils.py @@ -0,0 +1,37 @@ +""" +Shared utility functions for {{name}} +""" + +from typing import Any, Dict, Optional +import json +import datetime + + +def format_response(data: Any, status: str = "success", message: Optional[str] = None) -> Dict[str, Any]: + """Format a standardized API response.""" + response = { + "status": status, + "timestamp": datetime.datetime.utcnow().isoformat(), + "data": data + } + if message: + response["message"] = message + return response + + +def validate_config(config: Dict[str, Any], required_keys: list) -> bool: + """Validate that required keys exist in configuration.""" + return all(key in config for key in required_keys) + + +def safe_json_loads(json_str: str, default: Any = None) -> Any: + """Safely parse JSON string with fallback.""" + try: + return json.loads(json_str) + except (json.JSONDecodeError, TypeError): + return default + + +def generate_id() -> str: + """Generate a simple timestamp-based ID.""" + return f"{datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S_%f')}" \ No newline at end of file diff --git a/tests/remove-command.test.js b/tests/remove-command.test.js index e56cec3..f04f15f 100644 --- a/tests/remove-command.test.js +++ b/tests/remove-command.test.js @@ -221,7 +221,7 @@ networks: }); expect(result.exitCode).toBe(1); - expect(result.stderr).toContain('Unknown entity \'unknown\'. Use service or plugin.'); + expect(result.stderr).toContain('Unknown entity \'unknown\'. Use service, plugin, or lib.'); }); });