create-bunli 0.7.0 → 0.8.1

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 (106) hide show
  1. package/README.md +18 -6
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +372 -195
  5. package/dist/create-project.d.ts +5 -4
  6. package/dist/create-project.d.ts.map +1 -0
  7. package/dist/create.d.ts +4 -3
  8. package/dist/create.d.ts.map +1 -0
  9. package/dist/index.d.ts +6 -3
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +344 -187
  12. package/dist/steps.d.ts +36 -0
  13. package/dist/steps.d.ts.map +1 -0
  14. package/dist/template-engine.d.ts +3 -2
  15. package/dist/template-engine.d.ts.map +1 -0
  16. package/dist/templates/advanced/README.md +8 -4
  17. package/dist/templates/advanced/bunli.config.ts +19 -19
  18. package/dist/templates/advanced/package.json +9 -4
  19. package/dist/templates/advanced/src/commands/config.ts +129 -118
  20. package/dist/templates/advanced/src/commands/init.ts +53 -59
  21. package/dist/templates/advanced/src/commands/serve.ts +77 -82
  22. package/dist/templates/advanced/src/commands/validate.ts +58 -64
  23. package/dist/templates/advanced/src/index.ts +30 -29
  24. package/dist/templates/advanced/src/utils/config.ts +48 -47
  25. package/dist/templates/advanced/src/utils/constants.ts +6 -6
  26. package/dist/templates/advanced/src/utils/glob.ts +29 -28
  27. package/dist/templates/advanced/src/utils/validator.ts +60 -61
  28. package/dist/templates/advanced/template.json +2 -6
  29. package/dist/templates/advanced/tsconfig.json +1 -1
  30. package/dist/templates/basic/README.md +1 -1
  31. package/dist/templates/basic/bunli.config.ts +17 -17
  32. package/dist/templates/basic/package.json +4 -3
  33. package/dist/templates/basic/src/commands/hello.ts +20 -25
  34. package/dist/templates/basic/src/index.ts +9 -8
  35. package/dist/templates/basic/template.json +2 -6
  36. package/dist/templates/basic/tsconfig.json +1 -1
  37. package/dist/templates/monorepo/README.md +1 -1
  38. package/dist/templates/monorepo/bunli.config.ts +21 -21
  39. package/dist/templates/monorepo/package.json +3 -2
  40. package/dist/templates/monorepo/packages/cli/package.json +10 -5
  41. package/dist/templates/monorepo/packages/cli/src/index.ts +13 -13
  42. package/dist/templates/monorepo/packages/cli/tsconfig.json +3 -6
  43. package/dist/templates/monorepo/packages/core/package.json +6 -5
  44. package/dist/templates/monorepo/packages/core/scripts/build.ts +10 -10
  45. package/dist/templates/monorepo/packages/core/src/commands/analyze.ts +58 -56
  46. package/dist/templates/monorepo/packages/core/src/commands/process.ts +39 -46
  47. package/dist/templates/monorepo/packages/core/src/index.ts +3 -3
  48. package/dist/templates/monorepo/packages/core/src/types.ts +15 -15
  49. package/dist/templates/monorepo/packages/core/tsconfig.json +3 -5
  50. package/dist/templates/monorepo/packages/utils/package.json +6 -5
  51. package/dist/templates/monorepo/packages/utils/scripts/build.ts +10 -10
  52. package/dist/templates/monorepo/packages/utils/src/format.ts +19 -19
  53. package/dist/templates/monorepo/packages/utils/src/index.ts +3 -3
  54. package/dist/templates/monorepo/packages/utils/src/json.ts +4 -4
  55. package/dist/templates/monorepo/packages/utils/src/logger.ts +9 -9
  56. package/dist/templates/monorepo/packages/utils/tsconfig.json +2 -2
  57. package/dist/templates/monorepo/template.json +2 -6
  58. package/dist/templates/monorepo/tsconfig.json +1 -1
  59. package/dist/templates/monorepo/turbo.json +1 -1
  60. package/dist/types.d.ts +2 -1
  61. package/dist/types.d.ts.map +1 -0
  62. package/package.json +35 -34
  63. package/templates/advanced/README.md +8 -4
  64. package/templates/advanced/bunli.config.ts +19 -19
  65. package/templates/advanced/package.json +9 -4
  66. package/templates/advanced/src/commands/config.ts +129 -118
  67. package/templates/advanced/src/commands/init.ts +53 -59
  68. package/templates/advanced/src/commands/serve.ts +77 -82
  69. package/templates/advanced/src/commands/validate.ts +58 -64
  70. package/templates/advanced/src/index.ts +30 -29
  71. package/templates/advanced/src/utils/config.ts +48 -47
  72. package/templates/advanced/src/utils/constants.ts +6 -6
  73. package/templates/advanced/src/utils/glob.ts +29 -28
  74. package/templates/advanced/src/utils/validator.ts +60 -61
  75. package/templates/advanced/template.json +2 -6
  76. package/templates/advanced/tsconfig.json +1 -1
  77. package/templates/basic/README.md +1 -1
  78. package/templates/basic/bunli.config.ts +17 -17
  79. package/templates/basic/package.json +4 -3
  80. package/templates/basic/src/commands/hello.ts +20 -25
  81. package/templates/basic/src/index.ts +9 -8
  82. package/templates/basic/template.json +2 -6
  83. package/templates/basic/tsconfig.json +1 -1
  84. package/templates/monorepo/README.md +1 -1
  85. package/templates/monorepo/bunli.config.ts +21 -21
  86. package/templates/monorepo/package.json +3 -2
  87. package/templates/monorepo/packages/cli/package.json +10 -5
  88. package/templates/monorepo/packages/cli/src/index.ts +13 -13
  89. package/templates/monorepo/packages/cli/tsconfig.json +3 -6
  90. package/templates/monorepo/packages/core/package.json +6 -5
  91. package/templates/monorepo/packages/core/scripts/build.ts +10 -10
  92. package/templates/monorepo/packages/core/src/commands/analyze.ts +58 -56
  93. package/templates/monorepo/packages/core/src/commands/process.ts +39 -46
  94. package/templates/monorepo/packages/core/src/index.ts +3 -3
  95. package/templates/monorepo/packages/core/src/types.ts +15 -15
  96. package/templates/monorepo/packages/core/tsconfig.json +3 -5
  97. package/templates/monorepo/packages/utils/package.json +6 -5
  98. package/templates/monorepo/packages/utils/scripts/build.ts +10 -10
  99. package/templates/monorepo/packages/utils/src/format.ts +19 -19
  100. package/templates/monorepo/packages/utils/src/index.ts +3 -3
  101. package/templates/monorepo/packages/utils/src/json.ts +4 -4
  102. package/templates/monorepo/packages/utils/src/logger.ts +9 -9
  103. package/templates/monorepo/packages/utils/tsconfig.json +2 -2
  104. package/templates/monorepo/template.json +2 -6
  105. package/templates/monorepo/tsconfig.json +1 -1
  106. package/templates/monorepo/turbo.json +1 -1
package/dist/index.js CHANGED
@@ -1,158 +1,5 @@
1
1
  // @bun
2
- // src/template-engine.ts
3
- import { downloadTemplate } from "giget";
4
- import { readdir } from "fs/promises";
5
- import { join } from "path";
6
- async function processTemplate(options) {
7
- const { source, dir, offline, variables = {} } = options;
8
- let templateDir;
9
- if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
10
- const sourceDir = source.startsWith("/") ? source : join(process.cwd(), source);
11
- const copyExitCode = await Bun.spawn(["cp", "-r", sourceDir + "/.", dir], {
12
- stdout: "inherit",
13
- stderr: "inherit"
14
- }).exited;
15
- if (copyExitCode !== 0) {
16
- throw new Error(`Failed to copy local template from ${sourceDir}`);
17
- }
18
- templateDir = dir;
19
- } else {
20
- const result = await downloadTemplate(source, {
21
- dir,
22
- offline,
23
- preferOffline: true,
24
- force: true
25
- });
26
- templateDir = result.dir;
27
- }
28
- const manifest = await loadTemplateManifest(templateDir);
29
- if (manifest?.files || Object.keys(variables).length > 0) {
30
- await processTemplateFiles(templateDir, variables, manifest);
31
- }
32
- if (manifest?.hooks?.postInstall) {
33
- await runPostInstallHooks(templateDir, manifest.hooks.postInstall);
34
- }
35
- return { dir: templateDir, manifest };
36
- }
37
- async function loadTemplateManifest(dir) {
38
- const possiblePaths = [
39
- join(dir, "template.json"),
40
- join(dir, ".template.json"),
41
- join(dir, "template.yaml"),
42
- join(dir, ".template.yaml")
43
- ];
44
- for (const path of possiblePaths) {
45
- const file = Bun.file(path);
46
- if (await file.exists()) {
47
- const content = await file.text();
48
- if (path.endsWith(".json")) {
49
- const manifest = JSON.parse(content);
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
- }
57
- return manifest;
58
- }
59
- }
60
- }
61
- return null;
62
- }
63
- async function processTemplateFiles(dir, variables, manifest) {
64
- const files = await getFilesToProcess(dir, manifest);
65
- for (const file of files) {
66
- const filePath = join(dir, file);
67
- const content = await Bun.file(filePath).text();
68
- let processedContent = content;
69
- for (const [key, value] of Object.entries(variables)) {
70
- processedContent = processedContent.replaceAll(`{{${key}}}`, value).replaceAll(`<%= ${key} %>`, value).replaceAll(`$${key}`, value).replaceAll(`__${key}__`, value);
71
- }
72
- let newFilePath = filePath;
73
- for (const [key, value] of Object.entries(variables)) {
74
- newFilePath = newFilePath.replaceAll(`__${key}__`, value);
75
- }
76
- await Bun.write(newFilePath, processedContent);
77
- if (newFilePath !== filePath) {
78
- await Bun.spawn(["rm", filePath]).exited;
79
- }
80
- }
81
- }
82
- async function getFilesToProcess(dir, manifest) {
83
- if (manifest?.files?.include) {
84
- return manifest.files.include;
85
- }
86
- const files = [];
87
- async function walk(currentDir, prefix = "") {
88
- const entries = await readdir(currentDir, { withFileTypes: true });
89
- for (const entry of entries) {
90
- const path = join(prefix, entry.name);
91
- if (entry.isDirectory()) {
92
- if (!["node_modules", ".git", ".next", "dist", "build"].includes(entry.name)) {
93
- await walk(join(currentDir, entry.name), path);
94
- }
95
- } else {
96
- if (!path.match(/^(template\.json|\.template\.json|\.DS_Store|Thumbs\.db)$/) || path === ".gitignore") {
97
- files.push(path);
98
- }
99
- }
100
- }
101
- }
102
- await walk(dir);
103
- if (manifest?.files?.exclude) {
104
- return files.filter((file) => {
105
- return !manifest.files.exclude.some((pattern) => {
106
- const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
107
- const regex = new RegExp(`^${regexPattern}$`);
108
- return regex.test(file);
109
- });
110
- });
111
- }
112
- return files;
113
- }
114
- async function runPostInstallHooks(dir, hooks) {
115
- for (const hook of hooks) {
116
- const proc = Bun.spawn(hook.split(" "), {
117
- cwd: dir,
118
- stdout: "inherit",
119
- stderr: "inherit"
120
- });
121
- const exitCode = await proc.exited;
122
- if (exitCode !== 0) {
123
- throw new Error(`Post-install hook failed: ${hook}`);
124
- }
125
- }
126
- }
127
- function resolveTemplateSource(template) {
128
- const specialTemplates = {
129
- basic: "github:bunli/templates/basic",
130
- advanced: "github:bunli/templates/advanced",
131
- monorepo: "github:bunli/templates/monorepo"
132
- };
133
- if (specialTemplates[template]) {
134
- return specialTemplates[template];
135
- }
136
- if (template.startsWith("npm:")) {
137
- return template.replace("npm:", "npm:/");
138
- }
139
- if (template.includes("/") && !template.includes(":")) {
140
- return `github:${template}`;
141
- }
142
- return template;
143
- }
144
- function getBundledTemplatePath(name) {
145
- return join(import.meta.dir, "..", "templates", name);
146
- }
147
- async function isLocalTemplate(template) {
148
- if (template.startsWith("file:") || template.startsWith("./") || template.startsWith("../")) {
149
- return true;
150
- }
151
- const bundledPath = getBundledTemplatePath(template);
152
- return await Bun.file(join(bundledPath, "package.json")).exists();
153
- }
154
-
155
- // ../../node_modules/better-result/dist/index.mjs
2
+ // ../../node_modules/.bun/better-result@2.7.0/node_modules/better-result/dist/index.mjs
156
3
  function dual(arity, body) {
157
4
  if (arity === 2)
158
5
  return (...args) => {
@@ -575,8 +422,325 @@ var Result = {
575
422
  flatten
576
423
  };
577
424
 
425
+ // src/steps.ts
426
+ import { existsSync } from "fs";
427
+ import { join } from "path";
428
+ var LOCKFILE_MAP = [
429
+ ["bun.lock", "bun"],
430
+ ["bun.lockb", "bun"],
431
+ ["pnpm-lock.yaml", "pnpm"],
432
+ ["yarn.lock", "yarn"],
433
+ ["package-lock.json", "npm"]
434
+ ];
435
+ function detectPackageManager(cwd) {
436
+ const dir = cwd ?? process.cwd();
437
+ for (const [lockfile, manager] of LOCKFILE_MAP) {
438
+ if (existsSync(join(dir, lockfile))) {
439
+ return manager;
440
+ }
441
+ }
442
+ const userAgent = process.env.npm_config_user_agent;
443
+ if (userAgent) {
444
+ if (userAgent.startsWith("bun"))
445
+ return "bun";
446
+ if (userAgent.startsWith("pnpm"))
447
+ return "pnpm";
448
+ if (userAgent.startsWith("yarn"))
449
+ return "yarn";
450
+ if (userAgent.startsWith("npm"))
451
+ return "npm";
452
+ }
453
+ return "bun";
454
+ }
455
+ function isInGitRepo(cwd) {
456
+ try {
457
+ const result = Bun.spawnSync(["git", "rev-parse", "--is-inside-work-tree"], {
458
+ cwd: cwd ?? process.cwd(),
459
+ stdout: "ignore",
460
+ stderr: "ignore"
461
+ });
462
+ return result.exitCode === 0;
463
+ } catch {
464
+ return false;
465
+ }
466
+ }
467
+ async function runSteps(dir, steps) {
468
+ for (const step of steps) {
469
+ switch (step.type) {
470
+ case "install":
471
+ await runInstall(dir);
472
+ break;
473
+ case "git-init":
474
+ await runGitInit(dir, step.commit);
475
+ break;
476
+ case "open-editor":
477
+ await runOpenEditor(dir);
478
+ break;
479
+ case "command":
480
+ await runCommand(step.cmd, step.cwd ?? dir);
481
+ break;
482
+ }
483
+ }
484
+ }
485
+ async function runInstall(cwd) {
486
+ const pm = detectPackageManager(cwd);
487
+ const proc = Bun.spawn([pm, "install"], {
488
+ cwd,
489
+ stdout: "pipe",
490
+ stderr: "pipe"
491
+ });
492
+ const exitCode = await proc.exited;
493
+ if (exitCode !== 0) {
494
+ const stderr = await new Response(proc.stderr).text();
495
+ throw new Error(`"${pm} install" exited with code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
496
+ }
497
+ }
498
+ async function runGitInit(cwd, commit) {
499
+ await spawnChecked(["git", "init"], cwd, "git init");
500
+ if (commit) {
501
+ await ensureGitIdentity(cwd);
502
+ await spawnChecked(["git", "add", "."], cwd, "git add");
503
+ await spawnChecked(["git", "commit", "-m", commit], cwd, "git commit");
504
+ }
505
+ }
506
+ async function runOpenEditor(cwd) {
507
+ const editor = process.env.EDITOR || "code";
508
+ try {
509
+ const proc = Bun.spawn([editor, cwd], {
510
+ stdout: "ignore",
511
+ stderr: "ignore"
512
+ });
513
+ const raceResult = await Promise.race([
514
+ proc.exited.then((code) => ({ kind: "exited", code })),
515
+ new Promise((resolve) => setTimeout(() => resolve({ kind: "timeout" }), 500))
516
+ ]);
517
+ if (raceResult.kind === "exited" && raceResult.code !== 0) {
518
+ console.warn(`Warning: could not open editor "${editor}" (exit code ${raceResult.code})`);
519
+ }
520
+ } catch {
521
+ console.warn(`Warning: could not open editor "${editor}"`);
522
+ }
523
+ }
524
+ function getCommandSpawnArgs(cmd, platform = process.platform) {
525
+ return platform === "win32" ? ["cmd", "/d", "/s", "/c", cmd] : ["sh", "-c", cmd];
526
+ }
527
+ async function runCommand(cmd, cwd) {
528
+ const proc = Bun.spawn(getCommandSpawnArgs(cmd), {
529
+ cwd,
530
+ stdout: "inherit",
531
+ stderr: "inherit"
532
+ });
533
+ const exitCode = await proc.exited;
534
+ if (exitCode !== 0) {
535
+ throw new Error(`Command "${cmd}" exited with code ${exitCode}`);
536
+ }
537
+ }
538
+ async function ensureGitIdentity(cwd) {
539
+ const hasName = Bun.spawnSync(["git", "config", "user.name"], { cwd }).exitCode === 0;
540
+ const hasEmail = Bun.spawnSync(["git", "config", "user.email"], { cwd }).exitCode === 0;
541
+ if (!hasName) {
542
+ await spawnChecked(["git", "config", "user.name", "Bunli"], cwd, "git config user.name");
543
+ }
544
+ if (!hasEmail) {
545
+ await spawnChecked(["git", "config", "user.email", "bunli@scaffolded.project"], cwd, "git config user.email");
546
+ }
547
+ }
548
+ async function spawnChecked(cmd, cwd, label) {
549
+ const proc = Bun.spawn(cmd, {
550
+ cwd,
551
+ stdout: "ignore",
552
+ stderr: "pipe"
553
+ });
554
+ const exitCode = await proc.exited;
555
+ if (exitCode !== 0) {
556
+ const stderr = await new Response(proc.stderr).text();
557
+ throw new Error(`"${label}" failed with exit code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
558
+ }
559
+ }
560
+
561
+ // src/template-engine.ts
562
+ import { readdir } from "fs/promises";
563
+ import { join as join2 } from "path";
564
+ import { downloadTemplate } from "giget";
565
+ async function processTemplate(options) {
566
+ const { source, dir, offline, variables = {} } = options;
567
+ let templateDir;
568
+ if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
569
+ const sourceDir = source.startsWith("/") ? source : join2(process.cwd(), source);
570
+ const copyExitCode = await Bun.spawn(["cp", "-r", sourceDir + "/.", dir], {
571
+ stdout: "inherit",
572
+ stderr: "inherit"
573
+ }).exited;
574
+ if (copyExitCode !== 0) {
575
+ throw new Error(`Failed to copy local template from ${sourceDir}`);
576
+ }
577
+ templateDir = dir;
578
+ } else {
579
+ const result = await downloadTemplate(source, {
580
+ dir,
581
+ offline,
582
+ preferOffline: true,
583
+ force: true
584
+ });
585
+ templateDir = result.dir;
586
+ }
587
+ const manifest = await loadTemplateManifest(templateDir);
588
+ if (manifest?.files || Object.keys(variables).length > 0) {
589
+ await processTemplateFiles(templateDir, variables, manifest);
590
+ }
591
+ if (manifest?.hooks?.postInstall) {
592
+ await runPostInstallHooks(templateDir, manifest.hooks.postInstall);
593
+ }
594
+ return { dir: templateDir, manifest };
595
+ }
596
+ async function loadTemplateManifest(dir) {
597
+ const possiblePaths = [
598
+ join2(dir, "template.json"),
599
+ join2(dir, ".template.json"),
600
+ join2(dir, "template.yaml"),
601
+ join2(dir, ".template.yaml")
602
+ ];
603
+ for (const path of possiblePaths) {
604
+ const file = Bun.file(path);
605
+ if (await file.exists()) {
606
+ const content = await file.text();
607
+ if (path.endsWith(".json")) {
608
+ const manifest = JSON.parse(content);
609
+ const removeExitCode = await Bun.spawn(["rm", "-f", path], {
610
+ stdout: "ignore",
611
+ stderr: "ignore"
612
+ }).exited;
613
+ if (removeExitCode !== 0) {
614
+ console.warn(`Warning: failed to remove template manifest ${path}`);
615
+ }
616
+ return manifest;
617
+ }
618
+ }
619
+ }
620
+ return null;
621
+ }
622
+ async function processTemplateFiles(dir, variables, manifest) {
623
+ const files = await getFilesToProcess(dir, manifest);
624
+ for (const file of files) {
625
+ const filePath = join2(dir, file);
626
+ const content = await Bun.file(filePath).text();
627
+ let processedContent = content;
628
+ for (const [key, value] of Object.entries(variables)) {
629
+ processedContent = processedContent.replaceAll(`{{${key}}}`, value).replaceAll(`<%= ${key} %>`, value).replaceAll(`$${key}`, value).replaceAll(`__${key}__`, value);
630
+ }
631
+ let newFilePath = filePath;
632
+ for (const [key, value] of Object.entries(variables)) {
633
+ newFilePath = newFilePath.replaceAll(`__${key}__`, value);
634
+ }
635
+ await Bun.write(newFilePath, processedContent);
636
+ if (newFilePath !== filePath) {
637
+ await Bun.spawn(["rm", filePath]).exited;
638
+ }
639
+ }
640
+ }
641
+ async function getFilesToProcess(dir, manifest) {
642
+ if (manifest?.files?.include) {
643
+ return manifest.files.include;
644
+ }
645
+ const files = [];
646
+ async function walk(currentDir, prefix = "") {
647
+ const entries = await readdir(currentDir, { withFileTypes: true });
648
+ for (const entry of entries) {
649
+ const path = join2(prefix, entry.name);
650
+ if (entry.isDirectory()) {
651
+ if (!["node_modules", ".git", ".next", "dist", "build"].includes(entry.name)) {
652
+ await walk(join2(currentDir, entry.name), path);
653
+ }
654
+ } else {
655
+ if (!path.match(/^(template\.json|\.template\.json|\.DS_Store|Thumbs\.db)$/) || path === ".gitignore") {
656
+ files.push(path);
657
+ }
658
+ }
659
+ }
660
+ }
661
+ await walk(dir);
662
+ if (manifest?.files?.exclude) {
663
+ return files.filter((file) => {
664
+ return !manifest.files.exclude.some((pattern) => {
665
+ const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
666
+ const regex = new RegExp(`^${regexPattern}$`);
667
+ return regex.test(file);
668
+ });
669
+ });
670
+ }
671
+ return files;
672
+ }
673
+ async function runPostInstallHooks(dir, hooks) {
674
+ for (const hook of hooks) {
675
+ const proc = Bun.spawn(hook.split(" "), {
676
+ cwd: dir,
677
+ stdout: "inherit",
678
+ stderr: "inherit"
679
+ });
680
+ const exitCode = await proc.exited;
681
+ if (exitCode !== 0) {
682
+ throw new Error(`Post-install hook failed: ${hook}`);
683
+ }
684
+ }
685
+ }
686
+ function resolveTemplateSource(template) {
687
+ const specialTemplates = {
688
+ basic: "github:bunli/templates/basic",
689
+ advanced: "github:bunli/templates/advanced",
690
+ monorepo: "github:bunli/templates/monorepo"
691
+ };
692
+ if (specialTemplates[template]) {
693
+ return specialTemplates[template];
694
+ }
695
+ if (template.startsWith("npm:")) {
696
+ return template.replace("npm:", "npm:/");
697
+ }
698
+ if (template.includes("/") && !template.includes(":")) {
699
+ return `github:${template}`;
700
+ }
701
+ return template;
702
+ }
703
+ function getBundledTemplatePath(name) {
704
+ return join2(import.meta.dir, "..", "templates", name);
705
+ }
706
+ async function isLocalTemplate(template) {
707
+ if (template.startsWith("file:") || template.startsWith("./") || template.startsWith("../")) {
708
+ return true;
709
+ }
710
+ const bundledPath = getBundledTemplatePath(template);
711
+ return await Bun.file(join2(bundledPath, "package.json")).exists();
712
+ }
713
+
578
714
  // src/create-project.ts
579
715
  var toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
716
+ function stepLabel(step) {
717
+ switch (step.type) {
718
+ case "install":
719
+ return {
720
+ running: "Installing dependencies...",
721
+ done: "Dependencies installed",
722
+ failed: "Failed to install dependencies"
723
+ };
724
+ case "git-init":
725
+ return {
726
+ running: "Initializing git repository...",
727
+ done: "Git repository initialized",
728
+ failed: "Failed to initialize git repository"
729
+ };
730
+ case "open-editor":
731
+ return {
732
+ running: "Opening editor...",
733
+ done: "Editor opened",
734
+ failed: "Failed to open editor"
735
+ };
736
+ case "command":
737
+ return {
738
+ running: `Running ${step.cmd}...`,
739
+ done: `Completed ${step.cmd}`,
740
+ failed: `Failed to run ${step.cmd}`
741
+ };
742
+ }
743
+ }
580
744
  var tryAsync = (fn, mapError2) => Result.tryPromise({ try: fn, catch: mapError2 });
581
745
 
582
746
  class UserCancelledError extends TaggedError("UserCancelledError")() {
@@ -604,7 +768,9 @@ async function createProject(options) {
604
768
  const { name, dir, template, git, install, prompt, spinner, colors, shell, offline } = options;
605
769
  const directoryCheck = await shell`test -d ${dir}`.nothrow();
606
770
  if (directoryCheck.exitCode === 0) {
607
- const overwrite = await prompt.confirm(`Directory ${dir} already exists. Overwrite?`, { default: false });
771
+ const overwrite = await prompt.confirm(`Directory ${dir} already exists. Overwrite?`, {
772
+ default: false
773
+ });
608
774
  if (!overwrite) {
609
775
  return Result.err(new UserCancelledError("Cancelled"));
610
776
  }
@@ -649,40 +815,28 @@ async function createProject(options) {
649
815
  return templateResult;
650
816
  }
651
817
  spin.succeed("Project structure created");
818
+ const postSteps = [];
819
+ if (install) {
820
+ postSteps.push({ type: "install" });
821
+ }
652
822
  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
658
-
659
- - Generated using create-bunli template
660
- - Includes basic CLI structure with commands directory
661
- - Configured with Bunli build system and TypeScript
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}`));
671
- }
672
- }
823
+ postSteps.push({
824
+ type: "git-init",
825
+ commit: `feat: initialize ${name} CLI project with Bunli`
826
+ });
673
827
  }
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}`));
828
+ for (const step of postSteps) {
829
+ const label = stepLabel(step);
830
+ const stepSpin = spinner(label.running);
831
+ stepSpin.start();
832
+ try {
833
+ await runSteps(dir, [step]);
834
+ stepSpin.succeed(label.done);
835
+ } catch (error) {
836
+ stepSpin.fail(label.failed);
837
+ const message = error instanceof Error ? error.message : String(error);
838
+ if (message) {
839
+ console.error(colors.dim(` ${message}`));
686
840
  }
687
841
  }
688
842
  }
@@ -693,8 +847,11 @@ async function createProject(options) {
693
847
  var version = "0.1.0";
694
848
  export {
695
849
  version,
850
+ runSteps,
696
851
  resolveTemplateSource,
697
852
  processTemplate,
698
853
  isLocalTemplate,
854
+ isInGitRepo,
855
+ detectPackageManager,
699
856
  createProject
700
857
  };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A declarative step to run after scaffolding completes.
3
+ * Steps are executed sequentially in array order by {@link runSteps}.
4
+ */
5
+ export type Step = {
6
+ readonly type: "install";
7
+ } | {
8
+ readonly type: "git-init";
9
+ readonly commit?: string;
10
+ } | {
11
+ readonly type: "open-editor";
12
+ } | {
13
+ readonly type: "command";
14
+ readonly cmd: string;
15
+ readonly cwd?: string;
16
+ };
17
+ export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
18
+ /**
19
+ * Detect the package manager for a project directory.
20
+ *
21
+ * 1. Lockfile probe (bun -> pnpm -> yarn -> npm)
22
+ * 2. npm_config_user_agent environment variable
23
+ * 3. Default to "bun" (this is a Bun-first framework)
24
+ */
25
+ export declare function detectPackageManager(cwd?: string): PackageManager;
26
+ /**
27
+ * Check whether a directory is inside an existing git repository.
28
+ */
29
+ export declare function isInGitRepo(cwd?: string): boolean;
30
+ /**
31
+ * Execute an array of post-scaffold steps sequentially.
32
+ * If any step fails, the error propagates immediately (remaining steps are skipped).
33
+ */
34
+ export declare function runSteps(dir: string, steps: Step[]): Promise<void>;
35
+ export declare function getCommandSpawnArgs(cmd: string, platform?: NodeJS.Platform): string[];
36
+ //# sourceMappingURL=steps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,MAAM,IAAI,GACZ;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAAE,GAChC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAU7D;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAkBjE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAWjD;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBxE;AAmDD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,EAAE,CAEV"}
@@ -1,7 +1,7 @@
1
- import type { TemplateManifest } from './types.js';
1
+ import type { TemplateManifest } from "./types.js";
2
2
  export interface TemplateOptions {
3
3
  source: string;
4
- type?: 'github' | 'npm' | 'local' | 'bundled';
4
+ type?: "github" | "npm" | "local" | "bundled";
5
5
  dir: string;
6
6
  offline?: boolean;
7
7
  variables?: Record<string, string>;
@@ -25,3 +25,4 @@ export declare function getBundledTemplatePath(name: string): string;
25
25
  * Check if template exists locally (for development)
26
26
  */
27
27
  export declare function isLocalTemplate(template: string): Promise<boolean>;
28
+ //# sourceMappingURL=template-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-engine.d.ts","sourceRoot":"","sources":["../src/template-engine.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,CAAC;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,eAAe;;;GA6C7D;AA2JD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAwB9D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQxE"}
@@ -21,6 +21,7 @@ bunx {{name}} [command]
21
21
  ### Commands
22
22
 
23
23
  #### `init`
24
+
24
25
  Initialize a new configuration file in the current directory.
25
26
 
26
27
  ```bash
@@ -32,6 +33,7 @@ Options:
32
33
  ```
33
34
 
34
35
  #### `validate`
36
+
35
37
  Validate files against defined rules.
36
38
 
37
39
  ```bash
@@ -44,6 +46,7 @@ Options:
44
46
  ```
45
47
 
46
48
  #### `serve`
49
+
47
50
  Start a development server.
48
51
 
49
52
  ```bash
@@ -56,6 +59,7 @@ Options:
56
59
  ```
57
60
 
58
61
  #### `config`
62
+
59
63
  Manage configuration settings.
60
64
 
61
65
  ```bash
@@ -88,9 +92,9 @@ export default {
88
92
  },
89
93
  server: {
90
94
  port: 3000,
91
- host: 'localhost'
92
- }
93
- }
95
+ host: "localhost",
96
+ },
97
+ };
94
98
  ```
95
99
 
96
100
  ## Development
@@ -111,4 +115,4 @@ bun run build
111
115
 
112
116
  ## License
113
117
 
114
- {{license}}
118
+ {{license}}