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