create-bunli 0.8.0 → 0.8.2

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 (104) hide show
  1. package/README.md +18 -6
  2. package/dist/cli.js +589 -560
  3. package/dist/create-project.d.ts +4 -4
  4. package/dist/create-project.d.ts.map +1 -1
  5. package/dist/create.d.ts +3 -3
  6. package/dist/create.d.ts.map +1 -1
  7. package/dist/index.d.ts +5 -5
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +518 -512
  10. package/dist/steps.d.ts +5 -5
  11. package/dist/steps.d.ts.map +1 -1
  12. package/dist/template-engine.d.ts +2 -2
  13. package/dist/template-engine.d.ts.map +1 -1
  14. package/dist/templates/advanced/README.md +8 -4
  15. package/dist/templates/advanced/bunli.config.ts +19 -19
  16. package/dist/templates/advanced/package.json +8 -4
  17. package/dist/templates/advanced/src/commands/config.ts +129 -119
  18. package/dist/templates/advanced/src/commands/init.ts +53 -60
  19. package/dist/templates/advanced/src/commands/serve.ts +77 -83
  20. package/dist/templates/advanced/src/commands/validate.ts +58 -66
  21. package/dist/templates/advanced/src/index.ts +30 -29
  22. package/dist/templates/advanced/src/utils/config.ts +48 -47
  23. package/dist/templates/advanced/src/utils/constants.ts +6 -6
  24. package/dist/templates/advanced/src/utils/glob.ts +29 -28
  25. package/dist/templates/advanced/src/utils/validator.ts +60 -61
  26. package/dist/templates/advanced/template.json +2 -6
  27. package/dist/templates/advanced/tsconfig.json +1 -1
  28. package/dist/templates/basic/README.md +1 -1
  29. package/dist/templates/basic/bunli.config.ts +17 -17
  30. package/dist/templates/basic/package.json +3 -3
  31. package/dist/templates/basic/src/commands/hello.ts +20 -26
  32. package/dist/templates/basic/src/index.ts +9 -8
  33. package/dist/templates/basic/template.json +2 -6
  34. package/dist/templates/basic/tsconfig.json +1 -1
  35. package/dist/templates/monorepo/README.md +1 -1
  36. package/dist/templates/monorepo/bunli.config.ts +21 -21
  37. package/dist/templates/monorepo/package.json +3 -3
  38. package/dist/templates/monorepo/packages/cli/package.json +9 -5
  39. package/dist/templates/monorepo/packages/cli/src/index.ts +13 -13
  40. package/dist/templates/monorepo/packages/cli/tsconfig.json +2 -5
  41. package/dist/templates/monorepo/packages/core/package.json +4 -4
  42. package/dist/templates/monorepo/packages/core/scripts/build.ts +10 -10
  43. package/dist/templates/monorepo/packages/core/src/commands/analyze.ts +58 -57
  44. package/dist/templates/monorepo/packages/core/src/commands/process.ts +39 -47
  45. package/dist/templates/monorepo/packages/core/src/index.ts +3 -3
  46. package/dist/templates/monorepo/packages/core/src/types.ts +15 -15
  47. package/dist/templates/monorepo/packages/core/tsconfig.json +2 -4
  48. package/dist/templates/monorepo/packages/utils/package.json +4 -4
  49. package/dist/templates/monorepo/packages/utils/scripts/build.ts +10 -10
  50. package/dist/templates/monorepo/packages/utils/src/format.ts +19 -19
  51. package/dist/templates/monorepo/packages/utils/src/index.ts +3 -3
  52. package/dist/templates/monorepo/packages/utils/src/json.ts +4 -4
  53. package/dist/templates/monorepo/packages/utils/src/logger.ts +9 -9
  54. package/dist/templates/monorepo/packages/utils/tsconfig.json +1 -1
  55. package/dist/templates/monorepo/template.json +2 -6
  56. package/dist/templates/monorepo/tsconfig.json +1 -1
  57. package/dist/templates/monorepo/turbo.json +1 -1
  58. package/dist/types.d.ts +1 -1
  59. package/dist/types.d.ts.map +1 -1
  60. package/package.json +28 -28
  61. package/templates/advanced/README.md +8 -4
  62. package/templates/advanced/bunli.config.ts +19 -19
  63. package/templates/advanced/package.json +8 -4
  64. package/templates/advanced/src/commands/config.ts +129 -119
  65. package/templates/advanced/src/commands/init.ts +53 -60
  66. package/templates/advanced/src/commands/serve.ts +77 -83
  67. package/templates/advanced/src/commands/validate.ts +58 -66
  68. package/templates/advanced/src/index.ts +30 -29
  69. package/templates/advanced/src/utils/config.ts +48 -47
  70. package/templates/advanced/src/utils/constants.ts +6 -6
  71. package/templates/advanced/src/utils/glob.ts +29 -28
  72. package/templates/advanced/src/utils/validator.ts +60 -61
  73. package/templates/advanced/template.json +2 -6
  74. package/templates/advanced/tsconfig.json +1 -1
  75. package/templates/basic/README.md +1 -1
  76. package/templates/basic/bunli.config.ts +17 -17
  77. package/templates/basic/package.json +3 -3
  78. package/templates/basic/src/commands/hello.ts +20 -26
  79. package/templates/basic/src/index.ts +9 -8
  80. package/templates/basic/template.json +2 -6
  81. package/templates/basic/tsconfig.json +1 -1
  82. package/templates/monorepo/README.md +1 -1
  83. package/templates/monorepo/bunli.config.ts +21 -21
  84. package/templates/monorepo/package.json +3 -3
  85. package/templates/monorepo/packages/cli/package.json +9 -5
  86. package/templates/monorepo/packages/cli/src/index.ts +13 -13
  87. package/templates/monorepo/packages/cli/tsconfig.json +2 -5
  88. package/templates/monorepo/packages/core/package.json +4 -4
  89. package/templates/monorepo/packages/core/scripts/build.ts +10 -10
  90. package/templates/monorepo/packages/core/src/commands/analyze.ts +58 -57
  91. package/templates/monorepo/packages/core/src/commands/process.ts +39 -47
  92. package/templates/monorepo/packages/core/src/index.ts +3 -3
  93. package/templates/monorepo/packages/core/src/types.ts +15 -15
  94. package/templates/monorepo/packages/core/tsconfig.json +2 -4
  95. package/templates/monorepo/packages/utils/package.json +4 -4
  96. package/templates/monorepo/packages/utils/scripts/build.ts +10 -10
  97. package/templates/monorepo/packages/utils/src/format.ts +19 -19
  98. package/templates/monorepo/packages/utils/src/index.ts +3 -3
  99. package/templates/monorepo/packages/utils/src/json.ts +4 -4
  100. package/templates/monorepo/packages/utils/src/logger.ts +9 -9
  101. package/templates/monorepo/packages/utils/tsconfig.json +1 -1
  102. package/templates/monorepo/template.json +2 -6
  103. package/templates/monorepo/tsconfig.json +1 -1
  104. package/templates/monorepo/turbo.json +1 -1
package/dist/cli.js CHANGED
@@ -3,421 +3,135 @@
3
3
 
4
4
  // src/cli.ts
5
5
  import { createCLI, defineCommand, option } from "@bunli/core";
6
- import { z } from "zod";
7
6
 
8
- // src/template-engine.ts
9
- import { downloadTemplate } from "giget";
10
- import { readdir } from "fs/promises";
11
- import { join } from "path";
12
- async function processTemplate(options) {
13
- const { source, dir, offline, variables = {} } = options;
14
- let templateDir;
15
- if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
16
- const sourceDir = source.startsWith("/") ? source : join(process.cwd(), source);
17
- const copyExitCode = await Bun.spawn(["cp", "-r", sourceDir + "/.", dir], {
18
- stdout: "inherit",
19
- stderr: "inherit"
20
- }).exited;
21
- if (copyExitCode !== 0) {
22
- throw new Error(`Failed to copy local template from ${sourceDir}`);
23
- }
24
- templateDir = dir;
25
- } else {
26
- const result = await downloadTemplate(source, {
27
- dir,
28
- offline,
29
- preferOffline: true,
30
- force: true
31
- });
32
- templateDir = result.dir;
33
- }
34
- const manifest = await loadTemplateManifest(templateDir);
35
- if (manifest?.files || Object.keys(variables).length > 0) {
36
- await processTemplateFiles(templateDir, variables, manifest);
37
- }
38
- if (manifest?.hooks?.postInstall) {
39
- await runPostInstallHooks(templateDir, manifest.hooks.postInstall);
40
- }
41
- return { dir: templateDir, manifest };
42
- }
43
- async function loadTemplateManifest(dir) {
44
- const possiblePaths = [
45
- join(dir, "template.json"),
46
- join(dir, ".template.json"),
47
- join(dir, "template.yaml"),
48
- join(dir, ".template.yaml")
49
- ];
50
- for (const path of possiblePaths) {
51
- const file = Bun.file(path);
52
- if (await file.exists()) {
53
- const content = await file.text();
54
- if (path.endsWith(".json")) {
55
- const manifest = JSON.parse(content);
56
- const removeExitCode = await Bun.spawn(["rm", "-f", path], {
57
- stdout: "ignore",
58
- stderr: "ignore"
59
- }).exited;
60
- if (removeExitCode !== 0) {
61
- console.warn(`Warning: failed to remove template manifest ${path}`);
62
- }
63
- return manifest;
64
- }
65
- }
66
- }
67
- return null;
7
+ // ../../node_modules/.bun/better-result@2.7.0/node_modules/better-result/dist/index.mjs
8
+ function dual(arity, body) {
9
+ if (arity === 2)
10
+ return (...args) => {
11
+ if (args.length >= 2)
12
+ return body(args[0], args[1]);
13
+ return (self) => body(self, args[0]);
14
+ };
15
+ if (arity === 3)
16
+ return (...args) => {
17
+ if (args.length >= 3)
18
+ return body(args[0], args[1], args[2]);
19
+ return (self) => body(self, args[0], args[1]);
20
+ };
21
+ if (arity === 4)
22
+ return (...args) => {
23
+ if (args.length >= 4)
24
+ return body(args[0], args[1], args[2], args[3]);
25
+ return (self) => body(self, args[0], args[1], args[2]);
26
+ };
27
+ return (...args) => {
28
+ if (args.length >= arity)
29
+ return body(...args);
30
+ return (self) => body(self, ...args);
31
+ };
68
32
  }
69
- async function processTemplateFiles(dir, variables, manifest) {
70
- const files = await getFilesToProcess(dir, manifest);
71
- for (const file of files) {
72
- const filePath = join(dir, file);
73
- const content = await Bun.file(filePath).text();
74
- let processedContent = content;
75
- for (const [key, value] of Object.entries(variables)) {
76
- processedContent = processedContent.replaceAll(`{{${key}}}`, value).replaceAll(`<%= ${key} %>`, value).replaceAll(`$${key}`, value).replaceAll(`__${key}__`, value);
77
- }
78
- let newFilePath = filePath;
79
- for (const [key, value] of Object.entries(variables)) {
80
- newFilePath = newFilePath.replaceAll(`__${key}__`, value);
81
- }
82
- await Bun.write(newFilePath, processedContent);
83
- if (newFilePath !== filePath) {
84
- await Bun.spawn(["rm", filePath]).exited;
33
+ var serializeCause = (cause) => {
34
+ if (cause instanceof Error)
35
+ return {
36
+ name: cause.name,
37
+ message: cause.message,
38
+ stack: cause.stack
39
+ };
40
+ return cause;
41
+ };
42
+ var isAnyTaggedError = (value) => {
43
+ return value instanceof Error && "_tag" in value && typeof value._tag === "string";
44
+ };
45
+ var TaggedError = Object.assign((tag) => () => {
46
+
47
+ class Base extends Error {
48
+ _tag = tag;
49
+ static is(value) {
50
+ return value instanceof Base;
85
51
  }
86
- }
87
- }
88
- async function getFilesToProcess(dir, manifest) {
89
- if (manifest?.files?.include) {
90
- return manifest.files.include;
91
- }
92
- const files = [];
93
- async function walk(currentDir, prefix = "") {
94
- const entries = await readdir(currentDir, { withFileTypes: true });
95
- for (const entry of entries) {
96
- const path = join(prefix, entry.name);
97
- if (entry.isDirectory()) {
98
- if (!["node_modules", ".git", ".next", "dist", "build"].includes(entry.name)) {
99
- await walk(join(currentDir, entry.name), path);
100
- }
101
- } else {
102
- if (!path.match(/^(template\.json|\.template\.json|\.DS_Store|Thumbs\.db)$/) || path === ".gitignore") {
103
- files.push(path);
104
- }
52
+ constructor(args) {
53
+ const message = args && "message" in args && typeof args.message === "string" ? args.message : undefined;
54
+ const cause = args && "cause" in args ? args.cause : undefined;
55
+ super(message, cause !== undefined ? { cause } : undefined);
56
+ if (args)
57
+ Object.assign(this, args);
58
+ Object.setPrototypeOf(this, new.target.prototype);
59
+ this.name = tag;
60
+ if (cause instanceof Error && cause.stack) {
61
+ const indented = cause.stack.replace(/\n/g, `
62
+ `);
63
+ this.stack = `${this.stack}
64
+ Caused by: ${indented}`;
105
65
  }
106
66
  }
67
+ toJSON() {
68
+ return {
69
+ ...this,
70
+ _tag: this._tag,
71
+ name: this.name,
72
+ message: this.message,
73
+ cause: serializeCause(this.cause),
74
+ stack: this.stack
75
+ };
76
+ }
107
77
  }
108
- await walk(dir);
109
- if (manifest?.files?.exclude) {
110
- return files.filter((file) => {
111
- return !manifest.files.exclude.some((pattern) => {
112
- const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
113
- const regex = new RegExp(`^${regexPattern}$`);
114
- return regex.test(file);
115
- });
78
+ return Base;
79
+ }, { is: isAnyTaggedError });
80
+ var matchError = dual(2, (err$1, handlers) => {
81
+ const handler = handlers[err$1._tag];
82
+ return handler(err$1);
83
+ });
84
+ var matchErrorPartial = dual(3, (err$1, handlers, fallback) => {
85
+ const handler = handlers[err$1._tag];
86
+ if (typeof handler === "function")
87
+ return handler(err$1);
88
+ return fallback(err$1);
89
+ });
90
+ var UnhandledException = class extends TaggedError("UnhandledException")() {
91
+ constructor(args) {
92
+ const message = args.cause instanceof Error ? `Unhandled exception: ${args.cause.message}` : `Unhandled exception: ${String(args.cause)}`;
93
+ super({
94
+ message,
95
+ cause: args.cause
116
96
  });
117
97
  }
118
- return files;
119
- }
120
- async function runPostInstallHooks(dir, hooks) {
121
- for (const hook of hooks) {
122
- const proc = Bun.spawn(hook.split(" "), {
123
- cwd: dir,
124
- stdout: "inherit",
125
- stderr: "inherit"
98
+ };
99
+ var Panic = class extends TaggedError("Panic")() {
100
+ };
101
+ var ResultDeserializationError = class extends TaggedError("ResultDeserializationError")() {
102
+ constructor(args) {
103
+ super({
104
+ message: `Failed to deserialize value as Result: expected { status: "ok", value } or { status: "error", error }`,
105
+ value: args.value
126
106
  });
127
- const exitCode = await proc.exited;
128
- if (exitCode !== 0) {
129
- throw new Error(`Post-install hook failed: ${hook}`);
130
- }
131
107
  }
132
- }
133
- function resolveTemplateSource(template) {
134
- const specialTemplates = {
135
- basic: "github:bunli/templates/basic",
136
- advanced: "github:bunli/templates/advanced",
137
- monorepo: "github:bunli/templates/monorepo"
138
- };
139
- if (specialTemplates[template]) {
140
- return specialTemplates[template];
108
+ };
109
+ var panic = (message, cause) => {
110
+ throw new Panic({
111
+ message,
112
+ cause
113
+ });
114
+ };
115
+ var tryOrPanic = (fn, message) => {
116
+ try {
117
+ return fn();
118
+ } catch (cause) {
119
+ throw panic(message, cause);
141
120
  }
142
- if (template.startsWith("npm:")) {
143
- return template.replace("npm:", "npm:/");
121
+ };
122
+ var tryOrPanicAsync = async (fn, message) => {
123
+ try {
124
+ return await fn();
125
+ } catch (cause) {
126
+ throw panic(message, cause);
144
127
  }
145
- if (template.includes("/") && !template.includes(":")) {
146
- return `github:${template}`;
128
+ };
129
+ var Ok = class Ok2 {
130
+ status = "ok";
131
+ constructor(value) {
132
+ this.value = value;
147
133
  }
148
- return template;
149
- }
150
- function getBundledTemplatePath(name) {
151
- return join(import.meta.dir, "..", "templates", name);
152
- }
153
- async function isLocalTemplate(template) {
154
- if (template.startsWith("file:") || template.startsWith("./") || template.startsWith("../")) {
155
- return true;
156
- }
157
- const bundledPath = getBundledTemplatePath(template);
158
- return await Bun.file(join(bundledPath, "package.json")).exists();
159
- }
160
-
161
- // src/steps.ts
162
- import { existsSync } from "fs";
163
- import { join as join2 } from "path";
164
- var LOCKFILE_MAP = [
165
- ["bun.lock", "bun"],
166
- ["bun.lockb", "bun"],
167
- ["pnpm-lock.yaml", "pnpm"],
168
- ["yarn.lock", "yarn"],
169
- ["package-lock.json", "npm"]
170
- ];
171
- function detectPackageManager(cwd) {
172
- const dir = cwd ?? process.cwd();
173
- for (const [lockfile, manager] of LOCKFILE_MAP) {
174
- if (existsSync(join2(dir, lockfile))) {
175
- return manager;
176
- }
177
- }
178
- const userAgent = process.env.npm_config_user_agent;
179
- if (userAgent) {
180
- if (userAgent.startsWith("bun"))
181
- return "bun";
182
- if (userAgent.startsWith("pnpm"))
183
- return "pnpm";
184
- if (userAgent.startsWith("yarn"))
185
- return "yarn";
186
- if (userAgent.startsWith("npm"))
187
- return "npm";
188
- }
189
- return "bun";
190
- }
191
- function isInGitRepo(cwd) {
192
- try {
193
- const result = Bun.spawnSync(["git", "rev-parse", "--is-inside-work-tree"], { cwd: cwd ?? process.cwd(), stdout: "ignore", stderr: "ignore" });
194
- return result.exitCode === 0;
195
- } catch {
196
- return false;
197
- }
198
- }
199
- async function runSteps(dir, steps) {
200
- for (const step of steps) {
201
- switch (step.type) {
202
- case "install":
203
- await runInstall(dir);
204
- break;
205
- case "git-init":
206
- await runGitInit(dir, step.commit);
207
- break;
208
- case "open-editor":
209
- await runOpenEditor(dir);
210
- break;
211
- case "command":
212
- await runCommand(step.cmd, step.cwd ?? dir);
213
- break;
214
- }
215
- }
216
- }
217
- async function runInstall(cwd) {
218
- const pm = detectPackageManager(cwd);
219
- const proc = Bun.spawn([pm, "install"], {
220
- cwd,
221
- stdout: "pipe",
222
- stderr: "pipe"
223
- });
224
- const exitCode = await proc.exited;
225
- if (exitCode !== 0) {
226
- const stderr = await new Response(proc.stderr).text();
227
- throw new Error(`"${pm} install" exited with code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
228
- }
229
- }
230
- async function runGitInit(cwd, commit) {
231
- await spawnChecked(["git", "init"], cwd, "git init");
232
- if (commit) {
233
- await ensureGitIdentity(cwd);
234
- await spawnChecked(["git", "add", "."], cwd, "git add");
235
- await spawnChecked(["git", "commit", "-m", commit], cwd, "git commit");
236
- }
237
- }
238
- async function runOpenEditor(cwd) {
239
- const editor = process.env.EDITOR || "code";
240
- try {
241
- const proc = Bun.spawn([editor, cwd], {
242
- stdout: "ignore",
243
- stderr: "ignore"
244
- });
245
- const raceResult = await Promise.race([
246
- proc.exited.then((code) => ({ kind: "exited", code })),
247
- new Promise((resolve) => setTimeout(() => resolve({ kind: "timeout" }), 500))
248
- ]);
249
- if (raceResult.kind === "exited" && raceResult.code !== 0) {
250
- console.warn(`Warning: could not open editor "${editor}" (exit code ${raceResult.code})`);
251
- }
252
- } catch {
253
- console.warn(`Warning: could not open editor "${editor}"`);
254
- }
255
- }
256
- function getCommandSpawnArgs(cmd, platform = process.platform) {
257
- return platform === "win32" ? ["cmd", "/d", "/s", "/c", cmd] : ["sh", "-c", cmd];
258
- }
259
- async function runCommand(cmd, cwd) {
260
- const proc = Bun.spawn(getCommandSpawnArgs(cmd), {
261
- cwd,
262
- stdout: "inherit",
263
- stderr: "inherit"
264
- });
265
- const exitCode = await proc.exited;
266
- if (exitCode !== 0) {
267
- throw new Error(`Command "${cmd}" exited with code ${exitCode}`);
268
- }
269
- }
270
- async function ensureGitIdentity(cwd) {
271
- const hasName = Bun.spawnSync(["git", "config", "user.name"], { cwd }).exitCode === 0;
272
- const hasEmail = Bun.spawnSync(["git", "config", "user.email"], { cwd }).exitCode === 0;
273
- if (!hasName) {
274
- await spawnChecked(["git", "config", "user.name", "Bunli"], cwd, "git config user.name");
275
- }
276
- if (!hasEmail) {
277
- await spawnChecked(["git", "config", "user.email", "bunli@scaffolded.project"], cwd, "git config user.email");
278
- }
279
- }
280
- async function spawnChecked(cmd, cwd, label) {
281
- const proc = Bun.spawn(cmd, {
282
- cwd,
283
- stdout: "ignore",
284
- stderr: "pipe"
285
- });
286
- const exitCode = await proc.exited;
287
- if (exitCode !== 0) {
288
- const stderr = await new Response(proc.stderr).text();
289
- throw new Error(`"${label}" failed with exit code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
290
- }
291
- }
292
-
293
- // ../../node_modules/.bun/better-result@2.7.0/node_modules/better-result/dist/index.mjs
294
- function dual(arity, body) {
295
- if (arity === 2)
296
- return (...args) => {
297
- if (args.length >= 2)
298
- return body(args[0], args[1]);
299
- return (self) => body(self, args[0]);
300
- };
301
- if (arity === 3)
302
- return (...args) => {
303
- if (args.length >= 3)
304
- return body(args[0], args[1], args[2]);
305
- return (self) => body(self, args[0], args[1]);
306
- };
307
- if (arity === 4)
308
- return (...args) => {
309
- if (args.length >= 4)
310
- return body(args[0], args[1], args[2], args[3]);
311
- return (self) => body(self, args[0], args[1], args[2]);
312
- };
313
- return (...args) => {
314
- if (args.length >= arity)
315
- return body(...args);
316
- return (self) => body(self, ...args);
317
- };
318
- }
319
- var serializeCause = (cause) => {
320
- if (cause instanceof Error)
321
- return {
322
- name: cause.name,
323
- message: cause.message,
324
- stack: cause.stack
325
- };
326
- return cause;
327
- };
328
- var isAnyTaggedError = (value) => {
329
- return value instanceof Error && "_tag" in value && typeof value._tag === "string";
330
- };
331
- var TaggedError = Object.assign((tag) => () => {
332
-
333
- class Base extends Error {
334
- _tag = tag;
335
- static is(value) {
336
- return value instanceof Base;
337
- }
338
- constructor(args) {
339
- const message = args && "message" in args && typeof args.message === "string" ? args.message : undefined;
340
- const cause = args && "cause" in args ? args.cause : undefined;
341
- super(message, cause !== undefined ? { cause } : undefined);
342
- if (args)
343
- Object.assign(this, args);
344
- Object.setPrototypeOf(this, new.target.prototype);
345
- this.name = tag;
346
- if (cause instanceof Error && cause.stack) {
347
- const indented = cause.stack.replace(/\n/g, `
348
- `);
349
- this.stack = `${this.stack}
350
- Caused by: ${indented}`;
351
- }
352
- }
353
- toJSON() {
354
- return {
355
- ...this,
356
- _tag: this._tag,
357
- name: this.name,
358
- message: this.message,
359
- cause: serializeCause(this.cause),
360
- stack: this.stack
361
- };
362
- }
363
- }
364
- return Base;
365
- }, { is: isAnyTaggedError });
366
- var matchError = dual(2, (err$1, handlers) => {
367
- const handler = handlers[err$1._tag];
368
- return handler(err$1);
369
- });
370
- var matchErrorPartial = dual(3, (err$1, handlers, fallback) => {
371
- const handler = handlers[err$1._tag];
372
- if (typeof handler === "function")
373
- return handler(err$1);
374
- return fallback(err$1);
375
- });
376
- var UnhandledException = class extends TaggedError("UnhandledException")() {
377
- constructor(args) {
378
- const message = args.cause instanceof Error ? `Unhandled exception: ${args.cause.message}` : `Unhandled exception: ${String(args.cause)}`;
379
- super({
380
- message,
381
- cause: args.cause
382
- });
383
- }
384
- };
385
- var Panic = class extends TaggedError("Panic")() {
386
- };
387
- var ResultDeserializationError = class extends TaggedError("ResultDeserializationError")() {
388
- constructor(args) {
389
- super({
390
- message: `Failed to deserialize value as Result: expected { status: "ok", value } or { status: "error", error }`,
391
- value: args.value
392
- });
393
- }
394
- };
395
- var panic = (message, cause) => {
396
- throw new Panic({
397
- message,
398
- cause
399
- });
400
- };
401
- var tryOrPanic = (fn, message) => {
402
- try {
403
- return fn();
404
- } catch (cause) {
405
- throw panic(message, cause);
406
- }
407
- };
408
- var tryOrPanicAsync = async (fn, message) => {
409
- try {
410
- return await fn();
411
- } catch (cause) {
412
- throw panic(message, cause);
413
- }
414
- };
415
- var Ok = class Ok2 {
416
- status = "ok";
417
- constructor(value) {
418
- this.value = value;
419
- }
420
- isOk() {
134
+ isOk() {
421
135
  return true;
422
136
  }
423
137
  isErr() {
@@ -554,164 +268,459 @@ var tryPromise = async (options, config) => {
554
268
  throw panic("Result.tryPromise catch handler threw", catchHandlerError);
555
269
  }
556
270
  }
557
- };
558
- const retry = config?.retry;
559
- if (!retry)
560
- return execute();
561
- const getDelay = (retryAttempt) => {
562
- switch (retry.backoff) {
563
- case "constant":
564
- return retry.delayMs;
565
- case "linear":
566
- return retry.delayMs * (retryAttempt + 1);
567
- case "exponential":
568
- return retry.delayMs * 2 ** retryAttempt;
271
+ };
272
+ const retry = config?.retry;
273
+ if (!retry)
274
+ return execute();
275
+ const getDelay = (retryAttempt) => {
276
+ switch (retry.backoff) {
277
+ case "constant":
278
+ return retry.delayMs;
279
+ case "linear":
280
+ return retry.delayMs * (retryAttempt + 1);
281
+ case "exponential":
282
+ return retry.delayMs * 2 ** retryAttempt;
283
+ }
284
+ };
285
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
286
+ let result = await execute();
287
+ const shouldRetryFn = retry.shouldRetry ?? (() => true);
288
+ for (let attempt = 0;attempt < retry.times; attempt++) {
289
+ if (result.status !== "error")
290
+ break;
291
+ const error = result.error;
292
+ if (!tryOrPanic(() => shouldRetryFn(error), "shouldRetry predicate threw"))
293
+ break;
294
+ await sleep(getDelay(attempt));
295
+ result = await execute();
296
+ }
297
+ return result;
298
+ };
299
+ var map = dual(2, (result, fn) => {
300
+ return result.map(fn);
301
+ });
302
+ var mapError = dual(2, (result, fn) => {
303
+ return result.mapError(fn);
304
+ });
305
+ var andThen = dual(2, (result, fn) => {
306
+ return result.andThen(fn);
307
+ });
308
+ var andThenAsync = dual(2, (result, fn) => {
309
+ return result.andThenAsync(fn);
310
+ });
311
+ var match = dual(2, (result, handlers) => {
312
+ return result.match(handlers);
313
+ });
314
+ var tap = dual(2, (result, fn) => {
315
+ return result.tap(fn);
316
+ });
317
+ var tapAsync = dual(2, (result, fn) => {
318
+ return result.tapAsync(fn);
319
+ });
320
+ var unwrap = (result, message) => {
321
+ return result.unwrap(message);
322
+ };
323
+ function assertIsResult(value) {
324
+ if (value !== null && typeof value === "object" && "status" in value && (value.status === "ok" || value.status === "error"))
325
+ return;
326
+ return panic("Result.gen body must return Result.ok() or Result.err(), got: " + (value === null ? "null" : typeof value === "object" ? JSON.stringify(value) : String(value)));
327
+ }
328
+ var unwrapOr = dual(2, (result, fallback) => {
329
+ return result.unwrapOr(fallback);
330
+ });
331
+ var gen = (body, thisArg) => {
332
+ const iterator = body.call(thisArg);
333
+ if (Symbol.asyncIterator in iterator)
334
+ return (async () => {
335
+ const asyncIter = iterator;
336
+ let state$1;
337
+ try {
338
+ state$1 = await asyncIter.next();
339
+ } catch (cause) {
340
+ throw panic("generator body threw", cause);
341
+ }
342
+ assertIsResult(state$1.value);
343
+ if (!state$1.done)
344
+ try {
345
+ await asyncIter.return?.(undefined);
346
+ } catch (cause) {
347
+ throw panic("generator cleanup threw", cause);
348
+ }
349
+ return state$1.value;
350
+ })();
351
+ const syncIter = iterator;
352
+ let state;
353
+ try {
354
+ state = syncIter.next();
355
+ } catch (cause) {
356
+ throw panic("generator body threw", cause);
357
+ }
358
+ assertIsResult(state.value);
359
+ if (!state.done)
360
+ try {
361
+ syncIter.return?.(undefined);
362
+ } catch (cause) {
363
+ throw panic("generator cleanup threw", cause);
364
+ }
365
+ return state.value;
366
+ };
367
+ async function* resultAwait(promise) {
368
+ return yield* await promise;
369
+ }
370
+ function isSerializedResult(obj) {
371
+ return obj !== null && typeof obj === "object" && "status" in obj && (obj.status === "ok" && ("value" in obj) || obj.status === "error" && ("error" in obj));
372
+ }
373
+ var serialize = (result) => {
374
+ return result.status === "ok" ? {
375
+ status: "ok",
376
+ value: result.value
377
+ } : {
378
+ status: "error",
379
+ error: result.error
380
+ };
381
+ };
382
+ var deserialize = (value) => {
383
+ if (isSerializedResult(value))
384
+ return value.status === "ok" ? new Ok(value.value) : new Err(value.error);
385
+ return err(new ResultDeserializationError({ value }));
386
+ };
387
+ var hydrate = (value) => {
388
+ return deserialize(value);
389
+ };
390
+ var partition = (results) => {
391
+ const oks = [];
392
+ const errs = [];
393
+ for (const r of results)
394
+ if (r.status === "ok")
395
+ oks.push(r.value);
396
+ else
397
+ errs.push(r.error);
398
+ return [oks, errs];
399
+ };
400
+ var flatten = (result) => {
401
+ if (result.status === "ok")
402
+ return result.value;
403
+ return result;
404
+ };
405
+ var Result = {
406
+ ok,
407
+ isOk,
408
+ err,
409
+ isError,
410
+ try: tryFn,
411
+ tryPromise,
412
+ map,
413
+ mapError,
414
+ andThen,
415
+ andThenAsync,
416
+ match,
417
+ tap,
418
+ tapAsync,
419
+ unwrap,
420
+ unwrapOr,
421
+ gen,
422
+ await: resultAwait,
423
+ serialize,
424
+ deserialize,
425
+ hydrate,
426
+ partition,
427
+ flatten
428
+ };
429
+
430
+ // src/cli.ts
431
+ import { z } from "zod";
432
+
433
+ // src/create.ts
434
+ import path from "path";
435
+
436
+ // src/steps.ts
437
+ import { existsSync } from "fs";
438
+ import { join } from "path";
439
+ var LOCKFILE_MAP = [
440
+ ["bun.lock", "bun"],
441
+ ["bun.lockb", "bun"],
442
+ ["pnpm-lock.yaml", "pnpm"],
443
+ ["yarn.lock", "yarn"],
444
+ ["package-lock.json", "npm"]
445
+ ];
446
+ function detectPackageManager(cwd) {
447
+ const dir = cwd ?? process.cwd();
448
+ for (const [lockfile, manager] of LOCKFILE_MAP) {
449
+ if (existsSync(join(dir, lockfile))) {
450
+ return manager;
451
+ }
452
+ }
453
+ const userAgent = process.env.npm_config_user_agent;
454
+ if (userAgent) {
455
+ if (userAgent.startsWith("bun"))
456
+ return "bun";
457
+ if (userAgent.startsWith("pnpm"))
458
+ return "pnpm";
459
+ if (userAgent.startsWith("yarn"))
460
+ return "yarn";
461
+ if (userAgent.startsWith("npm"))
462
+ return "npm";
463
+ }
464
+ return "bun";
465
+ }
466
+ function isInGitRepo(cwd) {
467
+ try {
468
+ const result = Bun.spawnSync(["git", "rev-parse", "--is-inside-work-tree"], {
469
+ cwd: cwd ?? process.cwd(),
470
+ stdout: "ignore",
471
+ stderr: "ignore"
472
+ });
473
+ return result.exitCode === 0;
474
+ } catch {
475
+ return false;
476
+ }
477
+ }
478
+ async function runSteps(dir, steps) {
479
+ for (const step of steps) {
480
+ switch (step.type) {
481
+ case "install":
482
+ await runInstall(dir);
483
+ break;
484
+ case "git-init":
485
+ await runGitInit(dir, step.commit);
486
+ break;
487
+ case "open-editor":
488
+ await runOpenEditor(dir);
489
+ break;
490
+ case "command":
491
+ await runCommand(step.cmd, step.cwd ?? dir);
492
+ break;
493
+ }
494
+ }
495
+ }
496
+ async function runInstall(cwd) {
497
+ const pm = detectPackageManager(cwd);
498
+ const proc = Bun.spawn([pm, "install"], {
499
+ cwd,
500
+ stdout: "pipe",
501
+ stderr: "pipe"
502
+ });
503
+ const exitCode = await proc.exited;
504
+ if (exitCode !== 0) {
505
+ const stderr = await new Response(proc.stderr).text();
506
+ throw new Error(`"${pm} install" exited with code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
507
+ }
508
+ }
509
+ async function runGitInit(cwd, commit) {
510
+ await spawnChecked(["git", "init"], cwd, "git init");
511
+ if (commit) {
512
+ await ensureGitIdentity(cwd);
513
+ await spawnChecked(["git", "add", "."], cwd, "git add");
514
+ await spawnChecked(["git", "commit", "-m", commit], cwd, "git commit");
515
+ }
516
+ }
517
+ async function runOpenEditor(cwd) {
518
+ const editor = process.env.EDITOR || "code";
519
+ try {
520
+ const proc = Bun.spawn([editor, cwd], {
521
+ stdout: "ignore",
522
+ stderr: "ignore"
523
+ });
524
+ const raceResult = await Promise.race([
525
+ proc.exited.then((code) => ({ kind: "exited", code })),
526
+ new Promise((resolve) => setTimeout(() => resolve({ kind: "timeout" }), 500))
527
+ ]);
528
+ if (raceResult.kind === "exited" && raceResult.code !== 0) {
529
+ console.warn(`Warning: could not open editor "${editor}" (exit code ${raceResult.code})`);
530
+ }
531
+ } catch {
532
+ console.warn(`Warning: could not open editor "${editor}"`);
533
+ }
534
+ }
535
+ function getCommandSpawnArgs(cmd, platform = process.platform) {
536
+ return platform === "win32" ? ["cmd", "/d", "/s", "/c", cmd] : ["sh", "-c", cmd];
537
+ }
538
+ async function runCommand(cmd, cwd) {
539
+ const proc = Bun.spawn(getCommandSpawnArgs(cmd), {
540
+ cwd,
541
+ stdout: "inherit",
542
+ stderr: "inherit"
543
+ });
544
+ const exitCode = await proc.exited;
545
+ if (exitCode !== 0) {
546
+ throw new Error(`Command "${cmd}" exited with code ${exitCode}`);
547
+ }
548
+ }
549
+ async function ensureGitIdentity(cwd) {
550
+ const hasName = Bun.spawnSync(["git", "config", "user.name"], { cwd }).exitCode === 0;
551
+ const hasEmail = Bun.spawnSync(["git", "config", "user.email"], { cwd }).exitCode === 0;
552
+ if (!hasName) {
553
+ await spawnChecked(["git", "config", "user.name", "Bunli"], cwd, "git config user.name");
554
+ }
555
+ if (!hasEmail) {
556
+ await spawnChecked(["git", "config", "user.email", "bunli@scaffolded.project"], cwd, "git config user.email");
557
+ }
558
+ }
559
+ async function spawnChecked(cmd, cwd, label) {
560
+ const proc = Bun.spawn(cmd, {
561
+ cwd,
562
+ stdout: "ignore",
563
+ stderr: "pipe"
564
+ });
565
+ const exitCode = await proc.exited;
566
+ if (exitCode !== 0) {
567
+ const stderr = await new Response(proc.stderr).text();
568
+ throw new Error(`"${label}" failed with exit code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
569
+ }
570
+ }
571
+
572
+ // src/template-engine.ts
573
+ import { readdir } from "fs/promises";
574
+ import { join as join2 } from "path";
575
+ import { downloadTemplate } from "giget";
576
+ async function processTemplate(options) {
577
+ const { source, dir, offline, variables = {} } = options;
578
+ let templateDir;
579
+ if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
580
+ const sourceDir = source.startsWith("/") ? source : join2(process.cwd(), source);
581
+ const copyExitCode = await Bun.spawn(["cp", "-r", sourceDir + "/.", dir], {
582
+ stdout: "inherit",
583
+ stderr: "inherit"
584
+ }).exited;
585
+ if (copyExitCode !== 0) {
586
+ throw new Error(`Failed to copy local template from ${sourceDir}`);
587
+ }
588
+ templateDir = dir;
589
+ } else {
590
+ const result = await downloadTemplate(source, {
591
+ dir,
592
+ offline,
593
+ preferOffline: true,
594
+ force: true
595
+ });
596
+ templateDir = result.dir;
597
+ }
598
+ const manifest = await loadTemplateManifest(templateDir);
599
+ if (manifest?.files || Object.keys(variables).length > 0) {
600
+ await processTemplateFiles(templateDir, variables, manifest);
601
+ }
602
+ if (manifest?.hooks?.postInstall) {
603
+ await runPostInstallHooks(templateDir, manifest.hooks.postInstall);
604
+ }
605
+ return { dir: templateDir, manifest };
606
+ }
607
+ async function loadTemplateManifest(dir) {
608
+ const possiblePaths = [
609
+ join2(dir, "template.json"),
610
+ join2(dir, ".template.json"),
611
+ join2(dir, "template.yaml"),
612
+ join2(dir, ".template.yaml")
613
+ ];
614
+ for (const path of possiblePaths) {
615
+ const file = Bun.file(path);
616
+ if (await file.exists()) {
617
+ const content = await file.text();
618
+ if (path.endsWith(".json")) {
619
+ const manifest = JSON.parse(content);
620
+ const removeExitCode = await Bun.spawn(["rm", "-f", path], {
621
+ stdout: "ignore",
622
+ stderr: "ignore"
623
+ }).exited;
624
+ if (removeExitCode !== 0) {
625
+ console.warn(`Warning: failed to remove template manifest ${path}`);
626
+ }
627
+ return manifest;
628
+ }
629
+ }
630
+ }
631
+ return null;
632
+ }
633
+ async function processTemplateFiles(dir, variables, manifest) {
634
+ const files = await getFilesToProcess(dir, manifest);
635
+ for (const file of files) {
636
+ const filePath = join2(dir, file);
637
+ const content = await Bun.file(filePath).text();
638
+ let processedContent = content;
639
+ for (const [key, value] of Object.entries(variables)) {
640
+ processedContent = processedContent.replaceAll(`{{${key}}}`, value).replaceAll(`<%= ${key} %>`, value).replaceAll(`$${key}`, value).replaceAll(`__${key}__`, value);
641
+ }
642
+ let newFilePath = filePath;
643
+ for (const [key, value] of Object.entries(variables)) {
644
+ newFilePath = newFilePath.replaceAll(`__${key}__`, value);
645
+ }
646
+ await Bun.write(newFilePath, processedContent);
647
+ if (newFilePath !== filePath) {
648
+ await Bun.spawn(["rm", filePath]).exited;
569
649
  }
570
- };
571
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
572
- let result = await execute();
573
- const shouldRetryFn = retry.shouldRetry ?? (() => true);
574
- for (let attempt = 0;attempt < retry.times; attempt++) {
575
- if (result.status !== "error")
576
- break;
577
- const error = result.error;
578
- if (!tryOrPanic(() => shouldRetryFn(error), "shouldRetry predicate threw"))
579
- break;
580
- await sleep(getDelay(attempt));
581
- result = await execute();
582
650
  }
583
- return result;
584
- };
585
- var map = dual(2, (result, fn) => {
586
- return result.map(fn);
587
- });
588
- var mapError = dual(2, (result, fn) => {
589
- return result.mapError(fn);
590
- });
591
- var andThen = dual(2, (result, fn) => {
592
- return result.andThen(fn);
593
- });
594
- var andThenAsync = dual(2, (result, fn) => {
595
- return result.andThenAsync(fn);
596
- });
597
- var match = dual(2, (result, handlers) => {
598
- return result.match(handlers);
599
- });
600
- var tap = dual(2, (result, fn) => {
601
- return result.tap(fn);
602
- });
603
- var tapAsync = dual(2, (result, fn) => {
604
- return result.tapAsync(fn);
605
- });
606
- var unwrap = (result, message) => {
607
- return result.unwrap(message);
608
- };
609
- function assertIsResult(value) {
610
- if (value !== null && typeof value === "object" && "status" in value && (value.status === "ok" || value.status === "error"))
611
- return;
612
- return panic("Result.gen body must return Result.ok() or Result.err(), got: " + (value === null ? "null" : typeof value === "object" ? JSON.stringify(value) : String(value)));
613
651
  }
614
- var unwrapOr = dual(2, (result, fallback) => {
615
- return result.unwrapOr(fallback);
616
- });
617
- var gen = (body, thisArg) => {
618
- const iterator = body.call(thisArg);
619
- if (Symbol.asyncIterator in iterator)
620
- return (async () => {
621
- const asyncIter = iterator;
622
- let state$1;
623
- try {
624
- state$1 = await asyncIter.next();
625
- } catch (cause) {
626
- throw panic("generator body threw", cause);
627
- }
628
- assertIsResult(state$1.value);
629
- if (!state$1.done)
630
- try {
631
- await asyncIter.return?.(undefined);
632
- } catch (cause) {
633
- throw panic("generator cleanup threw", cause);
634
- }
635
- return state$1.value;
636
- })();
637
- const syncIter = iterator;
638
- let state;
639
- try {
640
- state = syncIter.next();
641
- } catch (cause) {
642
- throw panic("generator body threw", cause);
652
+ async function getFilesToProcess(dir, manifest) {
653
+ if (manifest?.files?.include) {
654
+ return manifest.files.include;
643
655
  }
644
- assertIsResult(state.value);
645
- if (!state.done)
646
- try {
647
- syncIter.return?.(undefined);
648
- } catch (cause) {
649
- throw panic("generator cleanup threw", cause);
656
+ const files = [];
657
+ async function walk(currentDir, prefix = "") {
658
+ const entries = await readdir(currentDir, { withFileTypes: true });
659
+ for (const entry of entries) {
660
+ const path = join2(prefix, entry.name);
661
+ if (entry.isDirectory()) {
662
+ if (!["node_modules", ".git", ".next", "dist", "build"].includes(entry.name)) {
663
+ await walk(join2(currentDir, entry.name), path);
664
+ }
665
+ } else {
666
+ if (!path.match(/^(template\.json|\.template\.json|\.DS_Store|Thumbs\.db)$/) || path === ".gitignore") {
667
+ files.push(path);
668
+ }
669
+ }
650
670
  }
651
- return state.value;
652
- };
653
- async function* resultAwait(promise) {
654
- return yield* await promise;
671
+ }
672
+ await walk(dir);
673
+ if (manifest?.files?.exclude) {
674
+ return files.filter((file) => {
675
+ return !manifest.files.exclude.some((pattern) => {
676
+ const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
677
+ const regex = new RegExp(`^${regexPattern}$`);
678
+ return regex.test(file);
679
+ });
680
+ });
681
+ }
682
+ return files;
655
683
  }
656
- function isSerializedResult(obj) {
657
- return obj !== null && typeof obj === "object" && "status" in obj && (obj.status === "ok" && ("value" in obj) || obj.status === "error" && ("error" in obj));
684
+ async function runPostInstallHooks(dir, hooks) {
685
+ for (const hook of hooks) {
686
+ const proc = Bun.spawn(hook.split(" "), {
687
+ cwd: dir,
688
+ stdout: "inherit",
689
+ stderr: "inherit"
690
+ });
691
+ const exitCode = await proc.exited;
692
+ if (exitCode !== 0) {
693
+ throw new Error(`Post-install hook failed: ${hook}`);
694
+ }
695
+ }
658
696
  }
659
- var serialize = (result) => {
660
- return result.status === "ok" ? {
661
- status: "ok",
662
- value: result.value
663
- } : {
664
- status: "error",
665
- error: result.error
697
+ function resolveTemplateSource(template) {
698
+ const specialTemplates = {
699
+ basic: "github:bunli/templates/basic",
700
+ advanced: "github:bunli/templates/advanced",
701
+ monorepo: "github:bunli/templates/monorepo"
666
702
  };
667
- };
668
- var deserialize = (value) => {
669
- if (isSerializedResult(value))
670
- return value.status === "ok" ? new Ok(value.value) : new Err(value.error);
671
- return err(new ResultDeserializationError({ value }));
672
- };
673
- var hydrate = (value) => {
674
- return deserialize(value);
675
- };
676
- var partition = (results) => {
677
- const oks = [];
678
- const errs = [];
679
- for (const r of results)
680
- if (r.status === "ok")
681
- oks.push(r.value);
682
- else
683
- errs.push(r.error);
684
- return [oks, errs];
685
- };
686
- var flatten = (result) => {
687
- if (result.status === "ok")
688
- return result.value;
689
- return result;
690
- };
691
- var Result = {
692
- ok,
693
- isOk,
694
- err,
695
- isError,
696
- try: tryFn,
697
- tryPromise,
698
- map,
699
- mapError,
700
- andThen,
701
- andThenAsync,
702
- match,
703
- tap,
704
- tapAsync,
705
- unwrap,
706
- unwrapOr,
707
- gen,
708
- await: resultAwait,
709
- serialize,
710
- deserialize,
711
- hydrate,
712
- partition,
713
- flatten
714
- };
703
+ if (specialTemplates[template]) {
704
+ return specialTemplates[template];
705
+ }
706
+ if (template.startsWith("npm:")) {
707
+ return template.replace("npm:", "npm:/");
708
+ }
709
+ if (template.includes("/") && !template.includes(":")) {
710
+ return `github:${template}`;
711
+ }
712
+ return template;
713
+ }
714
+ function getBundledTemplatePath(name) {
715
+ return join2(import.meta.dir, "..", "templates", name);
716
+ }
717
+ async function isLocalTemplate(template) {
718
+ if (template.startsWith("file:") || template.startsWith("./") || template.startsWith("../")) {
719
+ return true;
720
+ }
721
+ const bundledPath = getBundledTemplatePath(template);
722
+ return await Bun.file(join2(bundledPath, "package.json")).exists();
723
+ }
715
724
 
716
725
  // src/create-project.ts
717
726
  var toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
@@ -770,7 +779,9 @@ async function createProject(options) {
770
779
  const { name, dir, template, git, install, prompt, spinner, colors, shell, offline } = options;
771
780
  const directoryCheck = await shell`test -d ${dir}`.nothrow();
772
781
  if (directoryCheck.exitCode === 0) {
773
- const overwrite = await prompt.confirm(`Directory ${dir} already exists. Overwrite?`, { default: false });
782
+ const overwrite = await prompt.confirm(`Directory ${dir} already exists. Overwrite?`, {
783
+ default: false
784
+ });
774
785
  if (!overwrite) {
775
786
  return Result.err(new UserCancelledError("Cancelled"));
776
787
  }
@@ -844,10 +855,11 @@ async function createProject(options) {
844
855
  }
845
856
 
846
857
  // src/create.ts
847
- import path from "path";
848
858
  class InvalidProjectNameError extends TaggedError("InvalidProjectNameError")() {
849
859
  constructor(name) {
850
- super({ message: `Project name "${name}" must only contain lowercase letters, numbers, and hyphens` });
860
+ super({
861
+ message: `Project name "${name}" must only contain lowercase letters, numbers, and hyphens`
862
+ });
851
863
  }
852
864
  }
853
865
 
@@ -941,11 +953,28 @@ async function run() {
941
953
  description: "Create a new Bunli CLI project",
942
954
  options: {
943
955
  name: option(z.string().optional(), { description: "Project name" }),
944
- template: option(z.string().default("basic"), { short: "t", description: "Project template (basic, advanced, monorepo, or github:user/repo)" }),
945
- dir: option(z.string().optional(), { short: "d", description: "Directory to create project in" }),
946
- git: option(z.boolean().default(true), { short: "g", description: "Initialize git repository", argumentKind: "flag" }),
947
- install: option(z.boolean().default(true), { short: "i", description: "Install dependencies", argumentKind: "flag" }),
948
- offline: option(z.boolean().default(false), { description: "Use cached templates when available", argumentKind: "flag" })
956
+ template: option(z.string().default("basic"), {
957
+ short: "t",
958
+ description: "Project template (basic, advanced, monorepo, or github:user/repo)"
959
+ }),
960
+ dir: option(z.string().optional(), {
961
+ short: "d",
962
+ description: "Directory to create project in"
963
+ }),
964
+ git: option(z.boolean().default(true), {
965
+ short: "g",
966
+ description: "Initialize git repository",
967
+ argumentKind: "flag"
968
+ }),
969
+ install: option(z.boolean().default(true), {
970
+ short: "i",
971
+ description: "Install dependencies",
972
+ argumentKind: "flag"
973
+ }),
974
+ offline: option(z.boolean().default(false), {
975
+ description: "Use cached templates when available",
976
+ argumentKind: "flag"
977
+ })
949
978
  },
950
979
  handler: async (context) => {
951
980
  const result = await create(context);