Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:04:14 +08:00
commit 70c36b5eff
248 changed files with 47482 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
# Basic Gluegun CLI Example
A simple example demonstrating core Gluegun CLI patterns.
## Structure
```
basic-cli/
├── src/
│ ├── cli.ts # CLI entry point
│ ├── commands/
│ │ ├── hello.ts # Simple command
│ │ └── generate.ts # Template generator
│ └── extensions/
│ └── helpers.ts # Custom toolbox extension
├── templates/
│ └── component.ts.ejs # Example template
├── package.json
└── tsconfig.json
```
## Features
- Basic command structure
- Template generation
- Custom toolbox extensions
- TypeScript support
## Installation
```bash
npm install
npm run build
```
## Usage
```bash
# Run hello command
./bin/cli hello World
# Generate from template
./bin/cli generate MyComponent
# Show help
./bin/cli --help
```
## Commands
### hello
Simple greeting command demonstrating parameters.
```bash
./bin/cli hello [name]
```
### generate
Generate files from templates.
```bash
./bin/cli generate <name>
```
## Key Patterns
### 1. Command Structure
```typescript
const command: GluegunCommand = {
name: 'hello',
run: async (toolbox) => {
const { print, parameters } = toolbox
const name = parameters.first || 'World'
print.success(`Hello, ${name}!`)
}
}
```
### 2. Template Generation
```typescript
await template.generate({
template: 'component.ts.ejs',
target: `src/components/${name}.ts`,
props: { name }
})
```
### 3. Custom Extensions
```typescript
toolbox.helpers = {
formatName: (name: string) => {
return name.charAt(0).toUpperCase() + name.slice(1)
}
}
```
## Learning Path
1. Start with `src/cli.ts` - CLI initialization
2. Review `src/commands/hello.ts` - Simple command
3. Study `src/commands/generate.ts` - Template usage
4. Explore `templates/component.ts.ejs` - EJS templates
5. Check `src/extensions/helpers.ts` - Custom toolbox
## Next Steps
- Add more commands
- Create complex templates
- Implement plugin system
- Add interactive prompts

View File

@@ -0,0 +1,27 @@
import { build } from 'gluegun'
/**
* Create the CLI and kick it off
*/
async function run(argv: string[] = process.argv) {
// Create a CLI runtime
const cli = build()
.brand('mycli')
.src(__dirname)
.plugins('./node_modules', { matching: 'mycli-*', hidden: true })
.help() // provides default --help command
.version() // provides default --version command
.create()
// Enable the following method if you'd like to skip loading one of these core extensions
// this can improve performance if they're not necessary for your project:
// .exclude(['meta', 'strings', 'print', 'filesystem', 'semver', 'system', 'prompt', 'http', 'template', 'patching', 'package-manager'])
// Run the CLI
const toolbox = await cli.run(argv)
// Send it back (for testing, mostly)
return toolbox
}
module.exports = { run }

View File

@@ -0,0 +1,88 @@
import { GluegunCommand } from 'gluegun'
/**
* Generate Command
* Demonstrates template generation and filesystem operations
*/
const command: GluegunCommand = {
name: 'generate',
alias: ['g', 'create'],
description: 'Generate a new component from template',
run: async (toolbox) => {
const { template, print, parameters, filesystem, strings } = toolbox
// Get component name
const name = parameters.first
if (!name) {
print.error('Component name is required')
print.info('Usage: mycli generate <ComponentName>')
return
}
// Convert to different cases
const pascalName = strings.pascalCase(name)
const kebabName = strings.kebabCase(name)
// Target directory
const targetDir = 'src/components'
const targetFile = `${targetDir}/${kebabName}.ts`
// Ensure directory exists
await filesystem.dir(targetDir)
// Check if file already exists
if (filesystem.exists(targetFile)) {
const overwrite = await toolbox.prompt.confirm(
`${targetFile} already exists. Overwrite?`
)
if (!overwrite) {
print.warning('Generation cancelled')
return
}
}
// Show spinner while generating
const spinner = print.spin(`Generating ${pascalName} component...`)
try {
// Generate from template
await template.generate({
template: 'component.ts.ejs',
target: targetFile,
props: {
name: pascalName,
kebabName,
timestamp: new Date().toISOString()
}
})
spinner.succeed(`Generated ${targetFile}`)
// Add to index if it exists
const indexPath = `${targetDir}/index.ts`
if (filesystem.exists(indexPath)) {
await filesystem.append(
indexPath,
`export { ${pascalName} } from './${kebabName}'\n`
)
print.info(`Added export to ${indexPath}`)
}
// Success message with details
print.success('Component generated successfully!')
print.info('')
print.info('Next steps:')
print.info(` 1. Edit ${targetFile}`)
print.info(` 2. Import in your app: import { ${pascalName} } from './components/${kebabName}'`)
} catch (error) {
spinner.fail('Generation failed')
print.error(error.message)
}
}
}
module.exports = command

View File

@@ -0,0 +1,41 @@
import { GluegunCommand } from 'gluegun'
/**
* Hello Command
* Demonstrates basic parameter handling and print utilities
*/
const command: GluegunCommand = {
name: 'hello',
alias: ['hi', 'greet'],
description: 'Say hello to someone',
run: async (toolbox) => {
const { print, parameters } = toolbox
// Get name from first parameter
const name = parameters.first || 'World'
// Get options
const options = parameters.options
const loud = options.loud || options.l
// Format message
let message = `Hello, ${name}!`
if (loud) {
message = message.toUpperCase()
}
// Display with appropriate style
print.success(message)
// Show some additional info if verbose
if (options.verbose || options.v) {
print.info('Command executed successfully')
print.info(`Parameters: ${JSON.stringify(parameters.array)}`)
print.info(`Options: ${JSON.stringify(options)}`)
}
}
}
module.exports = command

View File

@@ -0,0 +1,23 @@
/**
* <%= name %> Component
* Generated: <%= timestamp %>
*/
export interface <%= name %>Props {
// Add your props here
}
export class <%= name %> {
private props: <%= name %>Props
constructor(props: <%= name %>Props) {
this.props = props
}
public render(): void {
console.log('<%= name %> rendered')
}
}
// Export for convenience
export default <%= name %>

View File

@@ -0,0 +1,252 @@
# Gluegun Plugin System Example
Demonstrates how to build an extensible CLI with plugin architecture.
## Structure
```
plugin-system/
├── src/
│ ├── cli.ts # CLI with plugin loading
│ ├── commands/
│ │ └── plugin.ts # Plugin management command
│ └── plugins/
│ ├── custom-plugin.ts # Example plugin
│ └── validator-plugin.ts # Validation plugin
├── package.json
└── README.md
```
## Features
- Plugin discovery and loading
- Custom toolbox extensions via plugins
- Plugin-provided commands
- Plugin configuration management
## Installation
```bash
npm install
npm run build
```
## Usage
### Load Plugins
```bash
# CLI automatically loads plugins from:
# - ./plugins/
# - ./node_modules/mycli-*
```
### Use Plugin Commands
```bash
# Commands added by plugins
./bin/cli validate
./bin/cli custom-action
```
### Use Plugin Extensions
```typescript
// In any command
const { myPlugin } = toolbox
myPlugin.doSomething()
```
## Creating a Plugin
### Basic Plugin Structure
```typescript
module.exports = (toolbox) => {
const { print } = toolbox
// Add to toolbox
toolbox.myPlugin = {
version: '1.0.0',
doSomething: () => {
print.info('Plugin action executed')
}
}
}
```
### Plugin with Commands
```typescript
module.exports = (toolbox) => {
const { runtime } = toolbox
runtime.addPlugin({
name: 'my-plugin',
commands: [
{
name: 'my-command',
run: async (toolbox) => {
// Command implementation
}
}
]
})
}
```
## Plugin Discovery
The CLI looks for plugins in:
1. **Local plugins directory**: `./plugins/*.js`
2. **Node modules**: `./node_modules/mycli-*`
3. **Scoped packages**: `@scope/mycli-*`
## Plugin Naming Convention
- Local plugins: Any `.js` or `.ts` file in `plugins/`
- NPM plugins: Must match pattern `mycli-*`
- Example: `mycli-validator`, `@myorg/mycli-helper`
## Key Patterns
### 1. Load Plugins from Directory
```typescript
cli.plugins('./plugins', { matching: '*.js' })
```
### 2. Load NPM Plugins
```typescript
cli.plugins('./node_modules', {
matching: 'mycli-*',
hidden: true
})
```
### 3. Add Toolbox Extension
```typescript
toolbox.validator = {
validate: (data) => { /* ... */ }
}
```
### 4. Register Commands
```typescript
runtime.addPlugin({
name: 'my-plugin',
commands: [/* commands */]
})
```
## Example Plugins
### custom-plugin.ts
Adds custom utilities to toolbox.
```typescript
toolbox.custom = {
formatDate: (date) => { /* ... */ },
parseConfig: (file) => { /* ... */ }
}
```
### validator-plugin.ts
Adds validation command and utilities.
```typescript
toolbox.validator = {
validateFile: (path) => { /* ... */ },
validateSchema: (data) => { /* ... */ }
}
```
## Publishing Plugins
### 1. Create NPM Package
```json
{
"name": "mycli-myplugin",
"main": "dist/index.js",
"keywords": ["mycli", "plugin"]
}
```
### 2. Export Plugin
```typescript
module.exports = (toolbox) => {
// Plugin implementation
}
```
### 3. Publish
```bash
npm publish
```
### 4. Install and Use
```bash
npm install mycli-myplugin
# Automatically loaded by CLI
```
## Advanced Patterns
### Conditional Plugin Loading
```typescript
if (config.enablePlugin) {
cli.plugins('./plugins/optional')
}
```
### Plugin Configuration
```typescript
toolbox.myPlugin = {
config: await filesystem.read('.mypluginrc', 'json'),
// Plugin methods
}
```
### Plugin Dependencies
```typescript
module.exports = (toolbox) => {
// Check for required plugins
if (!toolbox.otherPlugin) {
throw new Error('Requires other-plugin')
}
}
```
## Best Practices
1. **Namespace Extensions**: Use unique names for toolbox extensions
2. **Document APIs**: Provide clear documentation for plugin methods
3. **Handle Errors**: Validate inputs and handle failures gracefully
4. **Version Plugins**: Use semantic versioning
5. **Test Plugins**: Write tests for plugin functionality
## Testing Plugins
```typescript
import { build } from 'gluegun'
test('plugin loads correctly', async () => {
const cli = build().src(__dirname).plugins('./plugins').create()
const toolbox = await cli.run()
expect(toolbox.myPlugin).toBeDefined()
})
```

View File

@@ -0,0 +1,322 @@
# Template Generator Example
Advanced template patterns for Gluegun CLI generators.
## Structure
```
template-generator/
├── src/
│ └── commands/
│ ├── new-app.ts # Multi-file generator
│ ├── new-feature.ts # Feature scaffold
│ └── new-config.ts # Config generator
├── templates/
│ ├── app/
│ │ ├── package.json.ejs
│ │ ├── tsconfig.json.ejs
│ │ ├── src/
│ │ │ └── index.ts.ejs
│ │ └── README.md.ejs
│ ├── feature/
│ │ ├── component.tsx.ejs
│ │ ├── test.spec.ts.ejs
│ │ └── styles.css.ejs
│ └── config/
│ └── config.json.ejs
└── README.md
```
## Features
- Multi-file generation from template sets
- Conditional template sections
- Nested directory creation
- Template composition
- Helper functions for common operations
## Patterns
### 1. Multi-File Generation
Generate complete project structure:
```typescript
await template.generate({
template: 'app/package.json.ejs',
target: `${name}/package.json`,
props: { name, version }
})
await template.generate({
template: 'app/src/index.ts.ejs',
target: `${name}/src/index.ts`,
props: { name }
})
```
### 2. Conditional Templates
Templates with conditional sections:
```ejs
<% if (includeTests) { %>
import { test } from 'vitest'
test('<%= name %> works', () => {
// Test implementation
})
<% } %>
```
### 3. Template Loops
Generate repetitive structures:
```ejs
<% features.forEach(feature => { %>
export { <%= feature.name %> } from './<%= feature.path %>'
<% }) %>
```
### 4. Nested Templates
Organize templates in directories:
```
templates/
├── react-app/
│ ├── components/
│ │ └── Component.tsx.ejs
│ ├── pages/
│ │ └── Page.tsx.ejs
│ └── layouts/
│ └── Layout.tsx.ejs
```
### 5. Template Helpers
Use helper functions in templates:
```ejs
/**
* <%= helpers.titleCase(name) %> Component
* File: <%= helpers.kebabCase(name) %>.ts
*/
export class <%= helpers.pascalCase(name) %> {
// Implementation
}
```
## Commands
### new-app
Create complete application structure:
```bash
./bin/cli new-app my-project --typescript --tests
```
Options:
- `--typescript`: Include TypeScript configuration
- `--tests`: Include test setup
- `--git`: Initialize git repository
- `--install`: Run npm install
### new-feature
Scaffold a new feature with tests and styles:
```bash
./bin/cli new-feature UserProfile --redux --tests
```
Options:
- `--redux`: Include Redux setup
- `--tests`: Generate test files
- `--styles`: Include CSS/SCSS files
### new-config
Generate configuration files:
```bash
./bin/cli new-config --eslint --prettier --jest
```
Options:
- `--eslint`: ESLint configuration
- `--prettier`: Prettier configuration
- `--jest`: Jest configuration
- `--typescript`: TypeScript configuration
## Template Variables
### Common Props
```typescript
{
name: string // Component/project name
description: string // Description
author: string // Author name
version: string // Version number
timestamp: string // ISO timestamp
year: number // Current year
}
```
### Feature-Specific Props
```typescript
{
// React component
componentType: 'class' | 'function'
withHooks: boolean
withState: boolean
// Configuration
includeESLint: boolean
includePrettier: boolean
includeTests: boolean
// Dependencies
dependencies: string[]
devDependencies: string[]
}
```
## Advanced Techniques
### 1. Template Composition
Combine multiple templates:
```typescript
// Base component template
await template.generate({
template: 'base/component.ts.ejs',
target: 'src/components/Base.ts',
props: baseProps
})
// Extended component using base
await template.generate({
template: 'extended/component.ts.ejs',
target: 'src/components/Extended.ts',
props: { ...baseProps, extends: 'Base' }
})
```
### 2. Dynamic Template Selection
Choose templates based on user input:
```typescript
const framework = await prompt.select('Framework:', [
'React',
'Vue',
'Angular'
])
const templatePath = `${framework.toLowerCase()}/component.ejs`
await template.generate({
template: templatePath,
target: `src/components/${name}.tsx`,
props: { name }
})
```
### 3. Template Preprocessing
Modify props before generation:
```typescript
const props = {
name,
pascalName: strings.pascalCase(name),
kebabName: strings.kebabCase(name),
dependencies: dependencies.sort(),
imports: generateImports(dependencies)
}
await template.generate({
template: 'component.ts.ejs',
target: `src/${props.kebabName}.ts`,
props
})
```
### 4. Post-Generation Actions
Run actions after template generation:
```typescript
// Generate files
await template.generate({ /* ... */ })
// Format generated code
await system.run('npm run format')
// Run initial build
await system.run('npm run build')
// Initialize git
if (options.git) {
await system.run('git init')
await system.run('git add .')
await system.run('git commit -m "Initial commit"')
}
```
### 5. Template Validation
Validate templates before generation:
```typescript
// Check if template exists
if (!filesystem.exists(`templates/${templatePath}`)) {
print.error(`Template not found: ${templatePath}`)
return
}
// Validate template syntax
const templateContent = await filesystem.read(`templates/${templatePath}`)
if (!validateEJS(templateContent)) {
print.error('Invalid EJS syntax in template')
return
}
```
## Best Practices
1. **Organize Templates**: Group related templates in directories
2. **Use Helpers**: Extract common logic into helper functions
3. **Validate Input**: Check user input before generation
4. **Provide Feedback**: Show progress with spinners
5. **Handle Errors**: Gracefully handle missing templates
6. **Document Variables**: Comment template variables
7. **Test Templates**: Generate samples to verify output
## Testing Generated Code
```bash
# Generate sample
./bin/cli new-app test-project --all
# Verify structure
cd test-project
npm install
npm test
npm run build
```
## Template Development Workflow
1. Create template file with `.ejs` extension
2. Add template variables with `<%= %>` syntax
3. Test template with sample props
4. Add conditional sections with `<% if %>` blocks
5. Validate generated output
6. Document template variables and options