create-bunli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +302 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +310 -0
  4. package/dist/create-project.d.ts +13 -0
  5. package/dist/create.d.ts +13 -0
  6. package/dist/index.d.ts +4 -0
  7. package/dist/index.js +217 -0
  8. package/dist/template-engine.d.ts +27 -0
  9. package/dist/templates/advanced/README.md +114 -0
  10. package/dist/templates/advanced/package.json +36 -0
  11. package/dist/templates/advanced/src/commands/config.ts +145 -0
  12. package/dist/templates/advanced/src/commands/init.ts +153 -0
  13. package/dist/templates/advanced/src/commands/serve.ts +176 -0
  14. package/dist/templates/advanced/src/commands/validate.ts +116 -0
  15. package/dist/templates/advanced/src/index.ts +44 -0
  16. package/dist/templates/advanced/src/utils/config.ts +83 -0
  17. package/dist/templates/advanced/src/utils/constants.ts +12 -0
  18. package/dist/templates/advanced/src/utils/glob.ts +49 -0
  19. package/dist/templates/advanced/src/utils/validator.ts +131 -0
  20. package/dist/templates/advanced/template.json +37 -0
  21. package/dist/templates/advanced/test/commands.test.ts +34 -0
  22. package/dist/templates/advanced/tsconfig.json +23 -0
  23. package/dist/templates/basic/README.md +41 -0
  24. package/dist/templates/basic/package.json +29 -0
  25. package/dist/templates/basic/src/commands/hello.ts +29 -0
  26. package/dist/templates/basic/src/index.ts +13 -0
  27. package/dist/templates/basic/template.json +31 -0
  28. package/dist/templates/basic/test/hello.test.ts +26 -0
  29. package/dist/templates/basic/tsconfig.json +19 -0
  30. package/dist/templates/monorepo/README.md +74 -0
  31. package/dist/templates/monorepo/package.json +28 -0
  32. package/dist/templates/monorepo/packages/cli/package.json +34 -0
  33. package/dist/templates/monorepo/packages/cli/src/index.ts +22 -0
  34. package/dist/templates/monorepo/packages/cli/tsconfig.json +15 -0
  35. package/dist/templates/monorepo/packages/core/package.json +32 -0
  36. package/dist/templates/monorepo/packages/core/scripts/build.ts +18 -0
  37. package/dist/templates/monorepo/packages/core/src/commands/analyze.ts +84 -0
  38. package/dist/templates/monorepo/packages/core/src/commands/process.ts +64 -0
  39. package/dist/templates/monorepo/packages/core/src/index.ts +3 -0
  40. package/dist/templates/monorepo/packages/core/src/types.ts +21 -0
  41. package/dist/templates/monorepo/packages/core/tsconfig.json +15 -0
  42. package/dist/templates/monorepo/packages/utils/package.json +26 -0
  43. package/dist/templates/monorepo/packages/utils/scripts/build.ts +17 -0
  44. package/dist/templates/monorepo/packages/utils/src/format.ts +27 -0
  45. package/dist/templates/monorepo/packages/utils/src/index.ts +3 -0
  46. package/dist/templates/monorepo/packages/utils/src/json.ts +11 -0
  47. package/dist/templates/monorepo/packages/utils/src/logger.ts +19 -0
  48. package/dist/templates/monorepo/packages/utils/tsconfig.json +12 -0
  49. package/dist/templates/monorepo/template.json +24 -0
  50. package/dist/templates/monorepo/tsconfig.json +14 -0
  51. package/dist/templates/monorepo/turbo.json +28 -0
  52. package/dist/types.d.ts +48 -0
  53. package/package.json +57 -0
  54. package/templates/advanced/README.md +114 -0
  55. package/templates/advanced/package.json +36 -0
  56. package/templates/advanced/src/commands/config.ts +145 -0
  57. package/templates/advanced/src/commands/init.ts +153 -0
  58. package/templates/advanced/src/commands/serve.ts +176 -0
  59. package/templates/advanced/src/commands/validate.ts +116 -0
  60. package/templates/advanced/src/index.ts +44 -0
  61. package/templates/advanced/src/utils/config.ts +83 -0
  62. package/templates/advanced/src/utils/constants.ts +12 -0
  63. package/templates/advanced/src/utils/glob.ts +49 -0
  64. package/templates/advanced/src/utils/validator.ts +131 -0
  65. package/templates/advanced/template.json +37 -0
  66. package/templates/advanced/test/commands.test.ts +34 -0
  67. package/templates/advanced/tsconfig.json +23 -0
  68. package/templates/basic/README.md +41 -0
  69. package/templates/basic/package.json +29 -0
  70. package/templates/basic/src/commands/hello.ts +29 -0
  71. package/templates/basic/src/index.ts +13 -0
  72. package/templates/basic/template.json +31 -0
  73. package/templates/basic/test/hello.test.ts +26 -0
  74. package/templates/basic/tsconfig.json +19 -0
  75. package/templates/monorepo/README.md +74 -0
  76. package/templates/monorepo/package.json +28 -0
  77. package/templates/monorepo/packages/cli/package.json +34 -0
  78. package/templates/monorepo/packages/cli/src/index.ts +22 -0
  79. package/templates/monorepo/packages/cli/tsconfig.json +15 -0
  80. package/templates/monorepo/packages/core/package.json +32 -0
  81. package/templates/monorepo/packages/core/scripts/build.ts +18 -0
  82. package/templates/monorepo/packages/core/src/commands/analyze.ts +84 -0
  83. package/templates/monorepo/packages/core/src/commands/process.ts +64 -0
  84. package/templates/monorepo/packages/core/src/index.ts +3 -0
  85. package/templates/monorepo/packages/core/src/types.ts +21 -0
  86. package/templates/monorepo/packages/core/tsconfig.json +15 -0
  87. package/templates/monorepo/packages/utils/package.json +26 -0
  88. package/templates/monorepo/packages/utils/scripts/build.ts +17 -0
  89. package/templates/monorepo/packages/utils/src/format.ts +27 -0
  90. package/templates/monorepo/packages/utils/src/index.ts +3 -0
  91. package/templates/monorepo/packages/utils/src/json.ts +11 -0
  92. package/templates/monorepo/packages/utils/src/logger.ts +19 -0
  93. package/templates/monorepo/packages/utils/tsconfig.json +12 -0
  94. package/templates/monorepo/template.json +24 -0
  95. package/templates/monorepo/tsconfig.json +14 -0
  96. package/templates/monorepo/turbo.json +28 -0
@@ -0,0 +1,176 @@
1
+ import { defineCommand, option } from '@bunli/core'
2
+ import { z } from 'zod'
3
+ import { loadConfig } from '../utils/config.js'
4
+
5
+ export const serveCommand = defineCommand({
6
+ name: 'serve',
7
+ description: 'Start a development server',
8
+ options: {
9
+ port: option(
10
+ z.number().int().min(1).max(65535).default(3000),
11
+ {
12
+ short: 'p',
13
+ description: 'Port to listen on'
14
+ }
15
+ ),
16
+ host: option(
17
+ z.string().default('localhost'),
18
+ {
19
+ short: 'h',
20
+ description: 'Host to bind to'
21
+ }
22
+ ),
23
+ open: option(
24
+ z.boolean().default(true),
25
+ {
26
+ description: 'Open browser on start'
27
+ }
28
+ )
29
+ },
30
+ handler: async ({ flags, colors, spinner, shell }) => {
31
+ const spin = spinner('Starting server...')
32
+ spin.start()
33
+
34
+ try {
35
+ // Load config
36
+ const config = await loadConfig()
37
+
38
+ // Merge flags with config
39
+ const port = flags.port || config.server?.port || 3000
40
+ const host = flags.host || config.server?.host || 'localhost'
41
+ const shouldOpen = flags.open ?? config.server?.open ?? true
42
+
43
+ // Create server
44
+ const server = Bun.serve({
45
+ port,
46
+ hostname: host,
47
+ fetch(req) {
48
+ const url = new URL(req.url)
49
+
50
+ // Simple router
51
+ if (url.pathname === '/') {
52
+ return new Response(getHomePage(), {
53
+ headers: { 'Content-Type': 'text/html' }
54
+ })
55
+ }
56
+
57
+ if (url.pathname === '/api/status') {
58
+ return Response.json({
59
+ status: 'ok',
60
+ version: '0.1.0',
61
+ uptime: process.uptime()
62
+ })
63
+ }
64
+
65
+ return new Response('Not Found', { status: 404 })
66
+ }
67
+ })
68
+
69
+ spin.succeed(`Server running at http://${host}:${port}`)
70
+
71
+ // Open browser
72
+ if (shouldOpen) {
73
+ const openSpin = spinner('Opening browser...')
74
+ openSpin.start()
75
+
76
+ try {
77
+ const url = `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}`
78
+
79
+ // Platform-specific open commands
80
+ const openCommand = process.platform === 'darwin' ? 'open' :
81
+ process.platform === 'win32' ? 'start' :
82
+ 'xdg-open'
83
+
84
+ await shell`${openCommand} ${url}`.quiet()
85
+ openSpin.succeed('Browser opened')
86
+ } catch {
87
+ openSpin.fail('Failed to open browser')
88
+ }
89
+ }
90
+
91
+ // Keep server running
92
+ console.log()
93
+ console.log(colors.dim('Press Ctrl+C to stop the server'))
94
+
95
+ // Handle shutdown
96
+ process.on('SIGINT', () => {
97
+ console.log()
98
+ console.log(colors.yellow('Shutting down server...'))
99
+ server.stop()
100
+ process.exit(0)
101
+ })
102
+
103
+ } catch (error) {
104
+ spin.fail('Failed to start server')
105
+ console.error(colors.red(String(error)))
106
+ process.exit(1)
107
+ }
108
+ }
109
+ })
110
+
111
+ function getHomePage(): string {
112
+ return `
113
+ <!DOCTYPE html>
114
+ <html>
115
+ <head>
116
+ <title>{{projectName}}</title>
117
+ <style>
118
+ body {
119
+ font-family: system-ui, -apple-system, sans-serif;
120
+ max-width: 800px;
121
+ margin: 0 auto;
122
+ padding: 2rem;
123
+ background: #f5f5f5;
124
+ }
125
+ .container {
126
+ background: white;
127
+ padding: 2rem;
128
+ border-radius: 8px;
129
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
130
+ }
131
+ h1 {
132
+ color: #333;
133
+ margin-top: 0;
134
+ }
135
+ .status {
136
+ display: inline-block;
137
+ padding: 0.25rem 0.5rem;
138
+ background: #10b981;
139
+ color: white;
140
+ border-radius: 4px;
141
+ font-size: 0.875rem;
142
+ }
143
+ code {
144
+ background: #f3f4f6;
145
+ padding: 0.125rem 0.25rem;
146
+ border-radius: 3px;
147
+ font-family: monospace;
148
+ }
149
+ .endpoints {
150
+ margin-top: 2rem;
151
+ padding: 1rem;
152
+ background: #f9fafb;
153
+ border-radius: 4px;
154
+ }
155
+ </style>
156
+ </head>
157
+ <body>
158
+ <div class="container">
159
+ <h1>{{projectName}}</h1>
160
+ <p>{{description}}</p>
161
+ <p><span class="status">Running</span></p>
162
+
163
+ <div class="endpoints">
164
+ <h3>API Endpoints</h3>
165
+ <ul>
166
+ <li><code>GET /</code> - This page</li>
167
+ <li><code>GET /api/status</code> - Server status</li>
168
+ </ul>
169
+ </div>
170
+
171
+ <p>To get started, check out the <a href="https://github.com/AryaLabsHQ/bunli">documentation</a>.</p>
172
+ </div>
173
+ </body>
174
+ </html>
175
+ `.trim()
176
+ }
@@ -0,0 +1,116 @@
1
+ import { defineCommand, option } from '@bunli/core'
2
+ import { z } from 'zod'
3
+ import { loadConfig } from '../utils/config.js'
4
+ import { validateFiles } from '../utils/validator.js'
5
+ import { glob } from '../utils/glob.js'
6
+
7
+ export const validateCommand = defineCommand({
8
+ name: 'validate',
9
+ description: 'Validate files against defined rules',
10
+ args: z.array(z.string()).min(1).describe('Files to validate'),
11
+ options: {
12
+ config: option(
13
+ z.string().optional(),
14
+ {
15
+ short: 'c',
16
+ description: 'Path to config file'
17
+ }
18
+ ),
19
+ fix: option(
20
+ z.boolean().default(false),
21
+ {
22
+ short: 'f',
23
+ description: 'Auto-fix issues'
24
+ }
25
+ ),
26
+ cache: option(
27
+ z.boolean().default(true),
28
+ {
29
+ description: 'Enable caching'
30
+ }
31
+ )
32
+ },
33
+ handler: async ({ args, flags, colors, spinner }) => {
34
+ const spin = spinner('Loading configuration...')
35
+ spin.start()
36
+
37
+ try {
38
+ // Load config
39
+ const config = await loadConfig(flags.config)
40
+ spin.succeed('Configuration loaded')
41
+
42
+ // Resolve files
43
+ const fileSpin = spinner('Resolving files...')
44
+ fileSpin.start()
45
+
46
+ const files = await glob(args, {
47
+ include: config.include,
48
+ exclude: config.exclude
49
+ })
50
+
51
+ fileSpin.succeed(`Found ${files.length} files to validate`)
52
+
53
+ if (files.length === 0) {
54
+ console.log(colors.yellow('No files matched the pattern'))
55
+ return
56
+ }
57
+
58
+ // Run validation
59
+ const validateSpin = spinner('Validating files...')
60
+ validateSpin.start()
61
+
62
+ const results = await validateFiles(files, {
63
+ rules: config.rules,
64
+ fix: flags.fix,
65
+ cache: flags.cache && config.cache?.enabled
66
+ })
67
+
68
+ validateSpin.stop()
69
+
70
+ // Display results
71
+ let hasErrors = false
72
+
73
+ for (const result of results) {
74
+ if (result.errors.length > 0 || result.warnings.length > 0) {
75
+ console.log()
76
+ console.log(colors.bold(result.file))
77
+
78
+ for (const error of result.errors) {
79
+ console.log(colors.red(` ✗ ${error.line}:${error.column} ${error.message}`))
80
+ hasErrors = true
81
+ }
82
+
83
+ for (const warning of result.warnings) {
84
+ console.log(colors.yellow(` ⚠ ${warning.line}:${warning.column} ${warning.message}`))
85
+ }
86
+ }
87
+ }
88
+
89
+ // Summary
90
+ const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0)
91
+ const totalWarnings = results.reduce((sum, r) => sum + r.warnings.length, 0)
92
+
93
+ console.log()
94
+ if (totalErrors === 0 && totalWarnings === 0) {
95
+ console.log(colors.green('✅ All files passed validation!'))
96
+ } else {
97
+ console.log(colors.bold('Summary:'))
98
+ if (totalErrors > 0) {
99
+ console.log(colors.red(` ${totalErrors} error${totalErrors !== 1 ? 's' : ''}`))
100
+ }
101
+ if (totalWarnings > 0) {
102
+ console.log(colors.yellow(` ${totalWarnings} warning${totalWarnings !== 1 ? 's' : ''}`))
103
+ }
104
+
105
+ if (hasErrors) {
106
+ process.exit(1)
107
+ }
108
+ }
109
+
110
+ } catch (error) {
111
+ spin.fail('Validation failed')
112
+ console.error(colors.red(String(error)))
113
+ process.exit(1)
114
+ }
115
+ }
116
+ })
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bun
2
+ import { createCLI } from '@bunli/core'
3
+ import { initCommand } from './commands/init.js'
4
+ import { validateCommand } from './commands/validate.js'
5
+ import { serveCommand } from './commands/serve.js'
6
+ import { configCommand } from './commands/config.js'
7
+ import { loadConfig } from './utils/config.js'
8
+
9
+ const cli = createCLI({
10
+ name: '{{projectName}}',
11
+ version: '0.1.0',
12
+ description: '{{description}}'
13
+ })
14
+
15
+ // Global options
16
+ cli.option('verbose', {
17
+ type: 'boolean',
18
+ description: 'Enable verbose output'
19
+ })
20
+
21
+ cli.option('quiet', {
22
+ type: 'boolean',
23
+ description: 'Suppress output'
24
+ })
25
+
26
+ // Add commands
27
+ cli.command(initCommand)
28
+ cli.command(validateCommand)
29
+ cli.command(serveCommand)
30
+ cli.command(configCommand)
31
+
32
+ // Load config and run
33
+ async function run() {
34
+ try {
35
+ const config = await loadConfig()
36
+ // Store config in global context if needed
37
+ await cli.run()
38
+ } catch (error) {
39
+ console.error('Failed to start CLI:', error)
40
+ process.exit(1)
41
+ }
42
+ }
43
+
44
+ await run()
@@ -0,0 +1,83 @@
1
+ import { CONFIG_FILE_NAME, DEFAULT_CONFIG } from './constants.js'
2
+ import path from 'node:path'
3
+
4
+ export interface Config {
5
+ rules?: Record<string, any>
6
+ server?: {
7
+ port?: number
8
+ host?: string
9
+ open?: boolean
10
+ cors?: boolean
11
+ }
12
+ include?: string[]
13
+ exclude?: string[]
14
+ cache?: {
15
+ enabled?: boolean
16
+ directory?: string
17
+ }
18
+ hooks?: {
19
+ beforeValidate?: (files: string[]) => Promise<void>
20
+ afterValidate?: (results: any) => Promise<void>
21
+ }
22
+ }
23
+
24
+ let cachedConfig: Config | null = null
25
+
26
+ export async function loadConfig(configPath?: string): Promise<Config> {
27
+ // Return cached config if available
28
+ if (cachedConfig && !configPath) {
29
+ return cachedConfig
30
+ }
31
+
32
+ const finalPath = configPath || path.join(process.cwd(), CONFIG_FILE_NAME)
33
+
34
+ try {
35
+ // Check if config file exists
36
+ const file = Bun.file(finalPath)
37
+ if (!(await file.exists())) {
38
+ return DEFAULT_CONFIG
39
+ }
40
+
41
+ // Import the config file
42
+ const configModule = await import(finalPath)
43
+ const config = configModule.default || configModule
44
+
45
+ // Merge with defaults
46
+ cachedConfig = {
47
+ ...DEFAULT_CONFIG,
48
+ ...config,
49
+ server: {
50
+ ...DEFAULT_CONFIG.server,
51
+ ...(config.server || {})
52
+ }
53
+ }
54
+
55
+ return cachedConfig
56
+ } catch (error) {
57
+ console.warn(`Failed to load config from ${finalPath}:`, error)
58
+ return DEFAULT_CONFIG
59
+ }
60
+ }
61
+
62
+ export async function saveConfig(config: Config): Promise<void> {
63
+ const configPath = path.join(process.cwd(), CONFIG_FILE_NAME)
64
+
65
+ // Convert config to ES module format
66
+ const content = `export default ${JSON.stringify(config, null, 2)}`
67
+
68
+ await Bun.write(configPath, content)
69
+
70
+ // Clear cache
71
+ cachedConfig = null
72
+ }
73
+
74
+ export async function getConfigPath(): Promise<string> {
75
+ const configPath = path.join(process.cwd(), CONFIG_FILE_NAME)
76
+ const file = Bun.file(configPath)
77
+
78
+ if (await file.exists()) {
79
+ return configPath
80
+ }
81
+
82
+ return 'No config file found'
83
+ }
@@ -0,0 +1,12 @@
1
+ export const CONFIG_FILE_NAME = '{{projectName}}.config.js'
2
+
3
+ export const DEFAULT_CONFIG = {
4
+ rules: {},
5
+ server: {
6
+ port: 3000,
7
+ host: 'localhost',
8
+ open: true
9
+ },
10
+ include: ['src/**/*.{js,ts}'],
11
+ exclude: ['node_modules', 'dist']
12
+ }
@@ -0,0 +1,49 @@
1
+ import { Glob } from 'bun'
2
+ import path from 'node:path'
3
+
4
+ export interface GlobOptions {
5
+ include?: string[]
6
+ exclude?: string[]
7
+ }
8
+
9
+ export async function glob(patterns: string[], options: GlobOptions = {}): Promise<string[]> {
10
+ const { include = [], exclude = [] } = options
11
+
12
+ // Combine user patterns with include patterns
13
+ const allPatterns = [...patterns, ...include]
14
+
15
+ // Convert exclude patterns to absolute paths
16
+ const excludePatterns = exclude.map(pattern => {
17
+ if (pattern.startsWith('/')) {
18
+ return pattern
19
+ }
20
+ return path.join(process.cwd(), pattern)
21
+ })
22
+
23
+ const results = new Set<string>()
24
+
25
+ for (const pattern of allPatterns) {
26
+ const glob = new Glob(pattern)
27
+
28
+ for await (const file of glob.scan({
29
+ cwd: process.cwd(),
30
+ absolute: true
31
+ })) {
32
+ // Check if file should be excluded
33
+ let shouldExclude = false
34
+
35
+ for (const excludePattern of excludePatterns) {
36
+ if (file.includes(excludePattern)) {
37
+ shouldExclude = true
38
+ break
39
+ }
40
+ }
41
+
42
+ if (!shouldExclude) {
43
+ results.add(file)
44
+ }
45
+ }
46
+ }
47
+
48
+ return Array.from(results).sort()
49
+ }
@@ -0,0 +1,131 @@
1
+ export interface ValidationResult {
2
+ file: string
3
+ errors: ValidationIssue[]
4
+ warnings: ValidationIssue[]
5
+ }
6
+
7
+ export interface ValidationIssue {
8
+ line: number
9
+ column: number
10
+ message: string
11
+ rule: string
12
+ }
13
+
14
+ export interface ValidateOptions {
15
+ rules?: Record<string, any>
16
+ fix?: boolean
17
+ cache?: boolean
18
+ }
19
+
20
+ export async function validateFiles(
21
+ files: string[],
22
+ options: ValidateOptions = {}
23
+ ): Promise<ValidationResult[]> {
24
+ const { rules = {}, fix = false } = options
25
+ const results: ValidationResult[] = []
26
+
27
+ for (const file of files) {
28
+ const result = await validateFile(file, rules, fix)
29
+ results.push(result)
30
+ }
31
+
32
+ return results
33
+ }
34
+
35
+ async function validateFile(
36
+ filePath: string,
37
+ rules: Record<string, any>,
38
+ fix: boolean
39
+ ): Promise<ValidationResult> {
40
+ const errors: ValidationIssue[] = []
41
+ const warnings: ValidationIssue[] = []
42
+
43
+ try {
44
+ const content = await Bun.file(filePath).text()
45
+ const lines = content.split('\n')
46
+
47
+ // Example rule implementations
48
+ if (rules.noConsoleLog) {
49
+ lines.forEach((line, index) => {
50
+ const match = line.match(/console\.log\s*\(/)
51
+ if (match) {
52
+ errors.push({
53
+ line: index + 1,
54
+ column: match.index! + 1,
55
+ message: 'console.log is not allowed',
56
+ rule: 'noConsoleLog'
57
+ })
58
+ }
59
+ })
60
+ }
61
+
62
+ if (rules.noDebugger) {
63
+ lines.forEach((line, index) => {
64
+ const match = line.match(/\bdebugger\b/)
65
+ if (match) {
66
+ errors.push({
67
+ line: index + 1,
68
+ column: match.index! + 1,
69
+ message: 'debugger statement is not allowed',
70
+ rule: 'noDebugger'
71
+ })
72
+ }
73
+ })
74
+ }
75
+
76
+ if (rules.maxLineLength) {
77
+ const maxLength = typeof rules.maxLineLength === 'number' ? rules.maxLineLength : 100
78
+ lines.forEach((line, index) => {
79
+ if (line.length > maxLength) {
80
+ warnings.push({
81
+ line: index + 1,
82
+ column: maxLength + 1,
83
+ message: `Line exceeds maximum length of ${maxLength}`,
84
+ rule: 'maxLineLength'
85
+ })
86
+ }
87
+ })
88
+ }
89
+
90
+ if (rules.requireFileHeader) {
91
+ if (!content.startsWith('/*') && !content.startsWith('//')) {
92
+ errors.push({
93
+ line: 1,
94
+ column: 1,
95
+ message: 'File must start with a header comment',
96
+ rule: 'requireFileHeader'
97
+ })
98
+ }
99
+ }
100
+
101
+ // Auto-fix if requested
102
+ if (fix && errors.length > 0) {
103
+ // This is a simplified example - real fix logic would be more complex
104
+ let fixedContent = content
105
+
106
+ if (rules.noConsoleLog) {
107
+ fixedContent = fixedContent.replace(/console\.log\s*\([^)]*\);?/g, '')
108
+ }
109
+
110
+ if (rules.noDebugger) {
111
+ fixedContent = fixedContent.replace(/\bdebugger\b;?/g, '')
112
+ }
113
+
114
+ await Bun.write(filePath, fixedContent)
115
+ }
116
+
117
+ } catch (error) {
118
+ errors.push({
119
+ line: 0,
120
+ column: 0,
121
+ message: `Failed to validate file: ${error}`,
122
+ rule: 'system'
123
+ })
124
+ }
125
+
126
+ return {
127
+ file: filePath,
128
+ errors,
129
+ warnings
130
+ }
131
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "bunli-advanced",
3
+ "description": "Advanced Bunli CLI template with multiple commands",
4
+ "variables": [
5
+ {
6
+ "name": "projectName",
7
+ "message": "Project name",
8
+ "type": "string",
9
+ "default": "my-bunli-cli"
10
+ },
11
+ {
12
+ "name": "description",
13
+ "message": "Project description",
14
+ "type": "string",
15
+ "default": "A powerful CLI built with Bunli"
16
+ },
17
+ {
18
+ "name": "author",
19
+ "message": "Author name",
20
+ "type": "string",
21
+ "default": ""
22
+ },
23
+ {
24
+ "name": "license",
25
+ "message": "License",
26
+ "type": "select",
27
+ "default": "MIT",
28
+ "choices": [
29
+ { "label": "MIT", "value": "MIT" },
30
+ { "label": "Apache-2.0", "value": "Apache-2.0" },
31
+ { "label": "GPL-3.0", "value": "GPL-3.0" },
32
+ { "label": "BSD-3-Clause", "value": "BSD-3-Clause" },
33
+ { "label": "Unlicense", "value": "Unlicense" }
34
+ ]
35
+ }
36
+ ]
37
+ }
@@ -0,0 +1,34 @@
1
+ import { test, expect } from 'bun:test'
2
+ import { testCommand, expectCommand } from '@bunli/test'
3
+ import { initCommand } from '../src/commands/init.js'
4
+ import { validateCommand } from '../src/commands/validate.js'
5
+ import { configCommand } from '../src/commands/config.js'
6
+
7
+ test('init command - creates config file', async () => {
8
+ const result = await testCommand(initCommand, {
9
+ flags: { template: 'minimal', force: true }
10
+ })
11
+
12
+ expectCommand(result).toHaveSucceeded()
13
+ expectCommand(result).toContainInStdout('Config file created')
14
+ })
15
+
16
+ test('validate command - reports errors', async () => {
17
+ const result = await testCommand(validateCommand, {
18
+ args: ['src/**/*.ts'],
19
+ flags: { fix: false }
20
+ })
21
+
22
+ // Result depends on actual files, but command should run
23
+ expect(result.exitCode).toBeDefined()
24
+ })
25
+
26
+ test('config list command', async () => {
27
+ const listCommand = configCommand.subcommands?.find(cmd => cmd.name === 'list')
28
+ expect(listCommand).toBeDefined()
29
+
30
+ const result = await testCommand(listCommand!, {})
31
+
32
+ expectCommand(result).toHaveSucceeded()
33
+ expectCommand(result).toContainInStdout('Configuration:')
34
+ })