create-bunli 0.5.4 → 0.6.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.
@@ -1,5 +1,6 @@
1
1
  import type { BunliUtils } from '@bunli/utils';
2
2
  import type { CreateOptions } from './types.js';
3
+ import { Result } from 'better-result';
3
4
  interface CreateProjectOptions extends CreateOptions {
4
5
  name: string;
5
6
  dir: string;
@@ -9,5 +10,27 @@ interface CreateProjectOptions extends CreateOptions {
9
10
  colors: BunliUtils['colors'];
10
11
  shell: typeof Bun.$;
11
12
  }
12
- export declare function createProject(options: CreateProjectOptions): Promise<void>;
13
+ declare const UserCancelledError_base: import("better-result").TaggedErrorClass<"UserCancelledError", {
14
+ message: string;
15
+ }>;
16
+ export declare class UserCancelledError extends UserCancelledError_base {
17
+ constructor(message: string);
18
+ }
19
+ declare const ShellCommandError_base: import("better-result").TaggedErrorClass<"ShellCommandError", {
20
+ message: string;
21
+ command: string;
22
+ output: string;
23
+ }>;
24
+ declare class ShellCommandError extends ShellCommandError_base {
25
+ constructor(command: string, output: string);
26
+ }
27
+ declare const TemplateProcessingError_base: import("better-result").TaggedErrorClass<"TemplateProcessingError", {
28
+ message: string;
29
+ cause: unknown;
30
+ }>;
31
+ declare class TemplateProcessingError extends TemplateProcessingError_base {
32
+ constructor(cause: unknown);
33
+ }
34
+ export type CreateProjectError = UserCancelledError | ShellCommandError | TemplateProcessingError;
35
+ export declare function createProject(options: CreateProjectOptions): Promise<Result<void, CreateProjectError>>;
13
36
  export {};
package/dist/create.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { HandlerArgs } from '@bunli/core';
2
+ import { type CreateProjectError } from './create-project.js';
3
+ import { Result } from 'better-result';
2
4
  interface CreateOptions {
3
5
  name?: string;
4
6
  template: string;
@@ -7,5 +9,18 @@ interface CreateOptions {
7
9
  install: boolean;
8
10
  offline?: boolean;
9
11
  }
10
- export declare function create(context: HandlerArgs<CreateOptions>): Promise<void>;
12
+ declare const InvalidProjectNameError_base: import("better-result").TaggedErrorClass<"InvalidProjectNameError", {
13
+ message: string;
14
+ }>;
15
+ declare class InvalidProjectNameError extends InvalidProjectNameError_base {
16
+ constructor(name: string);
17
+ }
18
+ declare const UserCancelledError_base: import("better-result").TaggedErrorClass<"UserCancelledError", {
19
+ message: string;
20
+ }>;
21
+ export declare class UserCancelledError extends UserCancelledError_base {
22
+ constructor(message: string);
23
+ }
24
+ export type CreateCommandError = InvalidProjectNameError | UserCancelledError | CreateProjectError;
25
+ export declare function create(context: HandlerArgs<CreateOptions>): Promise<Result<void, CreateCommandError>>;
11
26
  export {};
package/dist/index.js CHANGED
@@ -8,10 +8,13 @@ async function processTemplate(options) {
8
8
  let templateDir;
9
9
  if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
10
10
  const sourceDir = source.startsWith("/") ? source : join(process.cwd(), source);
11
- await Bun.spawn(["cp", "-r", sourceDir + "/.", dir], {
11
+ const copyExitCode = await Bun.spawn(["cp", "-r", sourceDir + "/.", dir], {
12
12
  stdout: "inherit",
13
13
  stderr: "inherit"
14
14
  }).exited;
15
+ if (copyExitCode !== 0) {
16
+ throw new Error(`Failed to copy local template from ${sourceDir}`);
17
+ }
15
18
  templateDir = dir;
16
19
  } else {
17
20
  const result = await downloadTemplate(source, {
@@ -44,12 +47,13 @@ async function loadTemplateManifest(dir) {
44
47
  const content = await file.text();
45
48
  if (path.endsWith(".json")) {
46
49
  const manifest = JSON.parse(content);
47
- try {
48
- await Bun.spawn(["rm", "-f", path], {
49
- stdout: "ignore",
50
- stderr: "ignore"
51
- }).exited;
52
- } catch {}
50
+ const removeExitCode = await Bun.spawn(["rm", "-f", path], {
51
+ stdout: "ignore",
52
+ stderr: "ignore"
53
+ }).exited;
54
+ if (removeExitCode !== 0) {
55
+ console.warn(`Warning: failed to remove template manifest ${path}`);
56
+ }
53
57
  return manifest;
54
58
  }
55
59
  }
@@ -114,7 +118,10 @@ async function runPostInstallHooks(dir, hooks) {
114
118
  stdout: "inherit",
115
119
  stderr: "inherit"
116
120
  });
117
- await proc.exited;
121
+ const exitCode = await proc.exited;
122
+ if (exitCode !== 0) {
123
+ throw new Error(`Post-install hook failed: ${hook}`);
124
+ }
118
125
  }
119
126
  }
120
127
  function resolveTemplateSource(template) {
@@ -145,79 +152,541 @@ async function isLocalTemplate(template) {
145
152
  return await Bun.file(join(bundledPath, "package.json")).exists();
146
153
  }
147
154
 
155
+ // ../../node_modules/better-result/dist/index.mjs
156
+ function dual(arity, body) {
157
+ if (arity === 2)
158
+ return (...args) => {
159
+ if (args.length >= 2)
160
+ return body(args[0], args[1]);
161
+ return (self) => body(self, args[0]);
162
+ };
163
+ if (arity === 3)
164
+ return (...args) => {
165
+ if (args.length >= 3)
166
+ return body(args[0], args[1], args[2]);
167
+ return (self) => body(self, args[0], args[1]);
168
+ };
169
+ if (arity === 4)
170
+ return (...args) => {
171
+ if (args.length >= 4)
172
+ return body(args[0], args[1], args[2], args[3]);
173
+ return (self) => body(self, args[0], args[1], args[2]);
174
+ };
175
+ return (...args) => {
176
+ if (args.length >= arity)
177
+ return body(...args);
178
+ return (self) => body(self, ...args);
179
+ };
180
+ }
181
+ var serializeCause = (cause) => {
182
+ if (cause instanceof Error)
183
+ return {
184
+ name: cause.name,
185
+ message: cause.message,
186
+ stack: cause.stack
187
+ };
188
+ return cause;
189
+ };
190
+ var isAnyTaggedError = (value) => {
191
+ return value instanceof Error && "_tag" in value && typeof value._tag === "string";
192
+ };
193
+ var TaggedError = Object.assign((tag) => () => {
194
+
195
+ class Base extends Error {
196
+ _tag = tag;
197
+ static is(value) {
198
+ return value instanceof Base;
199
+ }
200
+ constructor(args) {
201
+ const message = args && "message" in args && typeof args.message === "string" ? args.message : undefined;
202
+ const cause = args && "cause" in args ? args.cause : undefined;
203
+ super(message, cause !== undefined ? { cause } : undefined);
204
+ if (args)
205
+ Object.assign(this, args);
206
+ Object.setPrototypeOf(this, new.target.prototype);
207
+ this.name = tag;
208
+ if (cause instanceof Error && cause.stack) {
209
+ const indented = cause.stack.replace(/\n/g, `
210
+ `);
211
+ this.stack = `${this.stack}
212
+ Caused by: ${indented}`;
213
+ }
214
+ }
215
+ toJSON() {
216
+ return {
217
+ ...this,
218
+ _tag: this._tag,
219
+ name: this.name,
220
+ message: this.message,
221
+ cause: serializeCause(this.cause),
222
+ stack: this.stack
223
+ };
224
+ }
225
+ }
226
+ return Base;
227
+ }, { is: isAnyTaggedError });
228
+ var matchError = dual(2, (err$1, handlers) => {
229
+ const handler = handlers[err$1._tag];
230
+ return handler(err$1);
231
+ });
232
+ var matchErrorPartial = dual(3, (err$1, handlers, fallback) => {
233
+ const handler = handlers[err$1._tag];
234
+ if (typeof handler === "function")
235
+ return handler(err$1);
236
+ return fallback(err$1);
237
+ });
238
+ var UnhandledException = class extends TaggedError("UnhandledException")() {
239
+ constructor(args) {
240
+ const message = args.cause instanceof Error ? `Unhandled exception: ${args.cause.message}` : `Unhandled exception: ${String(args.cause)}`;
241
+ super({
242
+ message,
243
+ cause: args.cause
244
+ });
245
+ }
246
+ };
247
+ var Panic = class extends TaggedError("Panic")() {
248
+ };
249
+ var ResultDeserializationError = class extends TaggedError("ResultDeserializationError")() {
250
+ constructor(args) {
251
+ super({
252
+ message: `Failed to deserialize value as Result: expected { status: "ok", value } or { status: "error", error }`,
253
+ value: args.value
254
+ });
255
+ }
256
+ };
257
+ var panic = (message, cause) => {
258
+ throw new Panic({
259
+ message,
260
+ cause
261
+ });
262
+ };
263
+ var tryOrPanic = (fn, message) => {
264
+ try {
265
+ return fn();
266
+ } catch (cause) {
267
+ throw panic(message, cause);
268
+ }
269
+ };
270
+ var tryOrPanicAsync = async (fn, message) => {
271
+ try {
272
+ return await fn();
273
+ } catch (cause) {
274
+ throw panic(message, cause);
275
+ }
276
+ };
277
+ var Ok = class Ok2 {
278
+ status = "ok";
279
+ constructor(value) {
280
+ this.value = value;
281
+ }
282
+ isOk() {
283
+ return true;
284
+ }
285
+ isErr() {
286
+ return false;
287
+ }
288
+ map(fn) {
289
+ return tryOrPanic(() => new Ok2(fn(this.value)), "map callback threw");
290
+ }
291
+ mapError(_fn) {
292
+ return this;
293
+ }
294
+ andThen(fn) {
295
+ return tryOrPanic(() => fn(this.value), "andThen callback threw");
296
+ }
297
+ andThenAsync(fn) {
298
+ return tryOrPanicAsync(() => fn(this.value), "andThenAsync callback threw");
299
+ }
300
+ match(handlers) {
301
+ return tryOrPanic(() => handlers.ok(this.value), "match ok handler threw");
302
+ }
303
+ unwrap(_message) {
304
+ return this.value;
305
+ }
306
+ unwrapOr(_fallback) {
307
+ return this.value;
308
+ }
309
+ tap(fn) {
310
+ return tryOrPanic(() => {
311
+ fn(this.value);
312
+ return this;
313
+ }, "tap callback threw");
314
+ }
315
+ tapAsync(fn) {
316
+ return tryOrPanicAsync(async () => {
317
+ await fn(this.value);
318
+ return this;
319
+ }, "tapAsync callback threw");
320
+ }
321
+ *[Symbol.iterator]() {
322
+ return this.value;
323
+ }
324
+ };
325
+ var Err = class Err2 {
326
+ status = "error";
327
+ constructor(error) {
328
+ this.error = error;
329
+ }
330
+ isOk() {
331
+ return false;
332
+ }
333
+ isErr() {
334
+ return true;
335
+ }
336
+ map(_fn) {
337
+ return this;
338
+ }
339
+ mapError(fn) {
340
+ return tryOrPanic(() => new Err2(fn(this.error)), "mapError callback threw");
341
+ }
342
+ andThen(_fn) {
343
+ return this;
344
+ }
345
+ andThenAsync(_fn) {
346
+ return Promise.resolve(this);
347
+ }
348
+ match(handlers) {
349
+ return tryOrPanic(() => handlers.err(this.error), "match err handler threw");
350
+ }
351
+ unwrap(message) {
352
+ return panic(message ?? `Unwrap called on Err: ${String(this.error)}`, this.error);
353
+ }
354
+ unwrapOr(fallback) {
355
+ return fallback;
356
+ }
357
+ tap(_fn) {
358
+ return this;
359
+ }
360
+ tapAsync(_fn) {
361
+ return Promise.resolve(this);
362
+ }
363
+ *[Symbol.iterator]() {
364
+ yield this;
365
+ return panic("Unreachable: Err yielded in Result.gen but generator continued", this.error);
366
+ }
367
+ };
368
+ function ok(value) {
369
+ return new Ok(value);
370
+ }
371
+ var isOk = (result) => {
372
+ return result.status === "ok";
373
+ };
374
+ var err = (error) => new Err(error);
375
+ var isError = (result) => {
376
+ return result.status === "error";
377
+ };
378
+ var tryFn = (options, config) => {
379
+ const execute = () => {
380
+ if (typeof options === "function")
381
+ try {
382
+ return ok(options());
383
+ } catch (cause) {
384
+ return err(new UnhandledException({ cause }));
385
+ }
386
+ try {
387
+ return ok(options.try());
388
+ } catch (originalCause) {
389
+ try {
390
+ return err(options.catch(originalCause));
391
+ } catch (catchHandlerError) {
392
+ throw panic("Result.try catch handler threw", catchHandlerError);
393
+ }
394
+ }
395
+ };
396
+ const times = config?.retry?.times ?? 0;
397
+ let result = execute();
398
+ for (let retry = 0;retry < times && result.status === "error"; retry++)
399
+ result = execute();
400
+ return result;
401
+ };
402
+ var tryPromise = async (options, config) => {
403
+ const execute = async () => {
404
+ if (typeof options === "function")
405
+ try {
406
+ return ok(await options());
407
+ } catch (cause) {
408
+ return err(new UnhandledException({ cause }));
409
+ }
410
+ try {
411
+ return ok(await options.try());
412
+ } catch (originalCause) {
413
+ try {
414
+ return err(await options.catch(originalCause));
415
+ } catch (catchHandlerError) {
416
+ throw panic("Result.tryPromise catch handler threw", catchHandlerError);
417
+ }
418
+ }
419
+ };
420
+ const retry = config?.retry;
421
+ if (!retry)
422
+ return execute();
423
+ const getDelay = (retryAttempt) => {
424
+ switch (retry.backoff) {
425
+ case "constant":
426
+ return retry.delayMs;
427
+ case "linear":
428
+ return retry.delayMs * (retryAttempt + 1);
429
+ case "exponential":
430
+ return retry.delayMs * 2 ** retryAttempt;
431
+ }
432
+ };
433
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
434
+ let result = await execute();
435
+ const shouldRetryFn = retry.shouldRetry ?? (() => true);
436
+ for (let attempt = 0;attempt < retry.times; attempt++) {
437
+ if (result.status !== "error")
438
+ break;
439
+ const error = result.error;
440
+ if (!tryOrPanic(() => shouldRetryFn(error), "shouldRetry predicate threw"))
441
+ break;
442
+ await sleep(getDelay(attempt));
443
+ result = await execute();
444
+ }
445
+ return result;
446
+ };
447
+ var map = dual(2, (result, fn) => {
448
+ return result.map(fn);
449
+ });
450
+ var mapError = dual(2, (result, fn) => {
451
+ return result.mapError(fn);
452
+ });
453
+ var andThen = dual(2, (result, fn) => {
454
+ return result.andThen(fn);
455
+ });
456
+ var andThenAsync = dual(2, (result, fn) => {
457
+ return result.andThenAsync(fn);
458
+ });
459
+ var match = dual(2, (result, handlers) => {
460
+ return result.match(handlers);
461
+ });
462
+ var tap = dual(2, (result, fn) => {
463
+ return result.tap(fn);
464
+ });
465
+ var tapAsync = dual(2, (result, fn) => {
466
+ return result.tapAsync(fn);
467
+ });
468
+ var unwrap = (result, message) => {
469
+ return result.unwrap(message);
470
+ };
471
+ function assertIsResult(value) {
472
+ if (value !== null && typeof value === "object" && "status" in value && (value.status === "ok" || value.status === "error"))
473
+ return;
474
+ return panic("Result.gen body must return Result.ok() or Result.err(), got: " + (value === null ? "null" : typeof value === "object" ? JSON.stringify(value) : String(value)));
475
+ }
476
+ var unwrapOr = dual(2, (result, fallback) => {
477
+ return result.unwrapOr(fallback);
478
+ });
479
+ var gen = (body, thisArg) => {
480
+ const iterator = body.call(thisArg);
481
+ if (Symbol.asyncIterator in iterator)
482
+ return (async () => {
483
+ const asyncIter = iterator;
484
+ let state$1;
485
+ try {
486
+ state$1 = await asyncIter.next();
487
+ } catch (cause) {
488
+ throw panic("generator body threw", cause);
489
+ }
490
+ assertIsResult(state$1.value);
491
+ if (!state$1.done)
492
+ try {
493
+ await asyncIter.return?.(undefined);
494
+ } catch (cause) {
495
+ throw panic("generator cleanup threw", cause);
496
+ }
497
+ return state$1.value;
498
+ })();
499
+ const syncIter = iterator;
500
+ let state;
501
+ try {
502
+ state = syncIter.next();
503
+ } catch (cause) {
504
+ throw panic("generator body threw", cause);
505
+ }
506
+ assertIsResult(state.value);
507
+ if (!state.done)
508
+ try {
509
+ syncIter.return?.(undefined);
510
+ } catch (cause) {
511
+ throw panic("generator cleanup threw", cause);
512
+ }
513
+ return state.value;
514
+ };
515
+ async function* resultAwait(promise) {
516
+ return yield* await promise;
517
+ }
518
+ function isSerializedResult(obj) {
519
+ return obj !== null && typeof obj === "object" && "status" in obj && (obj.status === "ok" && ("value" in obj) || obj.status === "error" && ("error" in obj));
520
+ }
521
+ var serialize = (result) => {
522
+ return result.status === "ok" ? {
523
+ status: "ok",
524
+ value: result.value
525
+ } : {
526
+ status: "error",
527
+ error: result.error
528
+ };
529
+ };
530
+ var deserialize = (value) => {
531
+ if (isSerializedResult(value))
532
+ return value.status === "ok" ? new Ok(value.value) : new Err(value.error);
533
+ return err(new ResultDeserializationError({ value }));
534
+ };
535
+ var hydrate = (value) => {
536
+ return deserialize(value);
537
+ };
538
+ var partition = (results) => {
539
+ const oks = [];
540
+ const errs = [];
541
+ for (const r of results)
542
+ if (r.status === "ok")
543
+ oks.push(r.value);
544
+ else
545
+ errs.push(r.error);
546
+ return [oks, errs];
547
+ };
548
+ var flatten = (result) => {
549
+ if (result.status === "ok")
550
+ return result.value;
551
+ return result;
552
+ };
553
+ var Result = {
554
+ ok,
555
+ isOk,
556
+ err,
557
+ isError,
558
+ try: tryFn,
559
+ tryPromise,
560
+ map,
561
+ mapError,
562
+ andThen,
563
+ andThenAsync,
564
+ match,
565
+ tap,
566
+ tapAsync,
567
+ unwrap,
568
+ unwrapOr,
569
+ gen,
570
+ await: resultAwait,
571
+ serialize,
572
+ deserialize,
573
+ hydrate,
574
+ partition,
575
+ flatten
576
+ };
577
+
148
578
  // src/create-project.ts
579
+ var toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
580
+ var tryAsync = (fn, mapError2) => Result.tryPromise({ try: fn, catch: mapError2 });
581
+
582
+ class UserCancelledError extends TaggedError("UserCancelledError")() {
583
+ constructor(message) {
584
+ super({ message });
585
+ }
586
+ }
587
+
588
+ class ShellCommandError extends TaggedError("ShellCommandError")() {
589
+ constructor(command, output) {
590
+ super({
591
+ message: `Command failed (${command}): ${output}`,
592
+ command,
593
+ output
594
+ });
595
+ }
596
+ }
597
+
598
+ class TemplateProcessingError extends TaggedError("TemplateProcessingError")() {
599
+ constructor(cause) {
600
+ super({ message: `Failed to process template: ${toErrorMessage(cause)}`, cause });
601
+ }
602
+ }
149
603
  async function createProject(options) {
150
604
  const { name, dir, template, git, install, prompt, spinner, colors, shell, offline } = options;
151
- try {
152
- await shell`test -d ${dir}`.quiet();
605
+ const directoryCheck = await shell`test -d ${dir}`.nothrow();
606
+ if (directoryCheck.exitCode === 0) {
153
607
  const overwrite = await prompt.confirm(`Directory ${dir} already exists. Overwrite?`, { default: false });
154
608
  if (!overwrite) {
155
- console.log(colors.red("Cancelled"));
156
- process.exit(1);
609
+ return Result.err(new UserCancelledError("Cancelled"));
610
+ }
611
+ const removeDirectory = await shell`rm -rf ${dir}`.nothrow();
612
+ if (removeDirectory.exitCode !== 0) {
613
+ return Result.err(new ShellCommandError(`rm -rf ${dir}`, removeDirectory.stderr.toString().trim()));
157
614
  }
158
- await shell`rm -rf ${dir}`;
159
- } catch {}
615
+ }
160
616
  const spin = spinner("Creating project structure...");
161
617
  spin.start();
162
- await shell`mkdir -p ${dir}`;
163
- try {
164
- let templateSource = template;
165
- if (await isLocalTemplate(template)) {
166
- templateSource = getBundledTemplatePath(template);
167
- } else {
168
- templateSource = resolveTemplateSource(template);
618
+ const mkdirResult = await shell`mkdir -p ${dir}`.nothrow();
619
+ if (mkdirResult.exitCode !== 0) {
620
+ spin.fail("Failed to create project directory");
621
+ return Result.err(new ShellCommandError(`mkdir -p ${dir}`, mkdirResult.stderr.toString().trim()));
622
+ }
623
+ let templateSource = template;
624
+ if (await isLocalTemplate(template)) {
625
+ templateSource = getBundledTemplatePath(template);
626
+ } else {
627
+ templateSource = resolveTemplateSource(template);
628
+ }
629
+ const templateResult = await tryAsync(() => processTemplate({
630
+ source: templateSource,
631
+ dir,
632
+ offline,
633
+ variables: {
634
+ name,
635
+ version: "0.1.0",
636
+ description: "A CLI built with Bunli",
637
+ author: "",
638
+ license: "MIT",
639
+ year: new Date().getFullYear().toString()
169
640
  }
170
- const { manifest } = await processTemplate({
171
- source: templateSource,
172
- dir,
173
- offline,
174
- variables: {
175
- name,
176
- version: "0.1.0",
177
- description: `A CLI built with Bunli`,
178
- author: "",
179
- license: "MIT",
180
- year: new Date().getFullYear().toString()
181
- }
182
- });
183
- spin.succeed("Project structure created");
184
- if (git) {
185
- const gitSpin = spinner("Initializing git repository...");
186
- gitSpin.start();
187
- try {
188
- await shell`cd ${dir} && git init`.quiet();
189
- await shell`cd ${dir} && git add .`.quiet();
190
- await shell`cd ${dir} && git commit -m "feat: initialize ${name} CLI project with Bunli
641
+ }), (cause) => new TemplateProcessingError(cause));
642
+ if (Result.isError(templateResult)) {
643
+ spin.fail("Failed to create project");
644
+ console.error(colors.red(templateResult.error.message));
645
+ const cleanup = await shell`rm -rf ${dir}`.nothrow();
646
+ if (cleanup.exitCode !== 0) {
647
+ console.error(colors.yellow(`Warning: cleanup failed: ${cleanup.stderr.toString().trim()}`));
648
+ }
649
+ return templateResult;
650
+ }
651
+ spin.succeed("Project structure created");
652
+ if (git) {
653
+ const gitSpin = spinner("Initializing git repository...");
654
+ gitSpin.start();
655
+ const gitInit = await shell`cd ${dir} && git init`.nothrow();
656
+ const gitAdd = await shell`cd ${dir} && git add .`.nothrow();
657
+ const gitCommit = await shell`cd ${dir} && git commit -m "feat: initialize ${name} CLI project with Bunli
191
658
 
192
659
  - Generated using create-bunli template
193
660
  - Includes basic CLI structure with commands directory
194
661
  - Configured with Bunli build system and TypeScript
195
- - Ready for development with bun run dev"`;
196
- gitSpin.succeed("Git repository initialized");
197
- } catch (error) {
198
- gitSpin.fail("Failed to initialize git repository");
199
- console.error(colors.dim(` ${error}`));
662
+ - Ready for development with bun run dev"`.nothrow();
663
+ if (gitInit.exitCode === 0 && gitAdd.exitCode === 0 && gitCommit.exitCode === 0) {
664
+ gitSpin.succeed("Git repository initialized");
665
+ } else {
666
+ gitSpin.fail("Failed to initialize git repository");
667
+ const output = [gitInit.stderr, gitAdd.stderr, gitCommit.stderr].map((value) => value.toString().trim()).filter(Boolean).join(`
668
+ `);
669
+ if (output) {
670
+ console.error(colors.dim(` ${output}`));
200
671
  }
201
672
  }
202
- if (install) {
203
- const installSpin = spinner(`Installing dependencies...`);
204
- installSpin.start();
205
- try {
206
- await shell`cd ${dir} && bun install`;
207
- installSpin.succeed("Dependencies installed");
208
- } catch (error) {
209
- installSpin.fail("Failed to install dependencies");
210
- console.error(colors.dim(` You can install them manually by running: bun install`));
673
+ }
674
+ if (install) {
675
+ const installSpin = spinner("Installing dependencies...");
676
+ installSpin.start();
677
+ const installResult = await shell`cd ${dir} && bun install`.nothrow();
678
+ if (installResult.exitCode === 0) {
679
+ installSpin.succeed("Dependencies installed");
680
+ } else {
681
+ installSpin.fail("Failed to install dependencies");
682
+ console.error(colors.dim(" You can install them manually by running: bun install"));
683
+ const errorOutput = installResult.stderr.toString().trim();
684
+ if (errorOutput) {
685
+ console.error(colors.dim(` ${errorOutput}`));
211
686
  }
212
687
  }
213
- } catch (error) {
214
- spin.fail("Failed to create project");
215
- console.error(colors.red(`Error: ${error}`));
216
- try {
217
- await shell`rm -rf ${dir}`.quiet();
218
- } catch {}
219
- process.exit(1);
220
688
  }
689
+ return Result.ok(undefined);
221
690
  }
222
691
 
223
692
  // src/index.ts