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,153 @@
|
|
|
1
|
+
import { defineCommand, option } from '@bunli/core'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { CONFIG_FILE_NAME, DEFAULT_CONFIG } from '../utils/constants.js'
|
|
4
|
+
|
|
5
|
+
export const initCommand = defineCommand({
|
|
6
|
+
name: 'init',
|
|
7
|
+
description: 'Initialize a new configuration file',
|
|
8
|
+
options: {
|
|
9
|
+
force: option(
|
|
10
|
+
z.boolean().default(false),
|
|
11
|
+
{
|
|
12
|
+
short: 'f',
|
|
13
|
+
description: 'Overwrite existing config'
|
|
14
|
+
}
|
|
15
|
+
),
|
|
16
|
+
template: option(
|
|
17
|
+
z.enum(['minimal', 'default', 'full']).default('default'),
|
|
18
|
+
{
|
|
19
|
+
short: 't',
|
|
20
|
+
description: 'Config template to use'
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
},
|
|
24
|
+
handler: async ({ flags, colors, prompt, spinner }) => {
|
|
25
|
+
const configPath = `${process.cwd()}/${CONFIG_FILE_NAME}`
|
|
26
|
+
|
|
27
|
+
// Check if config already exists
|
|
28
|
+
const configFile = Bun.file(configPath)
|
|
29
|
+
if (await configFile.exists() && !flags.force) {
|
|
30
|
+
const overwrite = await prompt.confirm(
|
|
31
|
+
`Config file already exists. Overwrite?`,
|
|
32
|
+
{ default: false }
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if (!overwrite) {
|
|
36
|
+
console.log(colors.yellow('Init cancelled'))
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const spin = spinner('Creating config file...')
|
|
42
|
+
spin.start()
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Get template content
|
|
46
|
+
const configContent = getConfigTemplate(flags.template)
|
|
47
|
+
|
|
48
|
+
// Write config file
|
|
49
|
+
await Bun.write(configPath, configContent)
|
|
50
|
+
|
|
51
|
+
spin.succeed('Config file created')
|
|
52
|
+
console.log(colors.dim(` ${CONFIG_FILE_NAME}`))
|
|
53
|
+
|
|
54
|
+
// Next steps
|
|
55
|
+
console.log()
|
|
56
|
+
console.log('Next steps:')
|
|
57
|
+
console.log(colors.gray(` 1. Edit ${CONFIG_FILE_NAME} to customize your configuration`))
|
|
58
|
+
console.log(colors.gray(` 2. Run '{{projectName}} validate' to check your files`))
|
|
59
|
+
|
|
60
|
+
} catch (error) {
|
|
61
|
+
spin.fail('Failed to create config file')
|
|
62
|
+
console.error(colors.red(String(error)))
|
|
63
|
+
process.exit(1)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
function getConfigTemplate(template: 'minimal' | 'default' | 'full'): string {
|
|
69
|
+
const templates = {
|
|
70
|
+
minimal: `export default ${JSON.stringify(DEFAULT_CONFIG, null, 2)}`,
|
|
71
|
+
|
|
72
|
+
default: `export default {
|
|
73
|
+
// Validation rules
|
|
74
|
+
rules: {
|
|
75
|
+
// Add your validation rules here
|
|
76
|
+
noConsoleLog: true,
|
|
77
|
+
requireFileHeader: false,
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Server configuration
|
|
81
|
+
server: {
|
|
82
|
+
port: 3000,
|
|
83
|
+
host: 'localhost',
|
|
84
|
+
open: true,
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// File patterns
|
|
88
|
+
include: ['src/**/*.{js,ts}'],
|
|
89
|
+
exclude: ['node_modules', 'dist', 'test'],
|
|
90
|
+
}`,
|
|
91
|
+
|
|
92
|
+
full: `import { defineConfig } from '{{projectName}}'
|
|
93
|
+
|
|
94
|
+
export default defineConfig({
|
|
95
|
+
// Validation rules
|
|
96
|
+
rules: {
|
|
97
|
+
// Code style rules
|
|
98
|
+
noConsoleLog: true,
|
|
99
|
+
noDebugger: true,
|
|
100
|
+
requireFileHeader: true,
|
|
101
|
+
maxLineLength: 100,
|
|
102
|
+
|
|
103
|
+
// Import rules
|
|
104
|
+
noUnusedImports: true,
|
|
105
|
+
sortImports: true,
|
|
106
|
+
|
|
107
|
+
// Function rules
|
|
108
|
+
maxFunctionLength: 50,
|
|
109
|
+
maxComplexity: 10,
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// Server configuration
|
|
113
|
+
server: {
|
|
114
|
+
port: process.env.PORT || 3000,
|
|
115
|
+
host: process.env.HOST || 'localhost',
|
|
116
|
+
open: !process.env.CI,
|
|
117
|
+
cors: true,
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// File patterns
|
|
121
|
+
include: [
|
|
122
|
+
'src/**/*.{js,ts,jsx,tsx}',
|
|
123
|
+
'scripts/**/*.{js,ts}',
|
|
124
|
+
],
|
|
125
|
+
exclude: [
|
|
126
|
+
'node_modules',
|
|
127
|
+
'dist',
|
|
128
|
+
'build',
|
|
129
|
+
'coverage',
|
|
130
|
+
'**/*.test.{js,ts}',
|
|
131
|
+
'**/*.spec.{js,ts}',
|
|
132
|
+
],
|
|
133
|
+
|
|
134
|
+
// Caching
|
|
135
|
+
cache: {
|
|
136
|
+
enabled: true,
|
|
137
|
+
directory: '.cache',
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// Hooks
|
|
141
|
+
hooks: {
|
|
142
|
+
beforeValidate: async (files) => {
|
|
143
|
+
console.log(\`Validating \${files.length} files...\`)
|
|
144
|
+
},
|
|
145
|
+
afterValidate: async (results) => {
|
|
146
|
+
console.log(\`Found \${results.errors} errors and \${results.warnings} warnings\`)
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
})`
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return templates[template]
|
|
153
|
+
}
|
|
@@ -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
|
+
}
|