@create-node-app/core 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,7 @@ 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_debug2 = __toESM(require("debug"), 1);
248
285
 
249
286
  // git.ts
250
287
  var import_os = __toESM(require("os"), 1);
@@ -254,6 +291,35 @@ var import_debug = __toESM(require("debug"), 1);
254
291
  var import_simple_git = require("simple-git");
255
292
  var fse = __toESM(require("fs-extra"), 1);
256
293
  var log = (0, import_debug.default)("cna:git");
294
+ var formatRepositoryDownloadError = (error, url) => {
295
+ const message = error instanceof Error ? error.message : String(error);
296
+ if (/not found|404|repository not found/i.test(message)) {
297
+ return [
298
+ `Error: Could not fetch template from '${url}'.`,
299
+ " \u2192 The URL returned HTTP 404 or the repository was not found. Please verify the URL is correct.",
300
+ " \u2192 Run 'npx create-awesome-node-app --list-templates' to see available templates."
301
+ ].join("\n");
302
+ }
303
+ if (/403|authentication|permission denied|access denied/i.test(message)) {
304
+ return [
305
+ `Error: Could not fetch template from '${url}'.`,
306
+ " \u2192 Access denied (HTTP 403). Check that the repository is public or you have access.",
307
+ " \u2192 Run 'npx create-awesome-node-app --list-templates' to see available templates."
308
+ ].join("\n");
309
+ }
310
+ if (/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|network/i.test(message)) {
311
+ return [
312
+ `Error: Could not fetch template from '${url}'.`,
313
+ " \u2192 Could not reach the repository. Please check your internet connection.",
314
+ " \u2192 Run 'npx create-awesome-node-app --list-templates' to see available templates."
315
+ ].join("\n");
316
+ }
317
+ return [
318
+ `Error: Could not fetch template from '${url}'.`,
319
+ ` \u2192 ${message}`,
320
+ " \u2192 Run 'npx create-awesome-node-app --list-templates' to see available templates."
321
+ ].join("\n");
322
+ };
257
323
  var filterGit = (src) => {
258
324
  return !/(\\|\/)\.git\b/.test(src);
259
325
  };
@@ -268,6 +334,7 @@ var downloadRepository = async ({
268
334
  cacheDir: optsCacheDir
269
335
  }) => {
270
336
  const absoluteTarget = import_path.default.isAbsolute(target) ? target : import_path.default.resolve(target);
337
+ const targetExistedBefore = import_fs.default.existsSync(absoluteTarget);
271
338
  const isGithub = /^[^/]+\/[^/]+$/.test(url);
272
339
  const gitUrl = isGithub ? `https://github.com/${url}` : url;
273
340
  const id = targetId || Buffer.from(`${gitUrl}@${branch}`).toString("base64");
@@ -318,7 +385,15 @@ var downloadRepository = async ({
318
385
  });
319
386
  completedTargetIds.set(id, true);
320
387
  } catch (error) {
321
- console.error("Error during repository download:", error);
388
+ if (!targetExistedBefore && import_fs.default.existsSync(absoluteTarget)) {
389
+ try {
390
+ fse.removeSync(absoluteTarget);
391
+ log("Cleaned up partially created directory: %s", absoluteTarget);
392
+ } catch (cleanupErr) {
393
+ log("Failed to clean up directory: %s", cleanupErr);
394
+ }
395
+ }
396
+ throw new Error(formatRepositoryDownloadError(error, gitUrl));
322
397
  } finally {
323
398
  gitOperationMap.delete(id);
324
399
  }
@@ -328,6 +403,8 @@ var downloadRepository = async ({
328
403
  };
329
404
 
330
405
  // paths.ts
406
+ var log2 = (0, import_debug2.default)("cna:paths");
407
+ var moduleDir = __dirname;
331
408
  var solveValuesFromTemplateOrExtensionUrl = (templateOrExtension) => {
332
409
  const url = new URL(templateOrExtension);
333
410
  const ignorePackage = url.searchParams.get("ignorePackage") === "true";
@@ -381,30 +458,23 @@ var solveRepositoryPath = async ({
381
458
  if (process.env.CNA_SKIP_GIT === "1") {
382
459
  return { dir: target, subdir };
383
460
  }
384
- try {
385
- await downloadRepository({
386
- url,
387
- branch: branch || "",
388
- target,
389
- targetId
390
- });
391
- } catch {
392
- }
461
+ await downloadRepository({
462
+ url,
463
+ branch: branch || "",
464
+ target,
465
+ targetId
466
+ });
393
467
  return { dir: target, subdir };
394
468
  };
395
469
  var solveTemplateOrExtensionPath = async (templateOrExtension) => {
470
+ let parsed;
396
471
  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 };
472
+ parsed = solveValuesFromTemplateOrExtensionUrl(templateOrExtension);
404
473
  } catch {
474
+ log2("Falling back to legacy template path for: %s", templateOrExtension);
405
475
  return {
406
476
  dir: import_path2.default.resolve(
407
- __dirname,
477
+ moduleDir,
408
478
  "..",
409
479
  "templatesOrExtensions",
410
480
  templateOrExtension
@@ -413,6 +483,12 @@ var solveTemplateOrExtensionPath = async (templateOrExtension) => {
413
483
  ignorePackage: void 0
414
484
  };
415
485
  }
486
+ const { url, branch, subdir, protocol, pathname, ignorePackage } = parsed;
487
+ if (protocol === "file:") {
488
+ return { dir: pathname, subdir, ignorePackage };
489
+ }
490
+ const gitData = await solveRepositoryPath({ url, branch, subdir });
491
+ return { dir: gitData.dir, subdir: gitData.subdir, ignorePackage };
416
492
  };
417
493
  var getPackagePath = async (templateOrExtension, name = "package", ignorePackage = false) => {
418
494
  const {
@@ -487,10 +563,10 @@ var loadPackages = async ({
487
563
  const setup = await Promise.all(
488
564
  templatesOrExtensions.map(async ({ url: templateOrExtension }) => {
489
565
  try {
490
- const template = requireIfExists(
566
+ const template2 = requireIfExists(
491
567
  await getPackagePath(templateOrExtension, "template.json")
492
568
  );
493
- return template.package || {};
569
+ return template2.package || {};
494
570
  } catch {
495
571
  return {};
496
572
  }
@@ -543,12 +619,13 @@ var loadPackages = async ({
543
619
  };
544
620
 
545
621
  // loaders.ts
546
- var import_underscore = __toESM(require("underscore"), 1);
547
622
  var import_fs4 = __toESM(require("fs"), 1);
548
623
  var import_picocolors2 = __toESM(require("picocolors"), 1);
549
624
  var import_readdirp = require("readdirp");
550
625
  var import_path3 = require("path");
551
626
  var import_util = require("util");
627
+ var import_lodash2 = __toESM(require("lodash"), 1);
628
+ var { template } = import_lodash2.default;
552
629
  var writeFileAsync = (0, import_util.promisify)(import_fs4.default.writeFile);
553
630
  var copyFileAsync = (0, import_util.promisify)(import_fs4.default.copyFile);
554
631
  var SRC_PATH_PATTERN = "[src]/";
@@ -577,6 +654,11 @@ var batchedCopyFiles = async (operations) => {
577
654
  try {
578
655
  makeDirectory((0, import_path3.dirname)(operation.dest));
579
656
  await copyFileAsync(operation.src, operation.dest);
657
+ try {
658
+ const srcStat = await (0, import_util.promisify)(import_fs4.default.stat)(operation.src);
659
+ await (0, import_util.promisify)(import_fs4.default.chmod)(operation.dest, srcStat.mode);
660
+ } catch {
661
+ }
580
662
  if (operation.verbose) {
581
663
  console.log(
582
664
  import_picocolors2.default.green(
@@ -646,7 +728,7 @@ var batchedAppendFiles = async (operations) => {
646
728
  var copyLoader = ({ root, templateDir, verbose, srcDir }) => async ({ path: path5 }) => {
647
729
  const operations = [];
648
730
  try {
649
- const newPath = path5.replace(/.if-(npm|yarn|pnpm)$/, "").replace(SRC_PATH_PATTERN, getSrcDirPattern(srcDir));
731
+ const newPath = path5.replace(/.if-(npm|yarn|pnpm|bun)$/, "").replace(SRC_PATH_PATTERN, getSrcDirPattern(srcDir));
650
732
  operations.push({
651
733
  src: `${templateDir}/${path5}`,
652
734
  dest: `${root}/${newPath}`,
@@ -663,7 +745,7 @@ var copyLoader = ({ root, templateDir, verbose, srcDir }) => async ({ path: path
663
745
  var appendLoader = ({ root, templateDir, verbose, srcDir }) => async ({ path: path5 }) => {
664
746
  const operations = [];
665
747
  try {
666
- const newPath = path5.replace(/.append$/, "").replace(/.if-(npm|yarn|pnpm)$/, "").replace(SRC_PATH_PATTERN, getSrcDirPattern(srcDir));
748
+ const newPath = path5.replace(/.append$/, "").replace(/.if-(npm|yarn|pnpm|bun)$/, "").replace(SRC_PATH_PATTERN, getSrcDirPattern(srcDir));
667
749
  operations.push({
668
750
  src: `${templateDir}/${path5}`,
669
751
  dest: `${root}/${newPath}`,
@@ -694,8 +776,8 @@ var templateLoader = ({
694
776
  const filePath = `${templateDir}/${path5}`;
695
777
  const file = await (0, import_util.promisify)(import_fs4.default.readFile)(filePath, "utf8");
696
778
  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));
779
+ const newFile = template(file);
780
+ const newPath = path5.replace(/.template$/, "").replace(/.append$/, "").replace(/.if-(npm|yarn|pnpm|bun)$/, "").replace(SRC_PATH_PATTERN, getSrcDirPattern(srcDir));
699
781
  operations.push({
700
782
  path: `${root}/${newPath}`,
701
783
  content: newFile({
@@ -725,6 +807,7 @@ var fileLoader = ({
725
807
  verbose,
726
808
  useYarn,
727
809
  usePnpm,
810
+ useBun,
728
811
  srcDir = DEFAULT_SRC_PATH,
729
812
  runCommand,
730
813
  installCommand,
@@ -747,6 +830,7 @@ var fileLoader = ({
747
830
  verbose,
748
831
  useYarn: !!useYarn,
749
832
  usePnpm: !!usePnpm,
833
+ useBun: !!useBun,
750
834
  mode,
751
835
  srcDir,
752
836
  runCommand,
@@ -770,6 +854,7 @@ var loadFiles = async ({
770
854
  verbose,
771
855
  useYarn = false,
772
856
  usePnpm = false,
857
+ useBun = false,
773
858
  srcDir = DEFAULT_SRC_PATH,
774
859
  runCommand,
775
860
  installCommand,
@@ -800,7 +885,7 @@ var loadFiles = async ({
800
885
  /\byarn\.lock$/,
801
886
  /\bpnpm-lock\.yaml$/
802
887
  ];
803
- const skipManager = usePnpm ? [/\.if-npm\./, /\.if-yarn\./] : useYarn ? [/\.if-npm\./, /\.if-pnpm\./] : [/\.if-yarn\./, /\.if-pnpm\./];
888
+ 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
889
  const shouldSkip = (p) => [...skipGlobs, ...skipManager].some((rgx) => rgx.test(p));
805
890
  for await (const entry of (0, import_readdirp.readdirp)(templateDir, {
806
891
  type: "files",
@@ -820,6 +905,7 @@ var loadFiles = async ({
820
905
  verbose,
821
906
  useYarn,
822
907
  usePnpm,
908
+ useBun,
823
909
  srcDir,
824
910
  runCommand,
825
911
  installCommand,
@@ -855,7 +941,8 @@ var loadFiles = async ({
855
941
  };
856
942
 
857
943
  // installer.ts
858
- var install = async (root, useYarn = false, usePnpm = false, dependencies = [], verbose = false, isOnline = true, isDevDependencies = false) => {
944
+ var { isEmpty } = import_lodash3.default;
945
+ var install = async (root, useYarn = false, usePnpm = false, useBun = false, dependencies = [], verbose = false, isOnline = true, isDevDependencies = false) => {
859
946
  let command;
860
947
  let args;
861
948
  if (useYarn) {
@@ -884,6 +971,17 @@ var install = async (root, useYarn = false, usePnpm = false, dependencies = [],
884
971
  args.push("--save");
885
972
  }
886
973
  args.push(...dependencies);
974
+ } else if (useBun) {
975
+ command = "bun";
976
+ if (dependencies.length > 0) {
977
+ args = ["add"];
978
+ if (isDevDependencies) {
979
+ args.push("--dev");
980
+ }
981
+ args.push(...dependencies);
982
+ } else {
983
+ args = ["install"];
984
+ }
887
985
  } else {
888
986
  command = "npm";
889
987
  args = ["install", "--loglevel", "error"];
@@ -898,7 +996,7 @@ var install = async (root, useYarn = false, usePnpm = false, dependencies = [],
898
996
  args.push("--verbose");
899
997
  }
900
998
  try {
901
- (0, import_child_process2.execSync)(`${command} ${args.join(" ")}`, {
999
+ (0, import_child_process2.execFileSync)(resolveExecutable(command), args, {
902
1000
  cwd: root,
903
1001
  stdio: "inherit"
904
1002
  });
@@ -907,8 +1005,9 @@ var install = async (root, useYarn = false, usePnpm = false, dependencies = [],
907
1005
  }
908
1006
  };
909
1007
  var runCommandInProjectDir = async (root, command, args = [], successMessage = "Operation completed successfully.", errorMessage = "Operation failed.") => {
1008
+ const [executable, ...baseArgs] = command === "npm run" ? ["npm", "run"] : command === "pnpm run" ? ["pnpm", "run"] : command === "bun run" ? ["bun", "run"] : [command];
910
1009
  try {
911
- (0, import_child_process2.execSync)(`${command} ${args.join(" ")}`, {
1010
+ (0, import_child_process2.execFileSync)(resolveExecutable(executable), [...baseArgs, ...args], {
912
1011
  cwd: root,
913
1012
  stdio: "ignore"
914
1013
  });
@@ -936,6 +1035,7 @@ var run = async ({
936
1035
  verbose = false,
937
1036
  useYarn = false,
938
1037
  usePnpm = false,
1038
+ useBun = false,
939
1039
  templatesOrExtensions = [],
940
1040
  dependencies = [],
941
1041
  devDependencies = [],
@@ -945,7 +1045,7 @@ var run = async ({
945
1045
  ...customOptions
946
1046
  }) => {
947
1047
  const isOnline = useYarn ? await checkIfOnline(useYarn) : true;
948
- if (import_underscore2.default.isEmpty(templatesOrExtensions)) {
1048
+ if (isEmpty(templatesOrExtensions)) {
949
1049
  console.log();
950
1050
  console.log(
951
1051
  import_picocolors3.default.yellow(
@@ -965,6 +1065,7 @@ var run = async ({
965
1065
  verbose,
966
1066
  useYarn,
967
1067
  usePnpm,
1068
+ useBun,
968
1069
  runCommand,
969
1070
  installCommand,
970
1071
  ...customOptions
@@ -982,6 +1083,7 @@ var run = async ({
982
1083
  root,
983
1084
  useYarn,
984
1085
  usePnpm,
1086
+ useBun,
985
1087
  dependencies,
986
1088
  verbose,
987
1089
  isOnline,
@@ -995,6 +1097,7 @@ var run = async ({
995
1097
  root,
996
1098
  useYarn,
997
1099
  usePnpm,
1100
+ useBun,
998
1101
  devDependencies,
999
1102
  verbose,
1000
1103
  isOnline,
@@ -1119,13 +1222,15 @@ var createApp = async ({
1119
1222
  console.log();
1120
1223
  const useYarn = customOptions.packageManager === "yarn" && shouldUseYarn();
1121
1224
  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";
1225
+ const useBun = customOptions.packageManager === "bun" && shouldUseBun();
1226
+ const runCommand = useYarn ? "yarn" : usePnpm ? "pnpm run" : useBun ? "bun run" : "npm run";
1227
+ const installCommand = useYarn ? "yarn" : usePnpm ? "pnpm install" : useBun ? "bun install" : "npm install";
1124
1228
  const { packageJson, dependencies, devDependencies } = await loadPackages({
1125
1229
  templatesOrExtensions,
1126
1230
  appName,
1127
1231
  usePnpm,
1128
1232
  useYarn,
1233
+ useBun,
1129
1234
  runCommand,
1130
1235
  ignorePackage
1131
1236
  });
@@ -1135,7 +1240,7 @@ var createApp = async ({
1135
1240
  );
1136
1241
  const originalDirectory = process.cwd();
1137
1242
  process.chdir(root);
1138
- if (!useYarn && !checkThatNpmCanReadCwd()) {
1243
+ if (!useYarn && !useBun && !checkThatNpmCanReadCwd()) {
1139
1244
  process.exit(1);
1140
1245
  }
1141
1246
  if (!import_semver2.default.satisfies(process.version, ">=18.0.0")) {
@@ -1148,7 +1253,7 @@ Please update to Node 18 or higher for a better, fully supported experience.
1148
1253
  )
1149
1254
  );
1150
1255
  }
1151
- if (!useYarn) {
1256
+ if (!useYarn && !useBun) {
1152
1257
  const npmInfo = checkNpmVersion();
1153
1258
  if (!npmInfo.hasMinNpm) {
1154
1259
  if (npmInfo.npmVersion) {
@@ -1166,7 +1271,11 @@ Please update to npm 3 or higher for a better, fully supported experience.
1166
1271
  if (useYarn) {
1167
1272
  let yarnUsesDefaultRegistry = true;
1168
1273
  try {
1169
- yarnUsesDefaultRegistry = (0, import_child_process2.execSync)("yarnpkg config get registry").toString().trim() === "https://registry.yarnpkg.com";
1274
+ yarnUsesDefaultRegistry = (0, import_child_process2.execFileSync)(resolveExecutable("yarnpkg"), [
1275
+ "config",
1276
+ "get",
1277
+ "registry"
1278
+ ]).toString().trim() === "https://registry.yarnpkg.com";
1170
1279
  } catch {
1171
1280
  }
1172
1281
  if (false) {
@@ -1184,6 +1293,7 @@ Please update to npm 3 or higher for a better, fully supported experience.
1184
1293
  verbose,
1185
1294
  useYarn,
1186
1295
  usePnpm,
1296
+ useBun,
1187
1297
  templatesOrExtensions,
1188
1298
  dependencies,
1189
1299
  devDependencies,
@@ -1238,7 +1348,11 @@ var checkForLatestVersion = async (packageName) => {
1238
1348
  return null;
1239
1349
  } catch {
1240
1350
  try {
1241
- return (0, import_child_process3.execSync)(`npm view ${packageName} version`).toString().trim();
1351
+ return (0, import_child_process3.execFileSync)(resolveExecutable("npm"), [
1352
+ "view",
1353
+ packageName,
1354
+ "version"
1355
+ ]).toString().trim();
1242
1356
  } catch {
1243
1357
  }
1244
1358
  }
@@ -1249,7 +1363,7 @@ var printEnvInfo = async () => {
1249
1363
  const info = await import_envinfo.default.run(
1250
1364
  {
1251
1365
  System: ["OS", "CPU", "Memory", "Shell"],
1252
- Binaries: ["Node", "npm", "pnpm", "Yarn", "Watchman"],
1366
+ Binaries: ["Node", "npm", "pnpm", "Yarn", "Bun", "Watchman"],
1253
1367
  Browsers: ["Chrome", "Edge", "Internet Explorer", "Firefox", "Safari"]
1254
1368
  },
1255
1369
  {