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
package/dist/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/template-engine.ts
|
|
3
|
+
import { downloadTemplate } from "giget";
|
|
4
|
+
import { readdir } from "fs/promises";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
async function processTemplate(options) {
|
|
7
|
+
const { source, dir, offline, variables = {} } = options;
|
|
8
|
+
let templateDir;
|
|
9
|
+
if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
|
|
10
|
+
const sourceDir = source.startsWith("/") ? source : join(process.cwd(), source);
|
|
11
|
+
await Bun.spawn(["cp", "-r", sourceDir + "/.", dir], {
|
|
12
|
+
stdout: "inherit",
|
|
13
|
+
stderr: "inherit"
|
|
14
|
+
}).exited;
|
|
15
|
+
templateDir = dir;
|
|
16
|
+
} else {
|
|
17
|
+
const result = await downloadTemplate(source, {
|
|
18
|
+
dir,
|
|
19
|
+
offline,
|
|
20
|
+
preferOffline: true,
|
|
21
|
+
force: true
|
|
22
|
+
});
|
|
23
|
+
templateDir = result.dir;
|
|
24
|
+
}
|
|
25
|
+
const manifest = await loadTemplateManifest(templateDir);
|
|
26
|
+
if (manifest?.files || Object.keys(variables).length > 0) {
|
|
27
|
+
await processTemplateFiles(templateDir, variables, manifest);
|
|
28
|
+
}
|
|
29
|
+
if (manifest?.hooks?.postInstall) {
|
|
30
|
+
await runPostInstallHooks(templateDir, manifest.hooks.postInstall);
|
|
31
|
+
}
|
|
32
|
+
return { dir: templateDir, manifest };
|
|
33
|
+
}
|
|
34
|
+
async function loadTemplateManifest(dir) {
|
|
35
|
+
const possiblePaths = [
|
|
36
|
+
join(dir, "template.json"),
|
|
37
|
+
join(dir, ".template.json"),
|
|
38
|
+
join(dir, "template.yaml"),
|
|
39
|
+
join(dir, ".template.yaml")
|
|
40
|
+
];
|
|
41
|
+
for (const path of possiblePaths) {
|
|
42
|
+
const file = Bun.file(path);
|
|
43
|
+
if (await file.exists()) {
|
|
44
|
+
const content = await file.text();
|
|
45
|
+
if (path.endsWith(".json")) {
|
|
46
|
+
const manifest = JSON.parse(content);
|
|
47
|
+
try {
|
|
48
|
+
await Bun.spawn(["rm", "-f", path], {
|
|
49
|
+
stdout: "ignore",
|
|
50
|
+
stderr: "ignore"
|
|
51
|
+
}).exited;
|
|
52
|
+
} catch {}
|
|
53
|
+
return manifest;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
async function processTemplateFiles(dir, variables, manifest) {
|
|
60
|
+
const files = await getFilesToProcess(dir, manifest);
|
|
61
|
+
for (const file of files) {
|
|
62
|
+
const filePath = join(dir, file);
|
|
63
|
+
const content = await Bun.file(filePath).text();
|
|
64
|
+
let processedContent = content;
|
|
65
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
66
|
+
processedContent = processedContent.replaceAll(`{{${key}}}`, value).replaceAll(`<%= ${key} %>`, value).replaceAll(`$${key}`, value).replaceAll(`__${key}__`, value);
|
|
67
|
+
}
|
|
68
|
+
let newFilePath = filePath;
|
|
69
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
70
|
+
newFilePath = newFilePath.replaceAll(`__${key}__`, value);
|
|
71
|
+
}
|
|
72
|
+
await Bun.write(newFilePath, processedContent);
|
|
73
|
+
if (newFilePath !== filePath) {
|
|
74
|
+
await Bun.spawn(["rm", filePath]).exited;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function getFilesToProcess(dir, manifest) {
|
|
79
|
+
if (manifest?.files?.include) {
|
|
80
|
+
return manifest.files.include;
|
|
81
|
+
}
|
|
82
|
+
const files = [];
|
|
83
|
+
async function walk(currentDir, prefix = "") {
|
|
84
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
const path = join(prefix, entry.name);
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
if (!["node_modules", ".git", ".next", "dist", "build"].includes(entry.name)) {
|
|
89
|
+
await walk(join(currentDir, entry.name), path);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
if (!path.match(/^(template\.json|\.template\.json|\.DS_Store|Thumbs\.db)$/)) {
|
|
93
|
+
files.push(path);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
await walk(dir);
|
|
99
|
+
return files;
|
|
100
|
+
}
|
|
101
|
+
async function runPostInstallHooks(dir, hooks) {
|
|
102
|
+
for (const hook of hooks) {
|
|
103
|
+
const proc = Bun.spawn(hook.split(" "), {
|
|
104
|
+
cwd: dir,
|
|
105
|
+
stdout: "inherit",
|
|
106
|
+
stderr: "inherit"
|
|
107
|
+
});
|
|
108
|
+
await proc.exited;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function resolveTemplateSource(template) {
|
|
112
|
+
const specialTemplates = {
|
|
113
|
+
basic: "github:bunli/templates/basic",
|
|
114
|
+
advanced: "github:bunli/templates/advanced",
|
|
115
|
+
monorepo: "github:bunli/templates/monorepo"
|
|
116
|
+
};
|
|
117
|
+
if (specialTemplates[template]) {
|
|
118
|
+
return specialTemplates[template];
|
|
119
|
+
}
|
|
120
|
+
if (template.startsWith("npm:")) {
|
|
121
|
+
return template.replace("npm:", "npm:/");
|
|
122
|
+
}
|
|
123
|
+
if (template.includes("/") && !template.includes(":")) {
|
|
124
|
+
return `github:${template}`;
|
|
125
|
+
}
|
|
126
|
+
return template;
|
|
127
|
+
}
|
|
128
|
+
function getBundledTemplatePath(name) {
|
|
129
|
+
return join(import.meta.dir, "..", "templates", name);
|
|
130
|
+
}
|
|
131
|
+
async function isLocalTemplate(template) {
|
|
132
|
+
if (template.startsWith("file:") || template.startsWith("./") || template.startsWith("../")) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
const bundledPath = getBundledTemplatePath(template);
|
|
136
|
+
return await Bun.file(join(bundledPath, "package.json")).exists();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/create-project.ts
|
|
140
|
+
async function createProject(options) {
|
|
141
|
+
const { name, dir, template, git, install, packageManager, prompt, spinner, colors, shell, offline } = options;
|
|
142
|
+
try {
|
|
143
|
+
await shell`test -d ${dir}`.quiet();
|
|
144
|
+
const overwrite = await prompt.confirm(`Directory ${dir} already exists. Overwrite?`, { default: false });
|
|
145
|
+
if (!overwrite) {
|
|
146
|
+
console.log(colors.red("Cancelled"));
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
await shell`rm -rf ${dir}`;
|
|
150
|
+
} catch {}
|
|
151
|
+
const spin = spinner("Creating project structure...");
|
|
152
|
+
spin.start();
|
|
153
|
+
await shell`mkdir -p ${dir}`;
|
|
154
|
+
try {
|
|
155
|
+
let templateSource = template;
|
|
156
|
+
if (await isLocalTemplate(template)) {
|
|
157
|
+
templateSource = getBundledTemplatePath(template);
|
|
158
|
+
} else {
|
|
159
|
+
templateSource = resolveTemplateSource(template);
|
|
160
|
+
}
|
|
161
|
+
const { manifest } = await processTemplate({
|
|
162
|
+
source: templateSource,
|
|
163
|
+
dir,
|
|
164
|
+
offline,
|
|
165
|
+
variables: {
|
|
166
|
+
projectName: name,
|
|
167
|
+
description: `A CLI built with Bunli`,
|
|
168
|
+
author: "",
|
|
169
|
+
packageManager: packageManager || "bun",
|
|
170
|
+
year: new Date().getFullYear().toString()
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
spin.succeed("Project structure created");
|
|
174
|
+
if (git) {
|
|
175
|
+
const gitSpin = spinner("Initializing git repository...");
|
|
176
|
+
gitSpin.start();
|
|
177
|
+
try {
|
|
178
|
+
await shell`cd ${dir} && git init`.quiet();
|
|
179
|
+
await shell`cd ${dir} && git add .`.quiet();
|
|
180
|
+
await shell`cd ${dir} && git commit -m "Initial commit"`.quiet();
|
|
181
|
+
gitSpin.succeed("Git repository initialized");
|
|
182
|
+
} catch (error) {
|
|
183
|
+
gitSpin.fail("Failed to initialize git repository");
|
|
184
|
+
console.error(colors.dim(` ${error}`));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (install) {
|
|
188
|
+
const installSpin = spinner(`Installing dependencies with ${packageManager}...`);
|
|
189
|
+
installSpin.start();
|
|
190
|
+
try {
|
|
191
|
+
const installCmd = packageManager === "bun" ? "bun install" : packageManager === "pnpm" ? "pnpm install" : packageManager === "yarn" ? "yarn install" : "npm install";
|
|
192
|
+
await shell`cd ${dir} && ${installCmd}`;
|
|
193
|
+
installSpin.succeed("Dependencies installed");
|
|
194
|
+
} catch (error) {
|
|
195
|
+
installSpin.fail("Failed to install dependencies");
|
|
196
|
+
console.error(colors.dim(` You can install them manually by running: ${packageManager} install`));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
spin.fail("Failed to create project");
|
|
201
|
+
console.error(colors.red(`Error: ${error}`));
|
|
202
|
+
try {
|
|
203
|
+
await shell`rm -rf ${dir}`.quiet();
|
|
204
|
+
} catch {}
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/index.ts
|
|
210
|
+
var version = "0.1.0";
|
|
211
|
+
export {
|
|
212
|
+
version,
|
|
213
|
+
resolveTemplateSource,
|
|
214
|
+
processTemplate,
|
|
215
|
+
isLocalTemplate,
|
|
216
|
+
createProject
|
|
217
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { TemplateManifest } from './types.js';
|
|
2
|
+
export interface TemplateOptions {
|
|
3
|
+
source: string;
|
|
4
|
+
type?: 'github' | 'npm' | 'local' | 'bundled';
|
|
5
|
+
dir: string;
|
|
6
|
+
offline?: boolean;
|
|
7
|
+
variables?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Download and process a template
|
|
11
|
+
*/
|
|
12
|
+
export declare function processTemplate(options: TemplateOptions): Promise<{
|
|
13
|
+
dir: string;
|
|
14
|
+
manifest: TemplateManifest | null;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Resolve template source to giget-compatible format
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveTemplateSource(template: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Get bundled template path
|
|
22
|
+
*/
|
|
23
|
+
export declare function getBundledTemplatePath(name: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Check if template exists locally (for development)
|
|
26
|
+
*/
|
|
27
|
+
export declare function isLocalTemplate(template: string): Promise<boolean>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
{{description}}
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install globally
|
|
9
|
+
bun add -g {{projectName}}
|
|
10
|
+
|
|
11
|
+
# Or use directly with bunx
|
|
12
|
+
bunx {{projectName}} [command]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
{{projectName}} <command> [options]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Commands
|
|
22
|
+
|
|
23
|
+
#### `init`
|
|
24
|
+
Initialize a new configuration file in the current directory.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
{{projectName}} init [options]
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
-f, --force Overwrite existing config
|
|
31
|
+
-t, --template Config template to use
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### `validate`
|
|
35
|
+
Validate files against defined rules.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
{{projectName}} validate <files...> [options]
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
-c, --config Path to config file
|
|
42
|
+
-f, --fix Auto-fix issues
|
|
43
|
+
--no-cache Disable caching
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### `serve`
|
|
47
|
+
Start a development server.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
{{projectName}} serve [options]
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
-p, --port Port to listen on (default: 3000)
|
|
54
|
+
-h, --host Host to bind to (default: localhost)
|
|
55
|
+
--no-open Don't open browser
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### `config`
|
|
59
|
+
Manage configuration settings.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
{{projectName}} config <action> [key] [value]
|
|
63
|
+
|
|
64
|
+
Actions:
|
|
65
|
+
get <key> Get a config value
|
|
66
|
+
set <key> <value> Set a config value
|
|
67
|
+
list List all config values
|
|
68
|
+
reset Reset to defaults
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Global Options
|
|
72
|
+
|
|
73
|
+
- `-v, --version` - Show version
|
|
74
|
+
- `-h, --help` - Show help
|
|
75
|
+
- `--verbose` - Enable verbose output
|
|
76
|
+
- `--quiet` - Suppress output
|
|
77
|
+
- `--no-color` - Disable colored output
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
Create a `{{projectName}}.config.js` file in your project root:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
export default {
|
|
85
|
+
// Configuration options
|
|
86
|
+
rules: {
|
|
87
|
+
// Define your rules
|
|
88
|
+
},
|
|
89
|
+
server: {
|
|
90
|
+
port: 3000,
|
|
91
|
+
host: 'localhost'
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Install dependencies
|
|
100
|
+
bun install
|
|
101
|
+
|
|
102
|
+
# Run in development
|
|
103
|
+
bun dev
|
|
104
|
+
|
|
105
|
+
# Run tests
|
|
106
|
+
bun test
|
|
107
|
+
|
|
108
|
+
# Build for production
|
|
109
|
+
bun run build
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
{{license}}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "{{description}}",
|
|
6
|
+
"author": "{{author}}",
|
|
7
|
+
"license": "{{license}}",
|
|
8
|
+
"bin": {
|
|
9
|
+
"{{projectName}}": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "bun run src/index.ts",
|
|
13
|
+
"build": "bunli build",
|
|
14
|
+
"test": "bun test",
|
|
15
|
+
"test:watch": "bun test --watch",
|
|
16
|
+
"type-check": "tsc --noEmit",
|
|
17
|
+
"lint": "tsc --noEmit",
|
|
18
|
+
"prepare": "bun run build"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@bunli/core": "latest",
|
|
22
|
+
"@bunli/utils": "latest",
|
|
23
|
+
"zod": "^3.22.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@bunli/test": "latest",
|
|
27
|
+
"@types/bun": "latest",
|
|
28
|
+
"bunli": "latest",
|
|
29
|
+
"typescript": "^5.0.0"
|
|
30
|
+
},
|
|
31
|
+
"bunli": {
|
|
32
|
+
"entry": "./src/index.ts",
|
|
33
|
+
"outDir": "./dist",
|
|
34
|
+
"external": ["@bunli/core", "@bunli/utils", "zod"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { defineCommand, option } from '@bunli/core'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { loadConfig, saveConfig, getConfigPath } from '../utils/config.js'
|
|
4
|
+
|
|
5
|
+
export const configCommand = defineCommand({
|
|
6
|
+
name: 'config',
|
|
7
|
+
description: 'Manage configuration',
|
|
8
|
+
subcommands: [
|
|
9
|
+
defineCommand({
|
|
10
|
+
name: 'get',
|
|
11
|
+
description: 'Get a config value',
|
|
12
|
+
args: z.tuple([z.string()]).describe('Config key to get'),
|
|
13
|
+
handler: async ({ args, colors }) => {
|
|
14
|
+
const [key] = args
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const config = await loadConfig()
|
|
18
|
+
const value = getNestedValue(config, key)
|
|
19
|
+
|
|
20
|
+
if (value === undefined) {
|
|
21
|
+
console.log(colors.yellow(`Config key '${key}' not found`))
|
|
22
|
+
} else {
|
|
23
|
+
console.log(JSON.stringify(value, null, 2))
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(colors.red(`Failed to load config: ${error}`))
|
|
27
|
+
process.exit(1)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}),
|
|
31
|
+
|
|
32
|
+
defineCommand({
|
|
33
|
+
name: 'set',
|
|
34
|
+
description: 'Set a config value',
|
|
35
|
+
args: z.tuple([z.string(), z.string()]).describe('Config key and value'),
|
|
36
|
+
handler: async ({ args, colors, spinner }) => {
|
|
37
|
+
const [key, value] = args
|
|
38
|
+
|
|
39
|
+
const spin = spinner('Updating config...')
|
|
40
|
+
spin.start()
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const config = await loadConfig()
|
|
44
|
+
setNestedValue(config, key, JSON.parse(value))
|
|
45
|
+
await saveConfig(config)
|
|
46
|
+
|
|
47
|
+
spin.succeed(`Config '${key}' updated`)
|
|
48
|
+
} catch (error) {
|
|
49
|
+
spin.fail('Failed to update config')
|
|
50
|
+
console.error(colors.red(String(error)))
|
|
51
|
+
process.exit(1)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
55
|
+
|
|
56
|
+
defineCommand({
|
|
57
|
+
name: 'list',
|
|
58
|
+
description: 'List all config values',
|
|
59
|
+
handler: async ({ colors }) => {
|
|
60
|
+
try {
|
|
61
|
+
const config = await loadConfig()
|
|
62
|
+
const configPath = await getConfigPath()
|
|
63
|
+
|
|
64
|
+
console.log(colors.bold('Configuration:'))
|
|
65
|
+
console.log(colors.dim(` File: ${configPath}`))
|
|
66
|
+
console.log()
|
|
67
|
+
console.log(JSON.stringify(config, null, 2))
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(colors.red(`Failed to load config: ${error}`))
|
|
70
|
+
process.exit(1)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}),
|
|
74
|
+
|
|
75
|
+
defineCommand({
|
|
76
|
+
name: 'reset',
|
|
77
|
+
description: 'Reset config to defaults',
|
|
78
|
+
options: {
|
|
79
|
+
force: option(
|
|
80
|
+
z.boolean().default(false),
|
|
81
|
+
{
|
|
82
|
+
short: 'f',
|
|
83
|
+
description: 'Skip confirmation'
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
},
|
|
87
|
+
handler: async ({ flags, colors, prompt, spinner }) => {
|
|
88
|
+
if (!flags.force) {
|
|
89
|
+
const confirmed = await prompt.confirm(
|
|
90
|
+
'This will reset all config to defaults. Continue?',
|
|
91
|
+
{ default: false }
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if (!confirmed) {
|
|
95
|
+
console.log(colors.yellow('Reset cancelled'))
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const spin = spinner('Resetting config...')
|
|
101
|
+
spin.start()
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const { DEFAULT_CONFIG } = await import('../utils/constants.js')
|
|
105
|
+
await saveConfig(DEFAULT_CONFIG)
|
|
106
|
+
|
|
107
|
+
spin.succeed('Config reset to defaults')
|
|
108
|
+
} catch (error) {
|
|
109
|
+
spin.fail('Failed to reset config')
|
|
110
|
+
console.error(colors.red(String(error)))
|
|
111
|
+
process.exit(1)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
]
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
function getNestedValue(obj: any, path: string): any {
|
|
119
|
+
const keys = path.split('.')
|
|
120
|
+
let current = obj
|
|
121
|
+
|
|
122
|
+
for (const key of keys) {
|
|
123
|
+
if (current === null || current === undefined) {
|
|
124
|
+
return undefined
|
|
125
|
+
}
|
|
126
|
+
current = current[key]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return current
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function setNestedValue(obj: any, path: string, value: any): void {
|
|
133
|
+
const keys = path.split('.')
|
|
134
|
+
const lastKey = keys.pop()!
|
|
135
|
+
let current = obj
|
|
136
|
+
|
|
137
|
+
for (const key of keys) {
|
|
138
|
+
if (!(key in current) || typeof current[key] !== 'object') {
|
|
139
|
+
current[key] = {}
|
|
140
|
+
}
|
|
141
|
+
current = current[key]
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
current[lastKey] = value
|
|
145
|
+
}
|
|
@@ -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
|
+
}
|