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.
- package/README.md +302 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +310 -0
- package/dist/create-project.d.ts +13 -0
- package/dist/create.d.ts +13 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +217 -0
- package/dist/template-engine.d.ts +27 -0
- package/dist/templates/advanced/README.md +114 -0
- package/dist/templates/advanced/package.json +36 -0
- package/dist/templates/advanced/src/commands/config.ts +145 -0
- package/dist/templates/advanced/src/commands/init.ts +153 -0
- package/dist/templates/advanced/src/commands/serve.ts +176 -0
- package/dist/templates/advanced/src/commands/validate.ts +116 -0
- package/dist/templates/advanced/src/index.ts +44 -0
- package/dist/templates/advanced/src/utils/config.ts +83 -0
- package/dist/templates/advanced/src/utils/constants.ts +12 -0
- package/dist/templates/advanced/src/utils/glob.ts +49 -0
- package/dist/templates/advanced/src/utils/validator.ts +131 -0
- package/dist/templates/advanced/template.json +37 -0
- package/dist/templates/advanced/test/commands.test.ts +34 -0
- package/dist/templates/advanced/tsconfig.json +23 -0
- package/dist/templates/basic/README.md +41 -0
- package/dist/templates/basic/package.json +29 -0
- package/dist/templates/basic/src/commands/hello.ts +29 -0
- package/dist/templates/basic/src/index.ts +13 -0
- package/dist/templates/basic/template.json +31 -0
- package/dist/templates/basic/test/hello.test.ts +26 -0
- package/dist/templates/basic/tsconfig.json +19 -0
- package/dist/templates/monorepo/README.md +74 -0
- package/dist/templates/monorepo/package.json +28 -0
- package/dist/templates/monorepo/packages/cli/package.json +34 -0
- package/dist/templates/monorepo/packages/cli/src/index.ts +22 -0
- package/dist/templates/monorepo/packages/cli/tsconfig.json +15 -0
- package/dist/templates/monorepo/packages/core/package.json +32 -0
- package/dist/templates/monorepo/packages/core/scripts/build.ts +18 -0
- package/dist/templates/monorepo/packages/core/src/commands/analyze.ts +84 -0
- package/dist/templates/monorepo/packages/core/src/commands/process.ts +64 -0
- package/dist/templates/monorepo/packages/core/src/index.ts +3 -0
- package/dist/templates/monorepo/packages/core/src/types.ts +21 -0
- package/dist/templates/monorepo/packages/core/tsconfig.json +15 -0
- package/dist/templates/monorepo/packages/utils/package.json +26 -0
- package/dist/templates/monorepo/packages/utils/scripts/build.ts +17 -0
- package/dist/templates/monorepo/packages/utils/src/format.ts +27 -0
- package/dist/templates/monorepo/packages/utils/src/index.ts +3 -0
- package/dist/templates/monorepo/packages/utils/src/json.ts +11 -0
- package/dist/templates/monorepo/packages/utils/src/logger.ts +19 -0
- package/dist/templates/monorepo/packages/utils/tsconfig.json +12 -0
- package/dist/templates/monorepo/template.json +24 -0
- package/dist/templates/monorepo/tsconfig.json +14 -0
- package/dist/templates/monorepo/turbo.json +28 -0
- package/dist/types.d.ts +48 -0
- package/package.json +57 -0
- package/templates/advanced/README.md +114 -0
- package/templates/advanced/package.json +36 -0
- package/templates/advanced/src/commands/config.ts +145 -0
- package/templates/advanced/src/commands/init.ts +153 -0
- package/templates/advanced/src/commands/serve.ts +176 -0
- package/templates/advanced/src/commands/validate.ts +116 -0
- package/templates/advanced/src/index.ts +44 -0
- package/templates/advanced/src/utils/config.ts +83 -0
- package/templates/advanced/src/utils/constants.ts +12 -0
- package/templates/advanced/src/utils/glob.ts +49 -0
- package/templates/advanced/src/utils/validator.ts +131 -0
- package/templates/advanced/template.json +37 -0
- package/templates/advanced/test/commands.test.ts +34 -0
- package/templates/advanced/tsconfig.json +23 -0
- package/templates/basic/README.md +41 -0
- package/templates/basic/package.json +29 -0
- package/templates/basic/src/commands/hello.ts +29 -0
- package/templates/basic/src/index.ts +13 -0
- package/templates/basic/template.json +31 -0
- package/templates/basic/test/hello.test.ts +26 -0
- package/templates/basic/tsconfig.json +19 -0
- package/templates/monorepo/README.md +74 -0
- package/templates/monorepo/package.json +28 -0
- package/templates/monorepo/packages/cli/package.json +34 -0
- package/templates/monorepo/packages/cli/src/index.ts +22 -0
- package/templates/monorepo/packages/cli/tsconfig.json +15 -0
- package/templates/monorepo/packages/core/package.json +32 -0
- package/templates/monorepo/packages/core/scripts/build.ts +18 -0
- package/templates/monorepo/packages/core/src/commands/analyze.ts +84 -0
- package/templates/monorepo/packages/core/src/commands/process.ts +64 -0
- package/templates/monorepo/packages/core/src/index.ts +3 -0
- package/templates/monorepo/packages/core/src/types.ts +21 -0
- package/templates/monorepo/packages/core/tsconfig.json +15 -0
- package/templates/monorepo/packages/utils/package.json +26 -0
- package/templates/monorepo/packages/utils/scripts/build.ts +17 -0
- package/templates/monorepo/packages/utils/src/format.ts +27 -0
- package/templates/monorepo/packages/utils/src/index.ts +3 -0
- package/templates/monorepo/packages/utils/src/json.ts +11 -0
- package/templates/monorepo/packages/utils/src/logger.ts +19 -0
- package/templates/monorepo/packages/utils/tsconfig.json +12 -0
- package/templates/monorepo/template.json +24 -0
- package/templates/monorepo/tsconfig.json +14 -0
- 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,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
|
+
})
|