# @dunx/create-app

Scaffolds a new [dunx](https://github.com/petarzarkov/dunx) application.

```bash
bunx @dunx/create-app my-api
```

> `bun create dunx-app` does **not** work and is not advertised:
> `bun create <template>` resolves the unscoped npm package
> `create-<template>`, which this package - being scoped - is not.

```
cd my-api
bun install
bun run dev       # http://localhost:3000/greetings, restarting on a change
```

## The questions

There is no flag for choosing features. The command opens a list:

```
? Features  2 chosen, 1 pulled in
  ○ notes       CRUD routes with zod validation. The smallest real feature.
  ◉ openapi     OpenAPI 3.1 from the routes own schemas, plus the Swagger UI page.
  ◈ database    drizzle over bun:sqlite, with a schema, seeds and migrations.
❯ ◉ users       A repository, a service and validated routes over the database.
  ○ auth        better-auth mounted, with SessionGuard and an audit trail.
  database comes along as a requirement.
  Space toggles. ↑↓ moves. a all, n none. Enter continues.
```

| Key             | Does                                          |
| --------------- | --------------------------------------------- |
| `↑` `↓`, `k` `j`, Tab | Move the cursor                         |
| Space           | Toggle the feature under it                   |
| `a` / `n`       | Everything / nothing                          |
| Enter           | Take the selection                            |
| Ctrl+C, Esc     | Stop, having written nothing                  |

`◉` is chosen, `◈` is pulled in by something else you chose, `○` is neither. The
two lines under the list update as you go: one shows what your selection drags in,
the other shows which of it needs Redis or Postgres running to do anything.

After the list, you are asked whether to compile the app to one standalone binary.
Yes adds `scripts/build.ts` and a `build` script. `bun run build` then uses
`bun build --compile` to write one executable that runs on a host with nothing
installed.

Three more questions appear only when there is something to ask: a directory, when
the command line named none; a package name, when the directory's is one npm would
reject; and whether to write into a directory that already has files in it.

## Options

| Flag            | Default            | Meaning                                       |
| --------------- | ------------------ | --------------------------------------------- |
| `--name <name>` | the directory name | Package name for the generated app            |
| `--force`       | off                | Write into a directory that already has files |
| `--yes`, `-y`   | off                | Skip the questions, take the minimal template |
| `--help`        |                    | Print usage                                   |

**Piped, redirected or in CI it asks nothing.** It writes the minimal template
instead, so a script never hangs on a question nothing can answer. To choose
features without a terminal, call [`scaffold`](#programmatic-use) rather than
passing flags.

The name is validated against npm's rules **before** anything is created, because
an invalid one would otherwise surface as a confusing `bun install` failure inside
a directory you just made.

## Scaffolding into a repo you already made

```bash
mkdir my-api && cd my-api && git init
bunx @dunx/create-app .
```

`.git`, `.gitkeep`, `.DS_Store` and `LICENSE` are ignored when checking whether
the directory is empty, so a new repo or a clone of an empty GitHub repository
works without a prompt. Any other file counts. The template includes `.gitignore`
and `README.md`, and the last question asks whether to overwrite yours.

## What a composed app looks like

```
my-api/
  src/
    main.ts          exports createApp, and serves it when run directly
    app.module.ts    the root module, importing every feature
    config.ts        one validation function, flat env in and a shaped object out
    users/           one directory per feature you chose
  package.json       dev, start, test, typecheck
  bunfig.toml        the transform preload
```

Three files are generated for the selection, and the feature directories are just
copied. `main.ts` is one file rather than two: a test imports `createApp` from it,
and the `import.meta.main` block at the bottom stops that same import from
starting a server.

**There is no worker entry point, even with queues.** `QueueModule` is given
`consume: true`, so the container opens the bullmq workers at `onInit` and closes
them before the connections the handlers use. A handler marked `background: true` is
forked by bullmq itself into `src/jobs/jobs.processor.ts`.

## What it generates

The `minimal` template is the same app as
[`examples/minimal`](https://github.com/petarzarkov/dunx/tree/main/examples/minimal):
a service, a controller, a module, `HttpFactory.create`, one test against a real
server, and the `bunfig.toml` preload line that makes constructor injection work.

Its `src/` is a **byte-for-byte copy** of that example. A test in this package
fails if the two ever drift.

Every app also gets an `AGENTS.md` naming its layout, its commands and the rules
dunx fails at boot over, plus a `CLAUDE.md` pointing at it. Both link
<https://dunx.win/setup.md>, which is served per release, rather
than copying the framework's own instructions into your repository.

The example is the one CI boots, so keeping them identical makes the template
trustworthy rather than merely plausible.

## Two details

**Versions are resolved at run time rather than written into the template.** Every
`@dunx/*` range in the template manifest is `__DUNX_VERSION__`, replaced with a
caret range on this package's own version. dunx versions in lockstep - every
package shares one number and ships together - so the version doing the
scaffolding is by definition a set that works together.

Writing versions into the template would go stale on the next release.

When you add a `@dunx/*` package later, use the same version as the others. The
packages peer-depend on each other by caret range, so mixing minor versions warns
on install. It can also install two copies of `@dunx/core`, which breaks
dependency injection: a token is a class, so a provider bound with one copy's
class cannot be found through the other copy's.

**The template's `.gitignore` ships as `_gitignore`.** npm renames a published
`.gitignore` to `.npmignore`, which would leave every scaffolded app without one.
The scaffolder puts the dot back on write.

## Programmatic use

```ts
import { scaffold } from '@dunx/create-app';

const { directory, files } = await scaffold({
  target: 'my-api',
  name: '@acme/my-api',
  features: ['users', 'openapi'],
});
```

This is the scripted path the removed `--with` flag used to be. `features` takes
the same names the list shows, in any order, and pulls in what they require.
`FEATURES` exports the set. Omitting it writes the minimal template. `binary: true`
adds the standalone-binary build, and forces the generation path so it works with
no features too.

`scaffold` throws `ScaffoldError` for anything the caller can fix - an unknown
template, an unusable package name, a non-empty target without `force` - and lets
everything else propagate.
