@create-node-app/core 0.6.0 → 0.6.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.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ <div align="center">
2
+
3
+ <h1>⚙️ <code>@create-node-app/core</code></h1>
4
+
5
+ <p><strong>Programmatic engine behind Create Awesome Node App.</strong><br/>
6
+ Import the scaffolding pipeline — composable, headless, and CI-ready.</p>
7
+
8
+ [![npm][npmversion]][npmurl]
9
+ [![Downloads][npmdownloads]][npmurl]
10
+ [![License: MIT][licensebadge]][licenseurl]
11
+
12
+ </div>
13
+
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @create-node-app/core
20
+ ```
21
+
22
+ Requires **Node.js >= 22**.
23
+
24
+ > This is the _engine_ package. For the interactive CLI, use [`create-awesome-node-app`](https://www.npmjs.com/package/create-awesome-node-app) instead.
25
+
26
+ ---
27
+
28
+ ## Usage
29
+
30
+ ### Scaffold a project programmatically
31
+
32
+ ```ts
33
+ import { createNodeApp, getTemplateDirPath } from "@create-node-app/core";
34
+
35
+ await createNodeApp(
36
+ "my-app",
37
+ {
38
+ projectName: "my-app",
39
+ template: "react-vite-boilerplate",
40
+ },
41
+ (options) => Promise.resolve(options),
42
+ );
43
+ ```
44
+
45
+ ### Check environment info
46
+
47
+ ```ts
48
+ import { printEnvInfo } from "@create-node-app/core";
49
+
50
+ await printEnvInfo();
51
+ // Prints OS, CPU, Node, npm, browsers, etc. Then exits.
52
+ ```
53
+
54
+ ### Validate the Node.js version
55
+
56
+ ```ts
57
+ import { checkNodeVersion } from "@create-node-app/core";
58
+
59
+ checkNodeVersion(">=22", "my-tool");
60
+ // Exits with a red error message if the version doesn't match.
61
+ ```
62
+
63
+ ---
64
+
65
+ ## API Reference
66
+
67
+ All exports from `@create-node-app/core`:
68
+
69
+ ### Functions
70
+
71
+ | Signature | Description |
72
+ |-----------|-------------|
73
+ | `createNodeApp(programName, options, transformOptions)` | Main scaffolding orchestrator. Resolves the template, copies files, merges configs, installs deps, and initializes git. |
74
+ | `checkNodeVersion(requiredVersion, packageName)` | Compares `process.version` against a semver range. Exits with code 1 if too old. |
75
+ | `checkForLatestVersion(packageName)` | Fetches the latest version from the npm registry. Falls back to `npm view`. Returns `null` if both fail. |
76
+ | `printEnvInfo()` | Prints OS, CPU, binaries, and browser info to stdout, then exits. |
77
+ | `getPackagePath(templateOrExtension, name?, ignorePackage?)` | Resolves a file path inside a template/extension directory (usually `package.json`). Handles GitHub URLs, `file://` URLs, and legacy slugs. |
78
+ | `getTemplateDirPath(templateOrExtensionUrl)` | Resolves the template directory. Looks for a `template/` subdirectory first, falls back to the resolved root. |
79
+ | `getTemplateBaseDirPath(templateOrExtensionUrl)` | Returns the parent of the `template/` directory (where `cna.config.json` lives). |
80
+ | `downloadRepository(options)` | Clones or pulls a Git repo into a cache dir, then copies files to the target. Supports offline mode, deduplication, and error formatting. |
81
+ | `loadTemplateCnaConfig(templateUrl)` | Loads the optional `cna.config.json` from a template's base directory. |
82
+
83
+ ### Types
84
+
85
+ | Type | Shape |
86
+ |------|-------|
87
+ | `CnaOptions` | `{ projectName, info?, verbose?, packageManager?, install?, template?, templatesOrExtensions?, ... }` |
88
+ | `CnaOptionsTransform` | `(options: CnaOptions) => Promise<CnaOptions>` |
89
+ | `CnaConfig` | `{ customOptions?: CnaCustomOption[] }` |
90
+ | `CnaCustomOption` | `{ name, type, message?, initial?, ... }` |
91
+ | `TemplateOrExtension` | `{ url: string; ignorePackage?: boolean }` |
92
+
93
+ ---
94
+
95
+ ## How It Works
96
+
97
+ ```
98
+ createNodeApp()
99
+ ├── checkNodeVersion()
100
+ ├── printEnvInfo() if info flag
101
+ ├── resolve templates/extensions via getTemplateDirPath()
102
+ ├── download Git repos via downloadRepository()
103
+ ├── load cna.config.json via loadTemplateCnaConfig()
104
+ ├── merge package.json files via lodash.merge
105
+ ├── copy/process template files (Lodash interpolation, .if-npm, .append, etc.)
106
+ ├── install dependencies (npm/yarn/pnpm/bun)
107
+ ├── git init
108
+ └── format + lint:fix post-install
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Architecture
114
+
115
+ The package is organized into these modules:
116
+
117
+ | Module | Responsibility |
118
+ |--------|---------------|
119
+ | `index.ts` | Barrel export and main `createNodeApp` orchestration |
120
+ | `installer.ts` | Project directory creation, dep installation, git init, post-install scripts |
121
+ | `loaders.ts` | File discovery, classification (`.template`, `.append`, conditional prefixes), and processing |
122
+ | `package.ts` | Deep-merges `package.json` from multiple templates/extensions |
123
+ | `paths.ts` | URL resolution — GitHub branches, `file://`, legacy slugs, cache at `~/.cna/` |
124
+ | `git.ts` | Clone/pull with deduplication, offline support, error formatting |
125
+ | `config.ts` | Reads optional `cna.config.json` for custom CLI prompts |
126
+ | `helpers.ts` | Package manager detection, online check, path/naming utilities |
127
+
128
+ ---
129
+
130
+ ## Related
131
+
132
+ - [`create-awesome-node-app`](https://www.npmjs.com/package/create-awesome-node-app) — Interactive CLI built on this core
133
+ - [Create Node App](https://github.com/Create-Node-App/create-node-app) — Monorepo
134
+ - [Templates catalog](https://github.com/Create-Node-App/cna-templates)
135
+
136
+ ---
137
+
138
+ ## License
139
+
140
+ MIT &copy; [Create Node App Contributors](https://github.com/Create-Node-App/create-node-app/graphs/contributors)
141
+
142
+ <!-- Reference links -->
143
+
144
+ [npmversion]: https://img.shields.io/npm/v/@create-node-app/core.svg?style=flat-square&color=cb3837
145
+ [npmdownloads]: https://img.shields.io/npm/dm/@create-node-app/core.svg?style=flat-square&color=cb3837
146
+ [licensebadge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square
147
+ [npmurl]: https://www.npmjs.com/package/@create-node-app/core
148
+ [licenseurl]: https://github.com/Create-Node-App/create-node-app/blob/main/LICENSE
package/dist/index.cjs CHANGED
@@ -47,7 +47,7 @@ var import_semver3 = __toESM(require("semver"), 1);
47
47
  var import_child_process3 = require("child_process");
48
48
 
49
49
  // installer.ts
50
- var import_underscore2 = __toESM(require("underscore"), 1);
50
+ var import_lodash3 = __toESM(require("lodash"), 1);
51
51
  var import_path4 = __toESM(require("path"), 1);
52
52
  var import_fs5 = __toESM(require("fs"), 1);
53
53
  var import_picocolors3 = __toESM(require("picocolors"), 1);
@@ -62,6 +62,11 @@ var import_picocolors = __toESM(require("picocolors"), 1);
62
62
  var import_semver = __toESM(require("semver"), 1);
63
63
  var import_dns = __toESM(require("dns"), 1);
64
64
  var import_url = require("url");
65
+
66
+ // executable.ts
67
+ var resolveExecutable = (bin) => process.platform === "win32" ? `${bin}.cmd` : bin;
68
+
69
+ // helpers.ts
65
70
  var shouldUseYarn = () => {
66
71
  const { hasMinYarnPnp, hasMaxYarnPnp, yarnVersion } = checkYarnVersion();
67
72
  if (!hasMinYarnPnp) {
@@ -98,6 +103,20 @@ var shouldUsePnpm = () => {
98
103
  }
99
104
  return true;
100
105
  };
106
+ var shouldUseBun = () => {
107
+ const { hasMinBun, bunVersion } = checkBunVersion();
108
+ if (!hasMinBun) {
109
+ console.log(
110
+ import_picocolors.default.yellow(
111
+ `You are using bun version ${import_picocolors.default.bold(
112
+ bunVersion
113
+ )} which is not supported yet. To use bun, install v1.0.0 or higher. See https://bun.sh for instructions on how to install.`
114
+ )
115
+ );
116
+ return false;
117
+ }
118
+ return true;
119
+ };
101
120
  var checkThatNpmCanReadCwd = () => {
102
121
  const cwd = process.cwd();
103
122
  let childOutput = null;
@@ -153,7 +172,7 @@ var checkPnpmVersion = () => {
153
172
  let hasMinPnpm = false;
154
173
  let pnpmVersion = null;
155
174
  try {
156
- pnpmVersion = (0, import_child_process.execSync)("pnpm --version").toString().trim();
175
+ pnpmVersion = (0, import_child_process.execFileSync)(resolveExecutable("pnpm"), ["--version"]).toString().trim();
157
176
  if (import_semver.default.valid(pnpmVersion)) {
158
177
  hasMinPnpm = import_semver.default.gte(pnpmVersion, minPnpm);
159
178
  } else {
@@ -166,6 +185,19 @@ var checkPnpmVersion = () => {
166
185
  }
167
186
  return { hasMinPnpm, pnpmVersion };
168
187
  };
188
+ var checkBunVersion = () => {
189
+ const minBun = "1.0.0";
190
+ let hasMinBun = false;
191
+ let bunVersion = null;
192
+ try {
193
+ bunVersion = (0, import_child_process.execFileSync)(resolveExecutable("bun"), ["--version"]).toString().trim();
194
+ if (import_semver.default.valid(bunVersion)) {
195
+ hasMinBun = import_semver.default.gte(bunVersion, minBun);
196
+ }
197
+ } catch {
198
+ }
199
+ return { hasMinBun, bunVersion };
200
+ };
169
201
  var checkYarnVersion = () => {
170
202
  const minYarnPnp = "1.12.0";
171
203
  const maxYarnPnp = "2.0.0";
@@ -173,7 +205,7 @@ var checkYarnVersion = () => {
173
205
  let hasMaxYarnPnp = false;
174
206
  let yarnVersion = null;
175
207
  try {
176
- yarnVersion = (0, import_child_process.execSync)("yarnpkg --version").toString().trim();
208
+ yarnVersion = (0, import_child_process.execFileSync)(resolveExecutable("yarnpkg"), ["--version"]).toString().trim();
177
209
  if (import_semver.default.valid(yarnVersion)) {
178
210
  hasMinYarnPnp = import_semver.default.gte(yarnVersion, minYarnPnp);
179
211
  hasMaxYarnPnp = import_semver.default.lt(yarnVersion, maxYarnPnp);
@@ -199,7 +231,7 @@ var checkNpmVersion = () => {
199
231
  let hasMinNpm = false;
200
232
  let npmVersion = null;
201
233
  try {
202
- npmVersion = (0, import_child_process.execSync)("npm --version").toString().trim();
234
+ npmVersion = (0, import_child_process.execFileSync)(resolveExecutable("npm"), ["--version"]).toString().trim();
203
235
  hasMinNpm = import_semver.default.gte(npmVersion, "6.0.0");
204
236
  } catch {
205
237
  }
@@ -213,7 +245,11 @@ var getProxy = () => {
213
245
  return process.env.HTTPS_PROXY;
214
246
  }
215
247
  try {
216
- const httpsProxy = (0, import_child_process.execSync)("npm config get https-proxy").toString().trim();
248
+ const httpsProxy = (0, import_child_process.execFileSync)(resolveExecutable("npm"), [
249
+ "config",
250
+ "get",
251
+ "https-proxy"
252
+ ]).toString().trim();
217
253
  return httpsProxy !== "null" ? httpsProxy : void 0;
218
254
  } catch {
219
255
  }
@@ -245,6 +281,8 @@ var import_lodash = __toESM(require("lodash.merge"), 1);
245
281
  var import_fs2 = __toESM(require("fs"), 1);
246
282
  var import_os2 = __toESM(require("os"), 1);
247
283
  var import_path2 = __toESM(require("path"), 1);
284
+ var import_url2 = require("url");
285
+ var import_debug2 = __toESM(require("debug"), 1);
248
286
 
249
287
  // git.ts
250
288
  var import_os = __toESM(require("os"), 1);
@@ -254,6 +292,35 @@ var import_debug = __toESM(require("debug"), 1);
254
292
  var import_simple_git = require("simple-git");
255
293
  var fse = __toESM(require("fs-extra"), 1);
256
294
  var log = (0, import_debug.default)("cna:git");
295
+ var formatRepositoryDownloadError = (error, url) => {
296
+ const message = error instanceof Error ? error.message : String(error);
297
+ if (/not found|404|repository not found/i.test(message)) {
298
+ return [
299
+ `Error: Could not fetch template from '${url}'.`,
300
+ " \u2192 The URL returned HTTP 404 or the repository was not found. Please verify the URL is correct.",
301
+ " \u2192 Run 'npx create-awesome-node-app --list-templates' to see available templates."
302
+ ].join("\n");
303
+ }
304
+ if (/403|authentication|permission denied|access denied/i.test(message)) {
305
+ return [
306
+ `Error: Could not fetch template from '${url}'.`,
307
+ " \u2192 Access denied (HTTP 403). Check that the repository is public or you have access.",
308
+ " \u2192 Run 'npx create-awesome-node-app --list-templates' to see available templates."
309
+ ].join("\n");
310
+ }
311
+ if (/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|network/i.test(message)) {
312
+ return [
313
+ `Error: Could not fetch template from '${url}'.`,
314
+ " \u2192 Could not reach the repository. Please check your internet connection.",
315
+ " \u2192 Run 'npx create-awesome-node-app --list-templates' to see available templates."
316
+ ].join("\n");
317
+ }
318
+ return [
319
+ `Error: Could not fetch template from '${url}'.`,
320
+ ` \u2192 ${message}`,
321
+ " \u2192 Run 'npx create-awesome-node-app --list-templates' to see available templates."
322
+ ].join("\n");
323
+ };
257
324
  var filterGit = (src) => {
258
325
  return !/(\\|\/)\.git\b/.test(src);
259
326
  };
@@ -268,6 +335,7 @@ var downloadRepository = async ({
268
335
  cacheDir: optsCacheDir
269
336
  }) => {
270
337
  const absoluteTarget = import_path.default.isAbsolute(target) ? target : import_path.default.resolve(target);
338
+ const targetExistedBefore = import_fs.default.existsSync(absoluteTarget);
271
339
  const isGithub = /^[^/]+\/[^/]+$/.test(url);
272
340
  const gitUrl = isGithub ? `https://github.com/${url}` : url;
273
341
  const id = targetId || Buffer.from(`${gitUrl}@${branch}`).toString("base64");
@@ -318,7 +386,15 @@ var downloadRepository = async ({
318
386
  });
319
387
  completedTargetIds.set(id, true);
320
388
  } catch (error) {
321
- console.error("Error during repository download:", error);
389
+ if (!targetExistedBefore && import_fs.default.existsSync(absoluteTarget)) {
390
+ try {
391
+ fse.removeSync(absoluteTarget);
392
+ log("Cleaned up partially created directory: %s", absoluteTarget);
393
+ } catch (cleanupErr) {
394
+ log("Failed to clean up directory: %s", cleanupErr);
395
+ }
396
+ }
397
+ throw new Error(formatRepositoryDownloadError(error, gitUrl));
322
398
  } finally {
323
399
  gitOperationMap.delete(id);
324
400
  }
@@ -328,6 +404,9 @@ var downloadRepository = async ({
328
404
  };
329
405
 
330
406
  // paths.ts
407
+ var import_meta = {};
408
+ var log2 = (0, import_debug2.default)("cna:paths");
409
+ var moduleDir = typeof __dirname !== "undefined" ? __dirname : import_path2.default.dirname((0, import_url2.fileURLToPath)(import_meta.url));
331
410
  var solveValuesFromTemplateOrExtensionUrl = (templateOrExtension) => {
332
411
  const url = new URL(templateOrExtension);
333
412
  const ignorePackage = url.searchParams.get("ignorePackage") === "true";
@@ -381,30 +460,23 @@ var solveRepositoryPath = async ({
381
460
  if (process.env.CNA_SKIP_GIT === "1") {
382
461
  return { dir: target, subdir };
383
462
  }
384
- try {
385
- await downloadRepository({
386
- url,
387
- branch: branch || "",
388
- target,
389
- targetId
390
- });
391
- } catch {
392
- }
463
+ await downloadRepository({
464
+ url,
465
+ branch: branch || "",
466
+ target,
467
+ targetId
468
+ });
393
469
  return { dir: target, subdir };
394
470
  };
395
471
  var solveTemplateOrExtensionPath = async (templateOrExtension) => {
472
+ let parsed;
396
473
  try {
397
- const { url, branch, subdir, protocol, pathname, ignorePackage } = solveValuesFromTemplateOrExtensionUrl(templateOrExtension);
398
- if (protocol === "file:") {
399
- const baseDir = pathname;
400
- return { dir: baseDir, subdir, ignorePackage };
401
- }
402
- const gitData = await solveRepositoryPath({ url, branch, subdir });
403
- return { dir: gitData.dir, subdir: gitData.subdir, ignorePackage };
474
+ parsed = solveValuesFromTemplateOrExtensionUrl(templateOrExtension);
404
475
  } catch {
476
+ log2("Falling back to legacy template path for: %s", templateOrExtension);
405
477
  return {
406
478
  dir: import_path2.default.resolve(
407
- __dirname,
479
+ moduleDir,
408
480
  "..",
409
481
  "templatesOrExtensions",
410
482
  templateOrExtension
@@ -413,6 +485,12 @@ var solveTemplateOrExtensionPath = async (templateOrExtension) => {
413
485
  ignorePackage: void 0
414
486
  };
415
487
  }
488
+ const { url, branch, subdir, protocol, pathname, ignorePackage } = parsed;
489
+ if (protocol === "file:") {
490
+ return { dir: pathname, subdir, ignorePackage };
491
+ }
492
+ const gitData = await solveRepositoryPath({ url, branch, subdir });
493
+ return { dir: gitData.dir, subdir: gitData.subdir, ignorePackage };
416
494
  };
417
495
  var getPackagePath = async (templateOrExtension, name = "package", ignorePackage = false) => {
418
496
  const {
@@ -487,10 +565,10 @@ var loadPackages = async ({
487
565
  const setup = await Promise.all(
488
566
  templatesOrExtensions.map(async ({ url: templateOrExtension }) => {
489
567
  try {
490
- const template = requireIfExists(
568
+ const template2 = requireIfExists(
491
569
  await getPackagePath(templateOrExtension, "template.json")
492
570
  );
493
- return template.package || {};
571
+ return template2.package || {};
494
572
  } catch {
495
573
  return {};
496
574
  }
@@ -543,12 +621,13 @@ var loadPackages = async ({
543
621
  };
544
622
 
545
623
  // loaders.ts
546
- var import_underscore = __toESM(require("underscore"), 1);
547
624
  var import_fs4 = __toESM(require("fs"), 1);
548
625
  var import_picocolors2 = __toESM(require("picocolors"), 1);
549
626
  var import_readdirp = require("readdirp");
550
627
  var import_path3 = require("path");
551
628
  var import_util = require("util");
629
+ var import_lodash2 = __toESM(require("lodash"), 1);
630
+ var { template } = import_lodash2.default;
552
631
  var writeFileAsync = (0, import_util.promisify)(import_fs4.default.writeFile);
553
632
  var copyFileAsync = (0, import_util.promisify)(import_fs4.default.copyFile);
554
633
  var SRC_PATH_PATTERN = "[src]/";
@@ -577,6 +656,11 @@ var batchedCopyFiles = async (operations) => {
577
656
  try {
578
657
  makeDirectory((0, import_path3.dirname)(operation.dest));
579
658
  await copyFileAsync(operation.src, operation.dest);
659
+ try {
660
+ const srcStat = await (0, import_util.promisify)(import_fs4.default.stat)(operation.src);
661
+ await (0, import_util.promisify)(import_fs4.default.chmod)(operation.dest, srcStat.mode);
662
+ } catch {
663
+ }
580
664
  if (operation.verbose) {
581
665
  console.log(
582
666
  import_picocolors2.default.green(
@@ -646,7 +730,7 @@ var batchedAppendFiles = async (operations) => {
646
730
  var copyLoader = ({ root, templateDir, verbose, srcDir }) => async ({ path: path5 }) => {
647
731
  const operations = [];
648
732
  try {
649
- const newPath = path5.replace(/.if-(npm|yarn|pnpm)$/, "").replace(SRC_PATH_PATTERN, getSrcDirPattern(srcDir));
733
+ const newPath = path5.replace(/.if-(npm|yarn|pnpm|bun)$/, "").replace(SRC_PATH_PATTERN, getSrcDirPattern(srcDir));
650
734
  operations.push({
651
735
  src: `${templateDir}/${path5}`,
652
736
  dest: `${root}/${newPath}`,
@@ -663,7 +747,7 @@ var copyLoader = ({ root, templateDir, verbose, srcDir }) => async ({ path: path
663
747
  var appendLoader = ({ root, templateDir, verbose, srcDir }) => async ({ path: path5 }) => {
664
748
  const operations = [];
665
749
  try {
666
- const newPath = path5.replace(/.append$/, "").replace(/.if-(npm|yarn|pnpm)$/, "").replace(SRC_PATH_PATTERN, getSrcDirPattern(srcDir));
750
+ const newPath = path5.replace(/.append$/, "").replace(/.if-(npm|yarn|pnpm|bun)$/, "").replace(SRC_PATH_PATTERN, getSrcDirPattern(srcDir));
667
751
  operations.push({
668
752
  src: `${templateDir}/${path5}`,
669
753
  dest: `${root}/${newPath}`,
@@ -694,8 +778,8 @@ var templateLoader = ({
694
778
  const filePath = `${templateDir}/${path5}`;
695
779
  const file = await (0, import_util.promisify)(import_fs4.default.readFile)(filePath, "utf8");
696
780
  const fileMode = (await (0, import_util.promisify)(import_fs4.default.stat)(filePath)).mode;
697
- const newFile = import_underscore.default.template(file);
698
- const newPath = path5.replace(/.template$/, "").replace(/.append$/, "").replace(/.if-(npm|yarn|pnpm)$/, "").replace(SRC_PATH_PATTERN, getSrcDirPattern(srcDir));
781
+ const newFile = template(file);
782
+ const newPath = path5.replace(/.template$/, "").replace(/.append$/, "").replace(/.if-(npm|yarn|pnpm|bun)$/, "").replace(SRC_PATH_PATTERN, getSrcDirPattern(srcDir));
699
783
  operations.push({
700
784
  path: `${root}/${newPath}`,
701
785
  content: newFile({
@@ -725,6 +809,7 @@ var fileLoader = ({
725
809
  verbose,
726
810
  useYarn,
727
811
  usePnpm,
812
+ useBun,
728
813
  srcDir = DEFAULT_SRC_PATH,
729
814
  runCommand,
730
815
  installCommand,
@@ -747,6 +832,7 @@ var fileLoader = ({
747
832
  verbose,
748
833
  useYarn: !!useYarn,
749
834
  usePnpm: !!usePnpm,
835
+ useBun: !!useBun,
750
836
  mode,
751
837
  srcDir,
752
838
  runCommand,
@@ -770,6 +856,7 @@ var loadFiles = async ({
770
856
  verbose,
771
857
  useYarn = false,
772
858
  usePnpm = false,
859
+ useBun = false,
773
860
  srcDir = DEFAULT_SRC_PATH,
774
861
  runCommand,
775
862
  installCommand,
@@ -800,7 +887,7 @@ var loadFiles = async ({
800
887
  /\byarn\.lock$/,
801
888
  /\bpnpm-lock\.yaml$/
802
889
  ];
803
- const skipManager = usePnpm ? [/\.if-npm\./, /\.if-yarn\./] : useYarn ? [/\.if-npm\./, /\.if-pnpm\./] : [/\.if-yarn\./, /\.if-pnpm\./];
890
+ const skipManager = usePnpm ? [/\.if-npm\./, /\.if-yarn\./, /\.if-bun\./] : useYarn ? [/\.if-npm\./, /\.if-pnpm\./, /\.if-bun\./] : useBun ? [/\.if-yarn\./, /\.if-pnpm\./] : [/\.if-yarn\./, /\.if-pnpm\./, /\.if-bun\./];
804
891
  const shouldSkip = (p) => [...skipGlobs, ...skipManager].some((rgx) => rgx.test(p));
805
892
  for await (const entry of (0, import_readdirp.readdirp)(templateDir, {
806
893
  type: "files",
@@ -820,6 +907,7 @@ var loadFiles = async ({
820
907
  verbose,
821
908
  useYarn,
822
909
  usePnpm,
910
+ useBun,
823
911
  srcDir,
824
912
  runCommand,
825
913
  installCommand,
@@ -855,7 +943,8 @@ var loadFiles = async ({
855
943
  };
856
944
 
857
945
  // installer.ts
858
- var install = async (root, useYarn = false, usePnpm = false, dependencies = [], verbose = false, isOnline = true, isDevDependencies = false) => {
946
+ var { isEmpty } = import_lodash3.default;
947
+ var install = async (root, useYarn = false, usePnpm = false, useBun = false, dependencies = [], verbose = false, isOnline = true, isDevDependencies = false) => {
859
948
  let command;
860
949
  let args;
861
950
  if (useYarn) {
@@ -884,6 +973,17 @@ var install = async (root, useYarn = false, usePnpm = false, dependencies = [],
884
973
  args.push("--save");
885
974
  }
886
975
  args.push(...dependencies);
976
+ } else if (useBun) {
977
+ command = "bun";
978
+ if (dependencies.length > 0) {
979
+ args = ["add"];
980
+ if (isDevDependencies) {
981
+ args.push("--dev");
982
+ }
983
+ args.push(...dependencies);
984
+ } else {
985
+ args = ["install"];
986
+ }
887
987
  } else {
888
988
  command = "npm";
889
989
  args = ["install", "--loglevel", "error"];
@@ -898,7 +998,7 @@ var install = async (root, useYarn = false, usePnpm = false, dependencies = [],
898
998
  args.push("--verbose");
899
999
  }
900
1000
  try {
901
- (0, import_child_process2.execSync)(`${command} ${args.join(" ")}`, {
1001
+ (0, import_child_process2.execFileSync)(resolveExecutable(command), args, {
902
1002
  cwd: root,
903
1003
  stdio: "inherit"
904
1004
  });
@@ -907,8 +1007,9 @@ var install = async (root, useYarn = false, usePnpm = false, dependencies = [],
907
1007
  }
908
1008
  };
909
1009
  var runCommandInProjectDir = async (root, command, args = [], successMessage = "Operation completed successfully.", errorMessage = "Operation failed.") => {
1010
+ const [executable, ...baseArgs] = command === "npm run" ? ["npm", "run"] : command === "pnpm run" ? ["pnpm", "run"] : command === "bun run" ? ["bun", "run"] : [command];
910
1011
  try {
911
- (0, import_child_process2.execSync)(`${command} ${args.join(" ")}`, {
1012
+ (0, import_child_process2.execFileSync)(resolveExecutable(executable), [...baseArgs, ...args], {
912
1013
  cwd: root,
913
1014
  stdio: "ignore"
914
1015
  });
@@ -936,6 +1037,7 @@ var run = async ({
936
1037
  verbose = false,
937
1038
  useYarn = false,
938
1039
  usePnpm = false,
1040
+ useBun = false,
939
1041
  templatesOrExtensions = [],
940
1042
  dependencies = [],
941
1043
  devDependencies = [],
@@ -945,7 +1047,7 @@ var run = async ({
945
1047
  ...customOptions
946
1048
  }) => {
947
1049
  const isOnline = useYarn ? await checkIfOnline(useYarn) : true;
948
- if (import_underscore2.default.isEmpty(templatesOrExtensions)) {
1050
+ if (isEmpty(templatesOrExtensions)) {
949
1051
  console.log();
950
1052
  console.log(
951
1053
  import_picocolors3.default.yellow(
@@ -965,6 +1067,7 @@ var run = async ({
965
1067
  verbose,
966
1068
  useYarn,
967
1069
  usePnpm,
1070
+ useBun,
968
1071
  runCommand,
969
1072
  installCommand,
970
1073
  ...customOptions
@@ -982,6 +1085,7 @@ var run = async ({
982
1085
  root,
983
1086
  useYarn,
984
1087
  usePnpm,
1088
+ useBun,
985
1089
  dependencies,
986
1090
  verbose,
987
1091
  isOnline,
@@ -995,6 +1099,7 @@ var run = async ({
995
1099
  root,
996
1100
  useYarn,
997
1101
  usePnpm,
1102
+ useBun,
998
1103
  devDependencies,
999
1104
  verbose,
1000
1105
  isOnline,
@@ -1119,13 +1224,15 @@ var createApp = async ({
1119
1224
  console.log();
1120
1225
  const useYarn = customOptions.packageManager === "yarn" && shouldUseYarn();
1121
1226
  const usePnpm = customOptions.packageManager === "pnpm" && shouldUsePnpm();
1122
- const runCommand = useYarn ? "yarn" : usePnpm ? "pnpm run" : "npm run";
1123
- const installCommand = useYarn ? "yarn" : usePnpm ? "pnpm install" : "npm install";
1227
+ const useBun = customOptions.packageManager === "bun" && shouldUseBun();
1228
+ const runCommand = useYarn ? "yarn" : usePnpm ? "pnpm run" : useBun ? "bun run" : "npm run";
1229
+ const installCommand = useYarn ? "yarn" : usePnpm ? "pnpm install" : useBun ? "bun install" : "npm install";
1124
1230
  const { packageJson, dependencies, devDependencies } = await loadPackages({
1125
1231
  templatesOrExtensions,
1126
1232
  appName,
1127
1233
  usePnpm,
1128
1234
  useYarn,
1235
+ useBun,
1129
1236
  runCommand,
1130
1237
  ignorePackage
1131
1238
  });
@@ -1135,7 +1242,7 @@ var createApp = async ({
1135
1242
  );
1136
1243
  const originalDirectory = process.cwd();
1137
1244
  process.chdir(root);
1138
- if (!useYarn && !checkThatNpmCanReadCwd()) {
1245
+ if (!useYarn && !useBun && !checkThatNpmCanReadCwd()) {
1139
1246
  process.exit(1);
1140
1247
  }
1141
1248
  if (!import_semver2.default.satisfies(process.version, ">=18.0.0")) {
@@ -1148,7 +1255,7 @@ Please update to Node 18 or higher for a better, fully supported experience.
1148
1255
  )
1149
1256
  );
1150
1257
  }
1151
- if (!useYarn) {
1258
+ if (!useYarn && !useBun) {
1152
1259
  const npmInfo = checkNpmVersion();
1153
1260
  if (!npmInfo.hasMinNpm) {
1154
1261
  if (npmInfo.npmVersion) {
@@ -1166,7 +1273,11 @@ Please update to npm 3 or higher for a better, fully supported experience.
1166
1273
  if (useYarn) {
1167
1274
  let yarnUsesDefaultRegistry = true;
1168
1275
  try {
1169
- yarnUsesDefaultRegistry = (0, import_child_process2.execSync)("yarnpkg config get registry").toString().trim() === "https://registry.yarnpkg.com";
1276
+ yarnUsesDefaultRegistry = (0, import_child_process2.execFileSync)(resolveExecutable("yarnpkg"), [
1277
+ "config",
1278
+ "get",
1279
+ "registry"
1280
+ ]).toString().trim() === "https://registry.yarnpkg.com";
1170
1281
  } catch {
1171
1282
  }
1172
1283
  if (false) {
@@ -1184,6 +1295,7 @@ Please update to npm 3 or higher for a better, fully supported experience.
1184
1295
  verbose,
1185
1296
  useYarn,
1186
1297
  usePnpm,
1298
+ useBun,
1187
1299
  templatesOrExtensions,
1188
1300
  dependencies,
1189
1301
  devDependencies,
@@ -1238,7 +1350,11 @@ var checkForLatestVersion = async (packageName) => {
1238
1350
  return null;
1239
1351
  } catch {
1240
1352
  try {
1241
- return (0, import_child_process3.execSync)(`npm view ${packageName} version`).toString().trim();
1353
+ return (0, import_child_process3.execFileSync)(resolveExecutable("npm"), [
1354
+ "view",
1355
+ packageName,
1356
+ "version"
1357
+ ]).toString().trim();
1242
1358
  } catch {
1243
1359
  }
1244
1360
  }
@@ -1249,7 +1365,7 @@ var printEnvInfo = async () => {
1249
1365
  const info = await import_envinfo.default.run(
1250
1366
  {
1251
1367
  System: ["OS", "CPU", "Memory", "Shell"],
1252
- Binaries: ["Node", "npm", "pnpm", "Yarn", "Watchman"],
1368
+ Binaries: ["Node", "npm", "pnpm", "Yarn", "Bun", "Watchman"],
1253
1369
  Browsers: ["Chrome", "Edge", "Internet Explorer", "Firefox", "Safari"]
1254
1370
  },
1255
1371
  {