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,82 @@
import { GluegunCommand } from 'gluegun'
const command: GluegunCommand = {
name: '<%= name %>',
description: 'Interact with <%= apiName %> API',
run: async (toolbox) => {
const { http, print, parameters, prompt } = toolbox
// Get API configuration
const apiKey = process.env.<%= apiKeyEnv %> || parameters.options.key
if (!apiKey) {
print.error('API key is required')
print.info('Set <%= apiKeyEnv %> environment variable or use --key option')
return
}
// Create API client
const api = http.create({
baseURL: '<%= apiBaseUrl %>',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
timeout: 10000
})
// Show spinner while loading
const spinner = print.spin('Fetching data from <%= apiName %>...')
try {
// Make API request
const response = await api.get('<%= endpoint %>')
// Check response
if (!response.ok) {
spinner.fail(`API Error: ${response.problem}`)
if (response.data) {
print.error(JSON.stringify(response.data, null, 2))
}
return
}
spinner.succeed('Data fetched successfully')
// Display results
const data = response.data
<% if (displayFormat === 'table') { %>
// Display as table
const tableData = data.map(item => [
item.<%= field1 %>,
item.<%= field2 %>,
item.<%= field3 %>
])
print.table([
['<%= header1 %>', '<%= header2 %>', '<%= header3 %>'],
...tableData
])
<% } else { %>
// Display as JSON
print.info(JSON.stringify(data, null, 2))
<% } %>
// Optional: Save to file
const shouldSave = await prompt.confirm('Save results to file?')
if (shouldSave) {
const filename = `<%= outputFile %>`
await toolbox.filesystem.write(filename, JSON.stringify(data, null, 2))
print.success(`Saved to ${filename}`)
}
} catch (error) {
spinner.fail('Request failed')
print.error(error.message)
}
}
}
module.exports = command

View File

@@ -0,0 +1,33 @@
import { GluegunCommand } from 'gluegun'
const command: GluegunCommand = {
name: '<%= name %>',
description: '<%= description %>',
run: async (toolbox) => {
const { print, parameters } = toolbox
// Get parameters
const options = parameters.options
const args = parameters.array
// Command logic
print.info(`Running <%= name %> command`)
// Process arguments
if (args.length === 0) {
print.warning('No arguments provided')
return
}
// Example: Process each argument
for (const arg of args) {
print.info(`Processing: ${arg}`)
}
// Success message
print.success('<%= name %> completed successfully')
}
}
module.exports = command

View File

@@ -0,0 +1,57 @@
import { GluegunCommand } from 'gluegun'
const command: GluegunCommand = {
name: 'generate',
alias: ['g'],
description: 'Generate <%= type %> from template',
run: async (toolbox) => {
const { template, print, parameters, filesystem, strings } = toolbox
// Get name from parameters
const name = parameters.first
if (!name) {
print.error('Name is required')
print.info('Usage: <%= cliName %> generate <name>')
return
}
// Convert to different cases
const pascalName = strings.pascalCase(name)
const camelName = strings.camelCase(name)
const kebabName = strings.kebabCase(name)
// Generate from template
const target = `<%= outputPath %>/${kebabName}.<%= extension %>`
try {
await template.generate({
template: '<%= templateFile %>',
target,
props: {
name: pascalName,
camelName,
kebabName,
timestamp: new Date().toISOString()
}
})
print.success(`Generated <%= type %>: ${target}`)
// Optional: Add to index
<% if (addToIndex) { %>
const indexPath = '<%= outputPath %>/index.ts'
if (filesystem.exists(indexPath)) {
await filesystem.append(indexPath, `export { ${pascalName} } from './${kebabName}'\n`)
print.info(`Added export to ${indexPath}`)
}
<% } %>
} catch (error) {
print.error(`Failed to generate <%= type %>: ${error.message}`)
}
}
}
module.exports = command

View File

@@ -0,0 +1,71 @@
import { GluegunToolbox } from 'gluegun'
// Extend the toolbox with custom functionality
module.exports = (toolbox: GluegunToolbox) => {
const { filesystem, strings, print } = toolbox
/**
* Custom <%= extensionName %> extension
*/
toolbox.<%= extensionName %> = {
/**
* <%= methodDescription %>
*/
<%= methodName %>: async (input: string): Promise<string> => {
// Implementation
print.info(`Processing: ${input}`)
<% if (usesFilesystem) { %>
// Filesystem operations
const files = filesystem.find('.', { matching: '*.ts' })
print.info(`Found ${files.length} TypeScript files`)
<% } %>
<% if (usesStrings) { %>
// String manipulation
const pascalCase = strings.pascalCase(input)
const camelCase = strings.camelCase(input)
const kebabCase = strings.kebabCase(input)
print.info(`Formats: ${pascalCase}, ${camelCase}, ${kebabCase}`)
<% } %>
return input
},
/**
* Validate <%= resourceType %>
*/
validate: (data: any): boolean => {
// Validation logic
if (!data || typeof data !== 'object') {
print.error('Invalid data format')
return false
}
<% validationFields.forEach(field => { %>
if (!data.<%= field %>) {
print.error('Missing required field: <%= field %>')
return false
}
<% }) %>
return true
},
/**
* Format <%= resourceType %> for display
*/
format: (data: any): string => {
// Format for display
const lines = [
`<%= resourceType %>:`,
<% displayFields.forEach(field => { %>
` <%= field %>: ${data.<%= field %>}`,
<% }) %>
]
return lines.join('\n')
}
}
}

View File

@@ -0,0 +1,132 @@
import { GluegunToolbox } from 'gluegun'
/**
* Helper functions extension for <%= cliName %>
*/
module.exports = (toolbox: GluegunToolbox) => {
const { filesystem, strings, print } = toolbox
toolbox.helpers = {
/**
* Read configuration file with validation
*/
readConfig: async (configPath: string = './<%= configFile %>'): Promise<any> => {
if (!filesystem.exists(configPath)) {
print.error(`Configuration file not found: ${configPath}`)
return null
}
try {
const config = await filesystem.read(configPath, 'json')
// Validate configuration
if (!config.<%= requiredField %>) {
print.warning('Configuration missing required field: <%= requiredField %>')
}
return config
} catch (error) {
print.error(`Failed to read config: ${error.message}`)
return null
}
},
/**
* Write configuration file safely
*/
writeConfig: async (config: any, configPath: string = './<%= configFile %>'): Promise<boolean> => {
try {
await filesystem.write(configPath, config, { jsonIndent: 2 })
print.success(`Configuration saved to ${configPath}`)
return true
} catch (error) {
print.error(`Failed to write config: ${error.message}`)
return false
}
},
/**
* Ensure directory exists and is writable
*/
ensureDir: async (dirPath: string): Promise<boolean> => {
try {
await filesystem.dir(dirPath)
print.debug(`Directory ensured: ${dirPath}`)
return true
} catch (error) {
print.error(`Failed to create directory: ${error.message}`)
return false
}
},
/**
* Find files matching pattern
*/
findFiles: (pattern: string, directory: string = '.'): string[] => {
const files = filesystem.find(directory, { matching: pattern })
print.debug(`Found ${files.length} files matching: ${pattern}`)
return files
},
/**
* Convert string to various cases
*/
convertCase: (input: string) => {
return {
pascal: strings.pascalCase(input),
camel: strings.camelCase(input),
kebab: strings.kebabCase(input),
snake: strings.snakeCase(input),
upper: strings.upperCase(input),
lower: strings.lowerCase(input)
}
},
/**
* Display success message with optional details
*/
success: (message: string, details?: string[]) => {
print.success(message)
if (details && details.length > 0) {
details.forEach(detail => print.info(` ${detail}`))
}
},
/**
* Display error message and exit
*/
fatal: (message: string, error?: Error) => {
print.error(message)
if (error) {
print.error(error.message)
if (error.stack) {
print.debug(error.stack)
}
}
process.exit(1)
},
/**
* Check if command is available in PATH
*/
hasCommand: async (command: string): Promise<boolean> => {
const result = await toolbox.system.which(command)
return result !== null
},
/**
* Run command with error handling
*/
runCommand: async (command: string, options = {}): Promise<string | null> => {
try {
print.debug(`Running: ${command}`)
const output = await toolbox.system.run(command, options)
return output
} catch (error) {
print.error(`Command failed: ${command}`)
print.error(error.message)
return null
}
}
}
}

View File

@@ -0,0 +1,125 @@
import { GluegunToolbox } from 'gluegun'
/**
* <%= pluginName %> Plugin
* <%= pluginDescription %>
*/
module.exports = (toolbox: GluegunToolbox) => {
const { print, filesystem, template } = toolbox
// Plugin initialization
print.debug('<%= pluginName %> plugin loaded')
// Add plugin namespace to toolbox
toolbox.<%= pluginNamespace %> = {
/**
* Plugin version
*/
version: '<%= version %>',
/**
* Check if plugin is initialized
*/
isInitialized: (): boolean => {
const configPath = '<%= configPath %>'
return filesystem.exists(configPath)
},
/**
* Initialize plugin configuration
*/
initialize: async (options: any = {}): Promise<void> => {
print.info('Initializing <%= pluginName %>...')
// Create config from template
await template.generate({
template: '<%= configTemplate %>',
target: '<%= configPath %>',
props: {
...options,
createdAt: new Date().toISOString()
}
})
print.success('<%= pluginName %> initialized')
},
/**
* Get plugin configuration
*/
getConfig: async (): Promise<any> => {
const configPath = '<%= configPath %>'
if (!filesystem.exists(configPath)) {
print.warning('<%= pluginName %> not initialized')
return null
}
return await filesystem.read(configPath, 'json')
},
/**
* Update plugin configuration
*/
updateConfig: async (updates: any): Promise<void> => {
const config = await toolbox.<%= pluginNamespace %>.getConfig()
if (!config) {
print.error('Cannot update config: <%= pluginName %> not initialized')
return
}
const updatedConfig = { ...config, ...updates }
await filesystem.write('<%= configPath %>', updatedConfig, { jsonIndent: 2 })
print.success('Configuration updated')
},
<% if (hasCommands) { %>
/**
* Execute plugin-specific operation
*/
execute: async (operation: string, params: any = {}): Promise<any> => {
print.info(`Executing <%= pluginName %> operation: ${operation}`)
switch (operation) {
case '<%= operation1 %>':
return await handle<%= operation1Pascal %>(toolbox, params)
case '<%= operation2 %>':
return await handle<%= operation2Pascal %>(toolbox, params)
default:
print.error(`Unknown operation: ${operation}`)
return null
}
}
<% } %>
}
}
<% if (hasCommands) { %>
/**
* Handle <%= operation1 %> operation
*/
async function handle<%= operation1Pascal %>(toolbox: GluegunToolbox, params: any) {
const { print } = toolbox
print.info('Handling <%= operation1 %>...')
// Implementation
return { success: true }
}
/**
* Handle <%= operation2 %> operation
*/
async function handle<%= operation2Pascal %>(toolbox: GluegunToolbox, params: any) {
const { print } = toolbox
print.info('Handling <%= operation2 %>...')
// Implementation
return { success: true }
}
<% } %>

View File

@@ -0,0 +1,93 @@
import { GluegunToolbox } from 'gluegun'
/**
* <%= pluginName %> Plugin with Commands
* Demonstrates how to add commands via plugins
*/
module.exports = (toolbox: GluegunToolbox) => {
const { print, runtime } = toolbox
print.debug('<%= pluginName %> plugin loaded')
// Register plugin commands
runtime.addPlugin({
name: '<%= pluginName %>',
commands: [
{
name: '<%= command1Name %>',
description: '<%= command1Description %>',
run: async (toolbox) => {
const { print, parameters } = toolbox
print.info('Running <%= command1Name %> from <%= pluginName %>')
// Command logic
const arg = parameters.first
if (arg) {
print.success(`Processed: ${arg}`)
} else {
print.warning('No argument provided')
}
}
},
{
name: '<%= command2Name %>',
description: '<%= command2Description %>',
run: async (toolbox) => {
const { print, filesystem, template } = toolbox
print.info('Running <%= command2Name %> from <%= pluginName %>')
// Generate from template
await template.generate({
template: '<%= template2 %>',
target: '<%= output2 %>',
props: {
pluginName: '<%= pluginName %>',
timestamp: new Date().toISOString()
}
})
print.success('Generated successfully')
}
}
]
})
// Add plugin utilities to toolbox
toolbox.<%= pluginNamespace %> = {
/**
* Shared utility function
*/
sharedUtility: (input: string): string => {
print.debug(`<%= pluginName %> processing: ${input}`)
return input.toUpperCase()
},
/**
* Validate plugin requirements
*/
validateRequirements: async (): Promise<boolean> => {
const { filesystem, system } = toolbox
// Check for required files
<% requiredFiles.forEach(file => { %>
if (!filesystem.exists('<%= file %>')) {
print.error('Missing required file: <%= file %>')
return false
}
<% }) %>
// Check for required commands
<% requiredCommands.forEach(cmd => { %>
const has<%= cmd %> = await system.which('<%= cmd %>')
if (!has<%= cmd %>) {
print.error('Missing required command: <%= cmd %>')
return false
}
<% }) %>
return true
}
}
}

View File

@@ -0,0 +1,117 @@
import { GluegunCommand } from 'gluegun'
/**
* Examples of filesystem operations with Gluegun
*/
const command: GluegunCommand = {
name: 'filesystem',
description: 'Filesystem operation examples',
run: async (toolbox) => {
const { filesystem, print } = toolbox
print.info('=== Filesystem Examples ===\n')
// 1. Read file
print.info('1. Read file')
const packageJson = await filesystem.read('package.json', 'json')
if (packageJson) {
print.success(`Project: ${packageJson.name}`)
}
// 2. Write file
print.info('\n2. Write file')
await filesystem.write('temp-output.txt', 'Hello from Gluegun!', {
atomic: true // Ensures file is written completely or not at all
})
print.success('File written: temp-output.txt')
// 3. Check if file exists
print.info('\n3. Check existence')
const exists = filesystem.exists('temp-output.txt')
print.info(`temp-output.txt exists: ${exists}`)
// 4. Create directory
print.info('\n4. Create directory')
await filesystem.dir('temp-dir/nested/deep')
print.success('Created nested directories')
// 5. List files
print.info('\n5. List files')
const files = filesystem.list('.')
print.info(`Files in current directory: ${files?.length || 0}`)
// 6. Find files with pattern
print.info('\n6. Find files')
const tsFiles = filesystem.find('.', { matching: '*.ts', recursive: false })
print.info(`TypeScript files found: ${tsFiles?.length || 0}`)
tsFiles?.slice(0, 5).forEach(file => print.info(` - ${file}`))
// 7. Copy file/directory
print.info('\n7. Copy operations')
await filesystem.copy('temp-output.txt', 'temp-dir/copy.txt')
print.success('File copied to temp-dir/copy.txt')
// 8. Move/rename
print.info('\n8. Move/rename')
await filesystem.move('temp-output.txt', 'temp-dir/moved.txt')
print.success('File moved to temp-dir/moved.txt')
// 9. Read directory tree
print.info('\n9. Directory tree')
const tree = filesystem.inspectTree('temp-dir')
print.info(`Temp directory structure:`)
print.info(JSON.stringify(tree, null, 2))
// 10. File info
print.info('\n10. File info')
const info = filesystem.inspect('temp-dir/moved.txt')
if (info) {
print.info(`File type: ${info.type}`)
print.info(`File size: ${info.size} bytes`)
}
// 11. Append to file
print.info('\n11. Append to file')
await filesystem.append('temp-dir/moved.txt', '\nAppended line')
print.success('Content appended')
// 12. Read file with encoding
print.info('\n12. Read with encoding')
const content = await filesystem.read('temp-dir/moved.txt', 'utf8')
print.info(`Content: ${content}`)
// 13. Path utilities
print.info('\n13. Path utilities')
const fullPath = filesystem.path('temp-dir', 'moved.txt')
print.info(`Full path: ${fullPath}`)
print.info(`Current directory: ${filesystem.cwd()}`)
print.info(`Path separator: ${filesystem.separator}`)
// 14. Find by extension
print.info('\n14. Find by extension')
const jsonFiles = filesystem.find('.', {
matching: '*.json',
recursive: false
})
print.info(`JSON files: ${jsonFiles?.join(', ')}`)
// 15. Remove files (cleanup)
print.info('\n15. Cleanup')
await filesystem.remove('temp-dir')
print.success('Removed temp directory')
// Summary
print.info('\n=== Summary ===')
print.success('Filesystem operations completed!')
print.info('Common operations:')
print.info(' - read/write: Handle file content')
print.info(' - exists: Check file/directory existence')
print.info(' - dir: Create directories')
print.info(' - find: Search for files')
print.info(' - copy/move: Manipulate files')
print.info(' - remove: Delete files/directories')
}
}
module.exports = command

View File

@@ -0,0 +1,126 @@
import { GluegunCommand } from 'gluegun'
/**
* Examples of different prompt patterns with Gluegun
*/
const command: GluegunCommand = {
name: 'prompts',
description: 'Interactive prompt examples',
run: async (toolbox) => {
const { prompt, print } = toolbox
print.info('=== Prompt Examples ===\n')
// 1. Simple text input
const nameResult = await prompt.ask({
type: 'input',
name: 'name',
message: 'What is your name?',
initial: 'John Doe'
})
print.info(`Hello, ${nameResult.name}!\n`)
// 2. Confirmation prompt
const shouldContinue = await prompt.confirm('Do you want to continue?')
if (!shouldContinue) {
print.warning('Operation cancelled')
return
}
// 3. Select from list
const frameworkResult = await prompt.ask({
type: 'select',
name: 'framework',
message: 'Choose your framework:',
choices: ['React', 'Vue', 'Angular', 'Svelte']
})
print.info(`Selected: ${frameworkResult.framework}\n`)
// 4. Multi-select
const featuresResult = await prompt.ask({
type: 'multiselect',
name: 'features',
message: 'Select features to enable:',
choices: [
{ name: 'TypeScript', value: 'typescript' },
{ name: 'ESLint', value: 'eslint' },
{ name: 'Prettier', value: 'prettier' },
{ name: 'Testing', value: 'testing' }
],
initial: ['typescript', 'eslint']
})
print.info(`Features: ${featuresResult.features.join(', ')}\n`)
// 5. Password input
const passwordResult = await prompt.ask({
type: 'password',
name: 'password',
message: 'Enter password:'
})
print.info('Password received (hidden)\n')
// 6. Number input
const portResult = await prompt.ask({
type: 'numeral',
name: 'port',
message: 'Enter port number:',
initial: 3000
})
print.info(`Port: ${portResult.port}\n`)
// 7. Autocomplete
const colorResult = await prompt.ask({
type: 'autocomplete',
name: 'color',
message: 'Choose a color:',
choices: ['Red', 'Blue', 'Green', 'Yellow', 'Purple', 'Orange']
})
print.info(`Color: ${colorResult.color}\n`)
// 8. List input (comma-separated)
const tagsResult = await prompt.ask({
type: 'list',
name: 'tags',
message: 'Enter tags (comma-separated):'
})
print.info(`Tags: ${tagsResult.tags.join(', ')}\n`)
// 9. Snippet (multiple fields)
const userResult = await prompt.ask({
type: 'snippet',
name: 'user',
message: 'Fill out user information:',
template: `Name: \${name}
Email: \${email}
Age: \${age}`
})
print.info('User info:')
print.info(JSON.stringify(userResult.user.values, null, 2))
print.info('')
// 10. Conditional prompts
const projectTypeResult = await prompt.ask({
type: 'select',
name: 'projectType',
message: 'Project type:',
choices: ['web', 'mobile', 'desktop']
})
if (projectTypeResult.projectType === 'web') {
const webFrameworkResult = await prompt.ask({
type: 'select',
name: 'webFramework',
message: 'Choose web framework:',
choices: ['Next.js', 'Remix', 'SvelteKit']
})
print.info(`Web framework: ${webFrameworkResult.webFramework}\n`)
}
// Summary
print.success('All prompts completed!')
print.info('See the examples above for different prompt patterns')
}
}
module.exports = command

View File

@@ -0,0 +1,97 @@
/**
* <%= componentName %> Component
* Generated by <%= generatorName %>
* Created: <%= timestamp %>
*/
<% if (language === 'typescript') { %>
import { <%= imports.join(', ') %> } from '<%= importPath %>'
export interface <%= componentName %>Props {
<% props.forEach(prop => { %>
<%= prop.name %>: <%= prop.type %><%= prop.optional ? '?' : '' %>
<% }) %>
}
export class <%= componentName %> {
<% properties.forEach(property => { %>
private <%= property.name %>: <%= property.type %>
<% }) %>
constructor(props: <%= componentName %>Props) {
<% properties.forEach(property => { %>
this.<%= property.name %> = props.<%= property.name %>
<% }) %>
}
<% methods.forEach(method => { %>
<%= method.name %>(<%= method.params %>): <%= method.returnType %> {
// TODO: Implement <%= method.name %>
<% if (method.returnType !== 'void') { %>
return <%= method.defaultReturn %>
<% } %>
}
<% }) %>
}
<% } else if (language === 'javascript') { %>
/**
* <%= componentName %> Component
*/
class <%= componentName %> {
constructor(options = {}) {
<% properties.forEach(property => { %>
this.<%= property.name %> = options.<%= property.name %> || <%= property.default %>
<% }) %>
}
<% methods.forEach(method => { %>
<%= method.name %>(<%= method.params %>) {
// TODO: Implement <%= method.name %>
}
<% }) %>
}
module.exports = <%= componentName %>
<% } %>
<% if (includeTests) { %>
/**
* Tests for <%= componentName %>
*/
describe('<%= componentName %>', () => {
<% methods.forEach(method => { %>
test('<%= method.name %> should work', () => {
// TODO: Write test for <%= method.name %>
})
<% }) %>
})
<% } %>
<% if (includeDocumentation) { %>
/**
* DOCUMENTATION
*
* Usage Example:
* ```<%= language %>
* <% if (language === 'typescript') { %>
* const instance = new <%= componentName %>({
* <% props.forEach(prop => { %>
* <%= prop.name %>: <%= prop.exampleValue %>,
* <% }) %>
* })
* <% } else { %>
* const instance = new <%= componentName %>({
* <% properties.forEach(property => { %>
* <%= property.name %>: <%= property.exampleValue %>,
* <% }) %>
* })
* <% } %>
*
* <% methods.forEach(method => { %>
* instance.<%= method.name %>(<%= method.exampleArgs %>)
* <% }) %>
* ```
*/
<% } %>