@dunx/create-app 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -98,10 +98,10 @@ var FEATURES = [
98
98
  {
99
99
  name: "openapi",
100
100
  source: "docs",
101
- summary: "OpenAPI 3.1 from the routes own schemas, plus the explorer page.",
101
+ summary: "OpenAPI 3.1 from the routes own schemas, plus the Swagger UI page.",
102
102
  requires: [],
103
103
  module: { klass: "DocsModule", from: "./docs/docs.module.js" },
104
- dependencies: ["@dunx/openapi", "zod"],
104
+ dependencies: ["@dunx/openapi", "swagger-ui-dist", "zod"],
105
105
  config: []
106
106
  },
107
107
  {
@@ -298,6 +298,7 @@ var manifest = (features) => {
298
298
  };
299
299
  var THIRD_PARTY = Object.freeze({
300
300
  zod: "^4.4.3",
301
+ "swagger-ui-dist": "^5.32.14",
301
302
  "drizzle-orm": "^0.45.2",
302
303
  "better-auth": "^1.6.25",
303
304
  bullmq: "^6.0.5",
@@ -694,5 +695,5 @@ var scaffold = async (options) => {
694
695
 
695
696
  export { FEATURES, featureNames, impliedBy, TEMPLATES, VERSION_PLACEHOLDER, ScaffoldError, scaffold };
696
697
 
697
- //# debugId=4D36F643251D18C264756E2164756E21
698
- //# sourceMappingURL=chunk-8tx6zxfe.js.map
698
+ //# debugId=BC087157153A09C764756E2164756E21
699
+ //# sourceMappingURL=chunk-yzz4z6jv.js.map
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/features.ts", "../src/scaffold.ts", "../src/generate.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * The features a generated app can be composed from, each one a directory of\n * `examples/full` - the example CI boots and tours on every push.\n *\n * That is the whole point of sourcing them there rather than writing starter code\n * here: a template nobody runs rots, and this repo already runs `examples/full`\n * end to end. `bun run sync:templates` copies the directories in and\n * `features.test.ts` fails if a copy drifts, so what gets scaffolded is what CI\n * proved works.\n *\n * What is **not** copied is the wiring: `app.module.ts`, `config.ts`,\n * `bootstrap.ts` and `main.ts` in the full example name every feature at once, so\n * they are generated from the selection instead. See `generate.ts`.\n */\nexport interface Feature {\n /** Flag name, and the directory under `templates/features/`. */\n readonly name: string;\n /** The directory in `examples/full/src` this mirrors. */\n readonly source: string;\n readonly summary: string;\n /** Features this one imports from, pulled in automatically. */\n readonly requires: readonly string[];\n /** The module class to import, and the file it comes from. */\n readonly module: { readonly klass: string; readonly from: string };\n /** Runtime dependencies this feature adds to the generated manifest. */\n readonly dependencies: readonly string[];\n /** Config groups this feature reads, contributed to the generated config. */\n readonly config: readonly string[];\n /**\n * A service that has to be running for the feature to do anything. Named so the\n * prompt can say so and the generated README can list it, rather than the app\n * failing in a way the reader has to diagnose.\n */\n readonly service?: string;\n}\n\n/**\n * Config groups, keyed by the name a feature asks for. `env` is what lands in\n * `.env.example`, `schema` the zod line, `field` the `AppConfig` member and `map`\n * how the flat variable becomes the shaped one - the four things\n * `examples/full/src/config.ts` states for every group at once, split so a\n * selection can state only its own.\n */\nexport interface ConfigGroup {\n readonly schema: readonly string[];\n readonly field: string;\n readonly map: string;\n readonly env: readonly { readonly name: string; readonly value: string }[];\n}\n\nexport const CONFIG_GROUPS: Readonly<Record<string, ConfigGroup>> =\n Object.freeze({\n port: {\n schema: [\n 'PORT: z.coerce.number().int().min(0).max(65535).default(3000),',\n ],\n field: 'readonly port: number;',\n map: 'port: value.PORT,',\n env: [{ name: 'PORT', value: '3000' }],\n },\n appName: {\n schema: [],\n field: 'readonly appName: string;',\n map: \"appName: '__DUNX_APP_NAME__',\",\n env: [],\n },\n log: {\n schema: [\n 'LOG_LEVEL: z.enum(LogLevel).default(LogLevel.INFO),',\n '/** Unset means console only. Set it to also append JSON to a rotating file. */',\n 'LOG_FILE: z.string().optional(),',\n ],\n field:\n 'readonly log: { readonly level: LogLevel; readonly file: string | undefined };',\n map: 'log: { level: value.LOG_LEVEL, file: value.LOG_FILE },',\n env: [{ name: 'LOG_LEVEL', value: 'info' }],\n },\n corsOrigin: {\n schema: [\"CORS_ORIGIN: z.string().default('https://example.com'),\"],\n field: 'readonly corsOrigin: string;',\n map: 'corsOrigin: value.CORS_ORIGIN,',\n env: [{ name: 'CORS_ORIGIN', value: 'https://example.com' }],\n },\n database: {\n schema: [\n '/** `:memory:` needs no server and leaves nothing behind, so restarts are clean. */',\n \"DATABASE_FILE: z.string().default(':memory:'),\",\n ],\n field: 'readonly database: { readonly file: string };',\n map: 'database: { file: value.DATABASE_FILE },',\n env: [{ name: 'DATABASE_FILE', value: ':memory:' }],\n },\n redis: {\n schema: [\n '/** Absent is fine: the cache routes report themselves degraded instead of failing. */',\n 'REDIS_URL: z.string().optional(),',\n ],\n field: 'readonly redis: { readonly url: string | undefined };',\n map: 'redis: { url: value.REDIS_URL },',\n env: [{ name: 'REDIS_URL', value: 'redis://localhost:6379' }],\n },\n images: {\n schema: [\n 'IMAGE_QUALITY: z.coerce.number().int().min(1).max(100).default(82),',\n ],\n field: 'readonly images: { readonly quality: number };',\n map: 'images: { quality: value.IMAGE_QUALITY },',\n env: [{ name: 'IMAGE_QUALITY', value: '82' }],\n },\n auth: {\n schema: [\n '/** better-auth signs session cookies with this. 32 characters is its own minimum. */',\n \"AUTH_SECRET: z.string().min(32).default('dunx-development-secret-not-for-production'),\",\n ],\n field: 'readonly auth: { readonly secret: string };',\n map: 'auth: { secret: value.AUTH_SECRET },',\n env: [\n {\n name: 'AUTH_SECRET',\n value: 'change-me-to-at-least-32-characters-long',\n },\n ],\n },\n seedUsers: {\n schema: [],\n field: 'readonly seedUsers: readonly string[];',\n map: \"seedUsers: ['ada', 'grace'],\",\n env: [],\n },\n authorization: {\n schema: [],\n field: 'readonly authorization: { readonly enabled: boolean };',\n map: 'authorization: { enabled: true },',\n env: [],\n },\n });\n\n/** Always present, whatever is selected: the port and the logger need them. */\nexport const BASE_CONFIG: readonly string[] = ['appName', 'port', 'log'];\n\nexport const FEATURES: readonly Feature[] = [\n {\n name: 'notes',\n source: 'notes',\n summary: 'CRUD routes with zod validation. The smallest real feature.',\n requires: [],\n module: { klass: 'NotesModule', from: './notes/notes.module.js' },\n dependencies: ['@dunx/openapi', 'zod'],\n config: [],\n },\n {\n name: 'openapi',\n source: 'docs',\n summary:\n 'OpenAPI 3.1 from the routes own schemas, plus the Swagger UI page.',\n requires: [],\n module: { klass: 'DocsModule', from: './docs/docs.module.js' },\n // `swagger-ui-dist` is what the page is: an optional peer of\n // `@dunx/openapi`, needed if and only if the explorer is mounted. The\n // `notes` feature declares `@dunx/openapi` too and does not need it, because\n // it only writes `@ApiDoc` metadata.\n dependencies: ['@dunx/openapi', 'swagger-ui-dist', 'zod'],\n config: [],\n },\n {\n name: 'http',\n source: 'http',\n summary: 'CORS, a request-logging middleware and error mapping.',\n requires: [],\n module: { klass: 'HttpModule', from: './http/http.module.js' },\n dependencies: [],\n config: ['corsOrigin'],\n },\n {\n name: 'guards',\n source: 'guards',\n summary:\n 'Route guards with @Roles and @Public, and a protected controller.',\n requires: [],\n module: { klass: 'GuardsModule', from: './guards/guards.module.js' },\n dependencies: ['zod'],\n config: ['authorization'],\n },\n {\n name: 'database',\n source: 'database',\n summary: 'drizzle over bun:sqlite, with a schema, seeds and migrations.',\n requires: [],\n module: { klass: 'DatabaseModule', from: './database/database.module.js' },\n dependencies: ['@dunx/infra', 'drizzle-orm', 'zod'],\n config: ['database'],\n },\n {\n name: 'users',\n source: 'users',\n summary: 'A repository, a service and validated routes over the database.',\n requires: ['database'],\n module: { klass: 'UsersModule', from: './users/users.module.js' },\n dependencies: ['@dunx/infra', 'drizzle-orm', 'zod'],\n config: ['appName', 'seedUsers'],\n },\n {\n name: 'auth',\n source: 'auth',\n summary: 'better-auth mounted, with SessionGuard and an audit trail.',\n requires: ['database'],\n module: { klass: 'AccountsModule', from: './auth/auth.module.js' },\n dependencies: ['@dunx/auth', 'better-auth', 'drizzle-orm'],\n config: ['auth', 'port'],\n },\n {\n name: 'cache',\n source: 'cache',\n summary: 'Bun.RedisClient behind a session store, degrading when absent.',\n requires: [],\n module: { klass: 'CacheModule', from: './cache/cache.module.js' },\n dependencies: ['@dunx/infra', 'zod'],\n config: ['redis'],\n service: 'Redis or Valkey',\n },\n {\n name: 'websockets',\n source: 'chat',\n summary: 'A @Gateway with @OnMessage events, PubSub and a Redis relay.',\n // `cache` joined this list for the same reason `files` joined health's: the gateway\n // injects `RedisConnection` for cross-process fan-out, and a module now has to\n // import the one that provides it. The summary already said \"and a Redis relay\".\n requires: ['cache'],\n module: { klass: 'ChatModule', from: './chat/chat.module.js' },\n dependencies: ['@dunx/infra'],\n config: [],\n service: 'Redis or Valkey, for multi-node fan-out only',\n },\n {\n name: 'images',\n source: 'pictures',\n summary: 'Bun.Image resizing and format conversion behind a route.',\n requires: [],\n module: { klass: 'PicturesModule', from: './pictures/pictures.module.js' },\n dependencies: ['@dunx/infra', 'zod'],\n config: ['images'],\n },\n {\n name: 'files',\n source: 'storage',\n summary: 'Uploads and downloads on Bun.file, with a workspace root.',\n requires: [],\n module: { klass: 'StorageModule', from: './storage/storage.module.js' },\n dependencies: ['@dunx/infra', 'zod'],\n config: [],\n },\n {\n name: 'jobs',\n source: 'jobs',\n summary: 'bullmq queues and a worker, over Bun.RedisClient.',\n requires: ['images'],\n module: { klass: 'JobsModule', from: './jobs/jobs.module.js' },\n dependencies: ['@dunx/infra', 'bullmq', 'ioredis', 'zod'],\n config: ['redis'],\n service: 'Redis or Valkey',\n },\n {\n name: 'health',\n source: 'health',\n summary: 'One endpoint reporting which parts are live and which degraded.',\n // `files` joined this list when module scoping made the dependency explicit: the\n // controller injects `Storage`, so the module has to import the one that provides\n // it. Selecting health without files used to typecheck and fail at boot.\n requires: ['cache', 'database', 'files'],\n module: { klass: 'HealthModule', from: './health/health.module.js' },\n dependencies: ['@dunx/infra'],\n config: ['appName'],\n },\n];\n\nexport const featureNames: readonly string[] = FEATURES.map(\n (feature) => feature.name,\n);\n\nconst byName = new Map(FEATURES.map((feature) => [feature.name, feature]));\n\nexport class UnknownFeatureError extends Error {\n override readonly name = 'UnknownFeatureError';\n}\n\n/**\n * The selection plus everything it requires, in **import order** - which is\n * construction order, and shutdown runs in reverse. A feature is emitted after\n * everything it requires, so the database outlives the features reading it, the\n * same ordering `examples/full/src/app.module.ts` states by hand.\n *\n * Depth-first over `requires`, with a visited set, so a diamond resolves once and\n * the result is stable whatever order the caller asked in.\n */\nexport const resolveFeatures = (\n requested: readonly string[],\n): readonly Feature[] => {\n const unknown = requested.filter((name) => !byName.has(name));\n if (unknown.length > 0) {\n throw new UnknownFeatureError(\n `Unknown feature${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}. ` +\n `Available: ${featureNames.join(', ')}.`,\n );\n }\n\n const ordered: Feature[] = [];\n const seen = new Set<string>();\n const rank = new Map(FEATURES.map((feature, at) => [feature.name, at]));\n\n const visit = (name: string): void => {\n if (seen.has(name)) return;\n seen.add(name);\n const feature = byName.get(name);\n if (!feature) return;\n // Requirements in **registry order**, not in the order this feature happens to\n // list them: two independent requirements would otherwise come out in the\n // order they were typed, which is not a statement about construction order and\n // would make `requires: ['cache', 'database']` build the cache first.\n for (const required of [...feature.requires].sort(\n (left, right) => (rank.get(left) ?? 0) - (rank.get(right) ?? 0),\n )) {\n visit(required);\n }\n ordered.push(feature);\n };\n\n // Registry order, not request order, so two runs asking for the same set in a\n // different order generate byte-identical files.\n for (const feature of FEATURES) {\n if (requested.includes(feature.name)) visit(feature.name);\n }\n\n return ordered;\n};\n\n/** Which of the resolved features the caller did not ask for. */\nexport const impliedBy = (\n requested: readonly string[],\n resolved: readonly Feature[],\n): readonly string[] =>\n resolved\n .map((feature) => feature.name)\n .filter((name) => !requested.includes(name));\n",
6
+ "import { existsSync, readdirSync } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Glob } from 'bun';\nimport { resolveFeatures, type Feature } from './features.js';\nimport {\n appModule,\n bootstrap,\n config,\n configGroupsFor,\n envExample,\n main,\n manifest,\n readme,\n worker,\n} from './generate.js';\n\n/** The templates that ship with the package, as `templates/<name>/`. */\nexport const TEMPLATES = Object.freeze(['minimal'] as const);\nexport type TemplateName = (typeof TEMPLATES)[number];\n\n/**\n * Every `@dunx/*` version in a template manifest is this placeholder. Versioning\n * is lockstep, so the right version to install is whatever version of\n * `@dunx/create-app` is doing the scaffolding - resolved at run time rather than\n * written into the template, which would go stale on the next release.\n */\nexport const VERSION_PLACEHOLDER = '__DUNX_VERSION__';\n\n/**\n * Names a package cannot ship as-is, so they ship prefixed and are renamed on write.\n *\n * `.gitignore` is the known one: npm renames a published copy to `.npmignore`.\n *\n * **`bunfig.toml` is the one that was silently missing.** It is stripped from the\n * tarball entirely - presumably so a dependency cannot hijack the installing\n * project's Bun config - and it is the single file dunx asks an app to have. Every\n * app scaffolded from a published `@dunx/create-app` therefore had no\n * `@dunx/transform/preload`, and failed at boot with the very error the guide\n * describes. Measured with `bun pm pack`, and `pack.test.ts` now measures it on\n * every run rather than trusting this comment.\n */\nconst RENAMED = Object.freeze({\n _gitignore: '.gitignore',\n '_bunfig.toml': 'bunfig.toml',\n});\n\n/**\n * Entries that do not make a directory non-empty for scaffolding purposes.\n *\n * `.git` is the one that matters: `git init` then scaffold into the repo is the\n * documented way to start, and refusing it blocks the flow outright. `.gitkeep`\n * exists only so git can track an otherwise empty directory, so it *means* empty.\n * `.DS_Store` appears from merely opening the folder in Finder. `LICENSE` is what\n * GitHub's create-a-repository flow leaves in a fresh clone.\n *\n * The list is deliberately short, and the test for it is whether the template\n * writes that name. It does not write any of these four, so ignoring them can\n * never destroy anything. `.gitignore` and `README.md` are excluded for exactly\n * that reason: the template writes both, and silently overwriting a user's copy\n * is what `--force` exists to gate.\n */\nconst IGNORED_WHEN_EMPTY: ReadonlySet<string> = new Set([\n '.DS_Store',\n '.git',\n '.gitkeep',\n 'LICENSE',\n]);\n\nexport interface ScaffoldOptions {\n /** Directory to create. Relative paths resolve against `cwd`. */\n readonly target: string;\n /** Package name for the generated app. Defaults to the target's basename. */\n readonly name?: string;\n readonly template?: TemplateName;\n /**\n * Features to compose the app from, by name. Anything they require is pulled in.\n *\n * Passing any switches from copying a fixed template to generating the wiring\n * around the chosen feature directories - see `generate.ts`. An empty list, or\n * none at all, scaffolds `template` unchanged, so the default behaviour is exactly\n * what it was.\n */\n readonly features?: readonly string[];\n /** Write into a directory that already has files in it. */\n readonly force?: boolean;\n readonly cwd?: string;\n /** Overrides the version written into the generated manifest. */\n readonly version?: string;\n}\n\nexport interface ScaffoldResult {\n readonly directory: string;\n readonly name: string;\n readonly template: TemplateName | 'composed';\n /** Resolved feature names, in import order. Empty for a fixed template. */\n readonly features: readonly string[];\n readonly files: readonly string[];\n}\n\nexport class ScaffoldError extends Error {\n override readonly name = 'ScaffoldError';\n}\n\n/**\n * `dist/index.js` and `dist/cli.js` both sit one level under the package root, so\n * `../templates` resolves the same from either. In the source tree it resolves\n * from `src/`, which is the same depth - so tests exercise the real path rather\n * than a special case.\n *\n * `fileURLToPath`, not `new URL(...).pathname`: the latter stays percent-encoded,\n * so an install under a directory with a space in it looks for `space%20test/`\n * and reports the template missing. On Windows it is worse - it yields a\n * leading-slash, drive-lettered path that resolves nowhere.\n */\nconst templatesRoot = (): string =>\n resolve(dirname(fileURLToPath(import.meta.url)), '..', 'templates');\n\n/**\n * npm forbids uppercase and a leading dot or underscore, and a scope is legal.\n * Checked here because the failure would otherwise surface as a confusing\n * `bun install` error inside a directory the user just created.\n */\nconst isValidPackageName = (name: string): boolean =>\n /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);\n\nconst readPackageVersion = async (): Promise<string> => {\n const file = Bun.file(join(templatesRoot(), '..', 'package.json'));\n const json = (await file.json()) as { version?: string };\n return json.version ?? '0.0.0';\n};\n\n/** Placeholders are substituted in every written file, generated or copied. */\nconst fill = (contents: string, name: string, version: string): string =>\n contents\n .replaceAll(VERSION_PLACEHOLDER, version)\n .replaceAll('__DUNX_APP_NAME__', name);\n\n/**\n * The four files a subset of features cannot copy, because the full example states\n * every feature at once in each of them.\n */\nconst generated = (\n name: string,\n features: readonly Feature[],\n): Readonly<Record<string, string>> => {\n const groups = configGroupsFor(features);\n const files: Record<string, string> = {\n 'package.json': manifest(features),\n 'README.md': readme(name, features),\n '.env.example': envExample(groups),\n 'src/main.ts': main(name, features),\n 'src/bootstrap.ts': bootstrap(name, features),\n 'src/app.module.ts': appModule(name, features),\n 'src/config.ts': config(name, groups),\n };\n if (features.some((feature) => feature.name === 'jobs')) {\n files['src/worker.ts'] = worker(name);\n }\n return files;\n};\n\nexport const scaffold = async (\n options: ScaffoldOptions,\n): Promise<ScaffoldResult> => {\n const template = options.template ?? 'minimal';\n if (!TEMPLATES.includes(template)) {\n throw new ScaffoldError(\n `Unknown template \"${template}\". Available: ${TEMPLATES.join(', ')}.`,\n );\n }\n\n // Resolved before anything is written, so an unknown feature name fails with the\n // list of real ones rather than half a directory.\n const requested = options.features ?? [];\n let features: readonly Feature[] = [];\n try {\n features = resolveFeatures(requested);\n } catch (error) {\n throw new ScaffoldError(\n error instanceof Error ? error.message : String(error),\n );\n }\n const composing = features.length > 0;\n\n const directory = resolve(options.cwd ?? process.cwd(), options.target);\n const name = options.name ?? basename(directory);\n\n if (!isValidPackageName(name)) {\n throw new ScaffoldError(\n `\"${name}\" is not a usable package name. Pass --name to choose one.`,\n );\n }\n\n if (existsSync(directory) && options.force !== true) {\n const blocking = readdirSync(directory).filter(\n (entry) => !IGNORED_WHEN_EMPTY.has(entry),\n );\n if (blocking.length > 0) {\n // Naming what blocked it, because `.git` used to block it and the message\n // gave no way to tell that from a directory of real work.\n const shown = blocking.sort().slice(0, 3).join(', ');\n const rest = blocking.length > 3 ? `, +${blocking.length - 3} more` : '';\n throw new ScaffoldError(\n `${directory} is not empty (${shown}${rest}). ` +\n `Pass --force to write into it anyway.`,\n );\n }\n }\n\n const version = options.version ?? `^${await readPackageVersion()}`;\n const written: string[] = [];\n\n /** Copies a directory of the package's own templates into the new app. */\n const copyTree = async (from: string, into: string): Promise<void> => {\n // `**/*` with `dot: true` so a template can carry a dotfile that npm did not\n // rename; the explicit `_gitignore` mapping covers the one that it does.\n for await (const relative of new Glob('**/*').scan({\n cwd: from,\n dot: true,\n onlyFiles: true,\n })) {\n const base = relative.split('/').at(-1) ?? relative;\n const renamed = (RENAMED as Record<string, string | undefined>)[base];\n const target = join(\n into,\n renamed === undefined ? relative : join(dirname(relative), renamed),\n );\n\n const contents = await Bun.file(join(from, relative)).text();\n // `Bun.write` creates parent directories, so there is no mkdir pass.\n await Bun.write(join(directory, target), fill(contents, name, version));\n written.push(target);\n }\n };\n\n if (!composing) {\n const source = join(templatesRoot(), template);\n if (!existsSync(source)) {\n throw new ScaffoldError(\n `Template \"${template}\" is missing from ${source}.`,\n );\n }\n await copyTree(source, '.');\n return {\n directory,\n name,\n template,\n features: [],\n files: written.sort(),\n };\n }\n\n // The base carries what every composed app needs and no feature owns: the\n // tsconfig, the transform preload, and the gitignore.\n const base = join(templatesRoot(), 'base');\n if (!existsSync(base)) {\n throw new ScaffoldError(\n `The base template is missing from ${base}. Run \\`bun run sync:templates\\`.`,\n );\n }\n await copyTree(base, '.');\n\n for (const feature of features) {\n const from = join(templatesRoot(), 'features', feature.source);\n if (!existsSync(from)) {\n throw new ScaffoldError(\n `Feature \"${feature.name}\" is missing from ${from}. ` +\n 'Run `bun run sync:templates`.',\n );\n }\n await copyTree(from, join('src', feature.source));\n }\n\n for (const [target, contents] of Object.entries(generated(name, features))) {\n await Bun.write(join(directory, target), fill(contents, name, version));\n written.push(target);\n }\n\n return {\n directory,\n name,\n template: 'composed',\n features: features.map((feature) => feature.name),\n files: written.sort(),\n };\n};\n",
7
+ "import { BASE_CONFIG, CONFIG_GROUPS, type Feature } from './features.js';\n\n/**\n * The wiring, generated from a feature selection.\n *\n * The full example states every feature at once in four files - `app.module.ts`,\n * `config.ts`, `bootstrap.ts` and `main.ts` - so those are the ones a subset cannot\n * copy. Everything else is the feature's own directory, copied verbatim.\n *\n * Generated rather than assembled by editing a copy on purpose: an edited copy\n * cannot be checked against the example it came from, and the byte-for-byte parity\n * test is what stops the vendored features drifting from the app CI actually boots.\n */\nconst HEADER = (name: string): string =>\n `// Generated by @dunx/create-app for ${name}. Yours to edit.\\n`;\n\nconst uniq = (values: readonly string[]): string[] => [...new Set(values)];\n\n/** Every config group the selection needs, base first, in a stable order. */\nexport const configGroupsFor = (\n features: readonly Feature[],\n): readonly string[] => {\n const wanted = uniq([\n ...BASE_CONFIG,\n ...features.flatMap((feature) => feature.config),\n ]);\n return Object.keys(CONFIG_GROUPS).filter((group) => wanted.includes(group));\n};\n\nexport const dependenciesFor = (\n features: readonly Feature[],\n): readonly string[] =>\n uniq([\n '@dunx/core',\n '@dunx/http',\n '@dunx/transform',\n '@dunx/infra',\n ...features.flatMap((feature) => feature.dependencies),\n ]).sort();\n\nconst DUNX = /^@dunx\\//;\n\nexport const manifest = (features: readonly Feature[]): string => {\n const deps = dependenciesFor(features);\n const dependencies: Record<string, string> = {};\n for (const dep of deps) {\n dependencies[dep] = DUNX.test(dep) ? '__DUNX_VERSION__' : versionOf(dep);\n }\n\n const scripts: Record<string, string> = {\n start: 'bun src/main.ts',\n test: 'bun test',\n typecheck: 'tsc --noEmit',\n };\n // A queue needs a process to drain it, and it is not the web one.\n if (features.some((feature) => feature.name === 'jobs')) {\n scripts['worker'] = 'bun src/worker.ts';\n }\n\n return `${JSON.stringify(\n {\n name: '__DUNX_APP_NAME__',\n version: '0.1.0',\n private: true,\n type: 'module',\n scripts,\n dependencies,\n devDependencies: {\n '@dunx/testing': '__DUNX_VERSION__',\n '@types/bun': '>=1.3.0',\n typescript: '^5.7.0',\n },\n engines: { bun: '>=1.3.0' },\n },\n null,\n 2,\n )}\\n`;\n};\n\n/**\n * Third-party ranges, pinned here rather than read off `examples/full` at run time:\n * the generated app installs from npm and the example installs from the workspace,\n * so the example's manifest is not a statement about what a consumer should take.\n * `features.test.ts` checks these against the example's, which is what stops them\n * silently diverging from a version combination that is actually exercised.\n */\nexport const THIRD_PARTY: Readonly<Record<string, string>> = Object.freeze({\n zod: '^4.4.3',\n 'swagger-ui-dist': '^5.32.14',\n 'drizzle-orm': '^0.45.2',\n 'better-auth': '^1.6.25',\n bullmq: '^6.0.5',\n ioredis: '^6.0.0',\n});\n\nconst versionOf = (dep: string): string => THIRD_PARTY[dep] ?? 'latest';\n\nexport const appModule = (\n name: string,\n features: readonly Feature[],\n): string => {\n const needsLogger = true;\n const imports = [\n \"import { ConfigModule, Module } from '@dunx/core';\",\n ...(needsLogger\n ? [\"import { LoggerModule } from '@dunx/infra/logger';\"]\n : []),\n \"import { AppConfigService, validate } from './config.js';\",\n ...features.map(\n (feature) =>\n `import { ${feature.module.klass} } from '${feature.module.from}';`,\n ),\n ];\n\n const moduleImports = [\n 'ConfigModule.forRoot({ validate, as: AppConfigService }),',\n '// The level comes from the validated config, which is the one thing a',\n '// zero-argument `forRoot` function cannot reach.',\n 'LoggerModule.forRootAsync(',\n ' {',\n ' useFactory: (config: AppConfigService) => ({',\n \" name: config.get('appName'),\",\n \" level: config.get('log').level,\",\n ' }),',\n ' inject: [AppConfigService] as const,',\n ' },',\n ' { captureGlobalErrors: true },',\n '),',\n ...features.map((feature) => `${feature.module.klass},`),\n ];\n\n return `${HEADER(name)}${imports.join('\\n')}\n\n/**\n * Import order is construction order, and shutdown runs in reverse - so config and\n * the logger are built first and torn down last, and anything a feature depends on\n * outlives it.\n */\n@Module({\n imports: [\n${moduleImports.map((line) => ` ${line}`).join('\\n')}\n ],\n})\nexport class AppModule {}\n`;\n};\n\nexport const config = (name: string, groups: readonly string[]): string => {\n const chosen = groups\n .map((group) => [group, CONFIG_GROUPS[group]] as const)\n .filter(\n (entry): entry is [string, (typeof CONFIG_GROUPS)[string]] =>\n entry[1] !== undefined,\n );\n\n const schema = chosen.flatMap(([, group]) => group.schema);\n const needsLogLevel = groups.includes('log');\n\n return `${HEADER(name)}import { ConfigService, type ConfigSource${\n needsLogLevel ? ', LogLevel' : ''\n } } from '@dunx/core';\nimport { z } from 'zod';\n\n/**\n * One validation function, which is the whole \\`ConfigModule\\` contract. dunx does\n * not pick the library - this is zod because the routes already use it, and a\n * hand-written function that throws would work identically.\n *\n * \\`.default()\\` is where a value comes from when the variable is unset, so a clean\n * checkout boots with no \\`.env\\` at all. Bun loads \\`.env\\` and \\`.env.local\\` itself,\n * so there is nothing here that reads a file.\n */\nconst envSchema = z.object({\n${schema.map((line) => ` ${line}`).join('\\n')}\n});\n\nexport interface AppConfig {\n${chosen.map(([, group]) => ` ${group.field}`).join('\\n')}\n}\n\n/**\n * One name for the typed config everywhere. A subclass rather than\n * \\`ConfigService<AppConfig>\\` at each site because a factory's \\`inject: [...]\\`\n * carries no type argument - the class does, and it is a real runtime value, so it\n * is both a precise token and a usable constructor annotation.\n */\nexport class AppConfigService extends ConfigService<AppConfig> {}\n\n/** The one broker channel the websocket relay carries every topic on. */\nexport const RELAY_CHANNEL = '__DUNX_APP_NAME__:ws';\n\n/** Flat variables in, a shaped object out. Nothing downstream reads \\`Bun.env\\`. */\nexport const validate = (env: ConfigSource): AppConfig => {\n const parsed = envSchema.safeParse(env);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((issue) => \\`\\${issue.path.join('.') || '(root)'}: \\${issue.message}\\`)\n .join('\\\\n - ');\n throw new Error(\\`Configuration is invalid:\\\\n - \\${issues}\\`);\n }\n const value = parsed.data;\n\n return {\n${chosen.map(([, group]) => ` ${group.map}`).join('\\n')}\n };\n};\n`;\n};\n\nconst has = (features: readonly Feature[], name: string): boolean =>\n features.some((feature) => feature.name === name);\n\nexport const bootstrap = (\n name: string,\n features: readonly Feature[],\n): string => {\n const openapi = has(features, 'openapi');\n const websockets = has(features, 'websockets');\n const http = has(features, 'http');\n\n const imports = [\n `import { HttpFactory${websockets ? ', RedisRelay' : ''}, type HttpApp } from '@dunx/http';`,\n ...(openapi ? [\"import { OpenApiModule } from '@dunx/openapi';\"] : []),\n \"import { AppModule } from './app.module.js';\",\n `import { ${[\n ...(http ? ['AppConfigService'] : []),\n ...(websockets ? ['RELAY_CHANNEL'] : []),\n ].join(', ')} } from './config.js';`,\n ...(http\n ? [\"import { RequestLoggerMiddleware } from './http/request-log.js';\"]\n : []),\n ].filter((line) => !line.includes('{ }'));\n\n const root = openapi\n ? `OpenApiModule.forRoot({\n title: '__DUNX_APP_NAME__',\n version: '0.1.0',\n root: AppModule,\n })`\n : 'AppModule';\n\n const options = websockets\n ? [\n '// Multi-node websocket fan-out on `Bun.RedisClient`, so it costs no',\n '// dependency. With no Redis running this degrades to single-process',\n '// behaviour, logs one warning, and the app still boots.',\n 'websocket: { idleTimeout: 30 },',\n 'relay: new RedisRelay({ connectionTimeout: 500 }),',\n 'relayChannel: RELAY_CHANNEL,',\n ]\n : [];\n\n /**\n * Everything between `create()` and `listen()`. The prefix is set whatever is\n * selected, because the copied controllers declare paths under it and the URLs\n * `main.ts` prints assume it.\n *\n * `app.use` takes the middleware **class**, not an instance: the container\n * constructs it, which is what lets it have dependencies of its own.\n */\n const shaping = [\n \"app.setGlobalPrefix('api');\",\n ...(http\n ? [\n 'app.use(RequestLoggerMiddleware);',\n \"app.set('trust proxy', true);\",\n 'app.enableCors({',\n \" origin: app.get(AppConfigService).get('corsOrigin'),\",\n ' credentials: true,',\n ' maxAge: 600,',\n '});',\n ]\n : []),\n ];\n\n return `${HEADER(name)}${imports.join('\\n')}\n\n/**\n * One app, built the same way for \\`bun start\\` and for the tests - so what the\n * tests exercise is what actually serves.\n *\n * \\`create()\\` boots the container and discovers routes and gateways; \\`listen()\\` is\n * what builds the \\`Bun.serve\\` route table. Everything between the two still gets to\n * shape it, and after \\`listen()\\` every one of those throws.\n */\nexport const createApp = async (): Promise<HttpApp> => {\n const app = await HttpFactory.create(\n ${root}${\n options.length === 0\n ? ',\\n'\n : `,\n {\n${options.map((line) => ` ${line}`).join('\\n')}\n },\n`\n } );\n\n${shaping.map((line) => ` ${line}`).join('\\n')}\n\n return app;\n};\n`;\n};\n\nexport const main = (name: string, features: readonly Feature[]): string => {\n const health = has(features, 'health');\n const openapi = has(features, 'openapi');\n\n const lines = [\n ...(openapi\n ? [\n \"logger.info(`docs ${new URL('api/docs', url).href}`);\",\n \"logger.info(`openapi ${new URL('api/openapi.json', url).href}`);\",\n ]\n : []),\n ...(health\n ? [\"logger.info(`health ${new URL('api/health', url).href}`);\"]\n : []),\n ];\n\n return `${HEADER(name)}import { Logger } from '@dunx/core';\nimport { createApp } from './bootstrap.js';\nimport { AppConfigService } from './config.js';\n\nasync function bootstrap(): Promise<void> {\n const app = await createApp();\n app.enableShutdownHooks();\n\n const config = app.get(AppConfigService);\n const logger = app.get(Logger);\n const url = await app.listen(config.get('port'));\n\n logger.info(\\`listening on \\${url}\\`);\n${lines.map((line) => ` ${line}`).join('\\n')}${lines.length > 0 ? '\\n' : ''}\n // Nothing else to do: the server holds the process open, and the shutdown hooks\n // resolve this once a signal arrives.\n await app.closed;\n}\n\nbootstrap().catch((error: unknown) => {\n console.error('failed to start', error);\n process.exit(1);\n});\n`;\n};\n\n/** The queue worker, only when a queue was asked for. */\nexport const worker = (name: string): string =>\n `${HEADER(name)}import { AppFactory } from '@dunx/core';\nimport { AppModule } from './app.module.js';\n\n/**\n * A queue needs a process to drain it, and it is deliberately not the web one: a\n * worker that shares the server's event loop competes with request handling.\n */\nconst app = await AppFactory.create(AppModule);\napp.enableShutdownHooks();\nawait app.closed;\n`;\n\nexport const envExample = (groups: readonly string[]): string => {\n const lines = groups\n .flatMap((group) => CONFIG_GROUPS[group]?.env ?? [])\n .map((entry) => `${entry.name}=${entry.value}`);\n\n return lines.length === 0\n ? '# Every variable has a default, so this file is optional.\\n'\n : `# Every variable here has a default, so the app boots with no .env at all.\\n${lines.join('\\n')}\\n`;\n};\n\nexport const readme = (name: string, features: readonly Feature[]): string => {\n const services = features.filter((feature) => feature.service !== undefined);\n\n return `# ${name}\n\nScaffolded with \\`bunx @dunx/create-app\\`.\n\n\\`\\`\\`bash\nbun install\nbun run start\n\\`\\`\\`\n\n## What is wired up\n\n${\n features.length === 0\n ? 'Nothing beyond the base app.'\n : features\n .map((feature) => `- **${feature.name}** - ${feature.summary}`)\n .join('\\n')\n}\n\n${\n services.length === 0\n ? ''\n : `## Services\n\nThese features need something running. Each one degrades rather than failing the\nboot, so the app still starts without them.\n\n${services.map((feature) => `- **${feature.name}** needs ${feature.service}`).join('\\n')}\n\n`\n}## Layout\n\n- \\`src/main.ts\\` - the entry point\n- \\`src/bootstrap.ts\\` - builds the app; shared by \\`start\\` and the tests\n- \\`src/app.module.ts\\` - the root module, importing every feature\n- \\`src/config.ts\\` - one validation function, flat env in and a shaped object out\n${features.map((feature) => `- \\`src/${feature.source}/\\` - ${feature.name}`).join('\\n')}\n\n\\`main.ts\\`, \\`bootstrap.ts\\`, \\`app.module.ts\\` and \\`config.ts\\` were generated for the\nfeatures you chose; everything else is copied from dunx's \\`examples/full\\`, which is\nrun and toured in CI on every push. The \\`*.demo.ts\\` files are that example's\nscripted walkthroughs - delete one and its \\`providers\\` entry when you do not want it.\n\n## Constructor injection\n\n\\`bunfig.toml\\` preloads \\`@dunx/transform\\`, which records each class's constructor\nparameter types so the container can resolve them. Without that line providers are\nbuilt with no arguments and boot fails saying so.\n`;\n};\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "BC087157153A09C764756E2164756E21",
11
+ "names": []
12
+ }
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  featureNames,
8
8
  impliedBy,
9
9
  scaffold
10
- } from "./chunk-8tx6zxfe.js";
10
+ } from "./chunk-yzz4z6jv.js";
11
11
 
12
12
  // src/cli.ts
13
13
  import { parseArgs } from "util";
package/dist/index.js CHANGED
@@ -4,13 +4,13 @@ import {
4
4
  TEMPLATES,
5
5
  VERSION_PLACEHOLDER,
6
6
  scaffold
7
- } from "./chunk-8tx6zxfe.js";
7
+ } from "./chunk-yzz4z6jv.js";
8
8
  export {
9
- scaffold,
10
- VERSION_PLACEHOLDER,
9
+ ScaffoldError,
11
10
  TEMPLATES,
12
- ScaffoldError
11
+ VERSION_PLACEHOLDER,
12
+ scaffold
13
13
  };
14
14
 
15
- //# debugId=83B6099C1AC65B2C64756E2164756E21
15
+ //# debugId=2DFAD801C180F0A064756E2164756E21
16
16
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "83B6099C1AC65B2C64756E2164756E21",
7
+ "debugId": "2DFAD801C180F0A064756E2164756E21",
8
8
  "names": []
9
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dunx/create-app",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Scaffold a new dunx application - bunx @dunx/create-app my-api",
5
5
  "keywords": [
6
6
  "bun",
@@ -75,26 +75,45 @@ export class DocsDemo {
75
75
 
76
76
  const page = await fetch(new URL('api/docs', url));
77
77
  const html = await page.text();
78
- // Two inline scripts: the document as JSON, and the explorer bundle. What
79
- // still has to hold is that nothing is *fetched* - so the check is on the
80
- // markup, with both script bodies removed. Inside a <script> everything is
81
- // text, and minified React's own string table contains `src=` and `<script`.
82
- const shell = html.replace(/(<script[^>]*>)[\s\S]*?(<\/script>)/g, '$1$2');
83
- // A `<link>` counts only if it would actually fetch. The page carries one
84
- // for its favicon, as a `data:` URI, which the browser never requests.
85
- const fetchedLink = [...shell.matchAll(/<link\b[^>]*href="([^"]*)"/g)].some(
86
- ([, href]) => href !== undefined && !href.startsWith('data:'),
87
- );
88
- const external =
89
- /\ssrc=/.test(shell) ||
90
- fetchedLink ||
91
- /url\(\s*["']?(https?:)?\/\//.test(html) ||
92
- html.includes('//cdn');
93
78
  logger.info(
94
79
  `GET /api/docs -> ${page.status} ${page.headers.get('content-type')}, ` +
95
- `${html.length} bytes, ${(html.match(/<\/script>/g) ?? []).length} inline scripts, ` +
96
- `external requests: ${external ? 'some' : 'none'}`,
80
+ `${html.length} bytes of Swagger UI shell`,
97
81
  );
82
+
83
+ /**
84
+ * **The page fetches, and this is the check that it only fetches from here.**
85
+ * The explorer used to be dunx's own bundle inlined into the page, so the
86
+ * assertion was that nothing was requested at all. It is now `swagger-ui-dist`,
87
+ * 3.7x the size gzipped, served as two assets - so the guarantee is narrower and
88
+ * has to be stated as what it is: same-origin only, no CDN.
89
+ *
90
+ * Script bodies are stripped first. Inside a `<script>` everything is text, so a
91
+ * `src=` in the boot script is not a resource.
92
+ */
93
+ const shell = html.replace(/(<script[^>]*>)[\s\S]*?(<\/script>)/g, '$1$2');
94
+ const requested = [
95
+ ...shell.matchAll(/<(?:script|link)\b[^>]*\s(?:src|href)="([^"]*)"/g),
96
+ ]
97
+ .map(([, href]) => href ?? '')
98
+ .filter((href) => !href.startsWith('data:'));
99
+ const offOrigin = requested.filter((href) => /^[a-z]+:|^\/\//i.test(href));
100
+ logger.info(
101
+ ` requests ${requested.length} asset(s), ${offOrigin.length} off-origin: ` +
102
+ JSON.stringify(requested),
103
+ );
104
+
105
+ // Every one of them has to actually answer, which is the half a unit test
106
+ // cannot show: these resolve out of the consumer's own swagger-ui-dist
107
+ // install, through this app's global prefix.
108
+ for (const href of requested) {
109
+ const asset = await fetch(new URL(href.replace(/^\//, ''), url));
110
+ logger.info(
111
+ ` ${href.split('?')[0]} -> ${asset.status} ` +
112
+ `${asset.headers.get('content-type')}, ` +
113
+ `${Number(asset.headers.get('content-length') ?? 0).toLocaleString('en-US')} bytes, ` +
114
+ `cache-control: ${asset.headers.get('cache-control')}`,
115
+ );
116
+ }
98
117
  }
99
118
 
100
119
  /**
@@ -1,12 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/features.ts", "../src/scaffold.ts", "../src/generate.ts"],
4
- "sourcesContent": [
5
- "/**\n * The features a generated app can be composed from, each one a directory of\n * `examples/full` - the example CI boots and tours on every push.\n *\n * That is the whole point of sourcing them there rather than writing starter code\n * here: a template nobody runs rots, and this repo already runs `examples/full`\n * end to end. `bun run sync:templates` copies the directories in and\n * `features.test.ts` fails if a copy drifts, so what gets scaffolded is what CI\n * proved works.\n *\n * What is **not** copied is the wiring: `app.module.ts`, `config.ts`,\n * `bootstrap.ts` and `main.ts` in the full example name every feature at once, so\n * they are generated from the selection instead. See `generate.ts`.\n */\nexport interface Feature {\n /** Flag name, and the directory under `templates/features/`. */\n readonly name: string;\n /** The directory in `examples/full/src` this mirrors. */\n readonly source: string;\n readonly summary: string;\n /** Features this one imports from, pulled in automatically. */\n readonly requires: readonly string[];\n /** The module class to import, and the file it comes from. */\n readonly module: { readonly klass: string; readonly from: string };\n /** Runtime dependencies this feature adds to the generated manifest. */\n readonly dependencies: readonly string[];\n /** Config groups this feature reads, contributed to the generated config. */\n readonly config: readonly string[];\n /**\n * A service that has to be running for the feature to do anything. Named so the\n * prompt can say so and the generated README can list it, rather than the app\n * failing in a way the reader has to diagnose.\n */\n readonly service?: string;\n}\n\n/**\n * Config groups, keyed by the name a feature asks for. `env` is what lands in\n * `.env.example`, `schema` the zod line, `field` the `AppConfig` member and `map`\n * how the flat variable becomes the shaped one - the four things\n * `examples/full/src/config.ts` states for every group at once, split so a\n * selection can state only its own.\n */\nexport interface ConfigGroup {\n readonly schema: readonly string[];\n readonly field: string;\n readonly map: string;\n readonly env: readonly { readonly name: string; readonly value: string }[];\n}\n\nexport const CONFIG_GROUPS: Readonly<Record<string, ConfigGroup>> =\n Object.freeze({\n port: {\n schema: [\n 'PORT: z.coerce.number().int().min(0).max(65535).default(3000),',\n ],\n field: 'readonly port: number;',\n map: 'port: value.PORT,',\n env: [{ name: 'PORT', value: '3000' }],\n },\n appName: {\n schema: [],\n field: 'readonly appName: string;',\n map: \"appName: '__DUNX_APP_NAME__',\",\n env: [],\n },\n log: {\n schema: [\n 'LOG_LEVEL: z.enum(LogLevel).default(LogLevel.INFO),',\n '/** Unset means console only. Set it to also append JSON to a rotating file. */',\n 'LOG_FILE: z.string().optional(),',\n ],\n field:\n 'readonly log: { readonly level: LogLevel; readonly file: string | undefined };',\n map: 'log: { level: value.LOG_LEVEL, file: value.LOG_FILE },',\n env: [{ name: 'LOG_LEVEL', value: 'info' }],\n },\n corsOrigin: {\n schema: [\"CORS_ORIGIN: z.string().default('https://example.com'),\"],\n field: 'readonly corsOrigin: string;',\n map: 'corsOrigin: value.CORS_ORIGIN,',\n env: [{ name: 'CORS_ORIGIN', value: 'https://example.com' }],\n },\n database: {\n schema: [\n '/** `:memory:` needs no server and leaves nothing behind, so restarts are clean. */',\n \"DATABASE_FILE: z.string().default(':memory:'),\",\n ],\n field: 'readonly database: { readonly file: string };',\n map: 'database: { file: value.DATABASE_FILE },',\n env: [{ name: 'DATABASE_FILE', value: ':memory:' }],\n },\n redis: {\n schema: [\n '/** Absent is fine: the cache routes report themselves degraded instead of failing. */',\n 'REDIS_URL: z.string().optional(),',\n ],\n field: 'readonly redis: { readonly url: string | undefined };',\n map: 'redis: { url: value.REDIS_URL },',\n env: [{ name: 'REDIS_URL', value: 'redis://localhost:6379' }],\n },\n images: {\n schema: [\n 'IMAGE_QUALITY: z.coerce.number().int().min(1).max(100).default(82),',\n ],\n field: 'readonly images: { readonly quality: number };',\n map: 'images: { quality: value.IMAGE_QUALITY },',\n env: [{ name: 'IMAGE_QUALITY', value: '82' }],\n },\n auth: {\n schema: [\n '/** better-auth signs session cookies with this. 32 characters is its own minimum. */',\n \"AUTH_SECRET: z.string().min(32).default('dunx-development-secret-not-for-production'),\",\n ],\n field: 'readonly auth: { readonly secret: string };',\n map: 'auth: { secret: value.AUTH_SECRET },',\n env: [\n {\n name: 'AUTH_SECRET',\n value: 'change-me-to-at-least-32-characters-long',\n },\n ],\n },\n seedUsers: {\n schema: [],\n field: 'readonly seedUsers: readonly string[];',\n map: \"seedUsers: ['ada', 'grace'],\",\n env: [],\n },\n authorization: {\n schema: [],\n field: 'readonly authorization: { readonly enabled: boolean };',\n map: 'authorization: { enabled: true },',\n env: [],\n },\n });\n\n/** Always present, whatever is selected: the port and the logger need them. */\nexport const BASE_CONFIG: readonly string[] = ['appName', 'port', 'log'];\n\nexport const FEATURES: readonly Feature[] = [\n {\n name: 'notes',\n source: 'notes',\n summary: 'CRUD routes with zod validation. The smallest real feature.',\n requires: [],\n module: { klass: 'NotesModule', from: './notes/notes.module.js' },\n dependencies: ['@dunx/openapi', 'zod'],\n config: [],\n },\n {\n name: 'openapi',\n source: 'docs',\n summary: 'OpenAPI 3.1 from the routes own schemas, plus the explorer page.',\n requires: [],\n module: { klass: 'DocsModule', from: './docs/docs.module.js' },\n dependencies: ['@dunx/openapi', 'zod'],\n config: [],\n },\n {\n name: 'http',\n source: 'http',\n summary: 'CORS, a request-logging middleware and error mapping.',\n requires: [],\n module: { klass: 'HttpModule', from: './http/http.module.js' },\n dependencies: [],\n config: ['corsOrigin'],\n },\n {\n name: 'guards',\n source: 'guards',\n summary:\n 'Route guards with @Roles and @Public, and a protected controller.',\n requires: [],\n module: { klass: 'GuardsModule', from: './guards/guards.module.js' },\n dependencies: ['zod'],\n config: ['authorization'],\n },\n {\n name: 'database',\n source: 'database',\n summary: 'drizzle over bun:sqlite, with a schema, seeds and migrations.',\n requires: [],\n module: { klass: 'DatabaseModule', from: './database/database.module.js' },\n dependencies: ['@dunx/infra', 'drizzle-orm', 'zod'],\n config: ['database'],\n },\n {\n name: 'users',\n source: 'users',\n summary: 'A repository, a service and validated routes over the database.',\n requires: ['database'],\n module: { klass: 'UsersModule', from: './users/users.module.js' },\n dependencies: ['@dunx/infra', 'drizzle-orm', 'zod'],\n config: ['appName', 'seedUsers'],\n },\n {\n name: 'auth',\n source: 'auth',\n summary: 'better-auth mounted, with SessionGuard and an audit trail.',\n requires: ['database'],\n module: { klass: 'AccountsModule', from: './auth/auth.module.js' },\n dependencies: ['@dunx/auth', 'better-auth', 'drizzle-orm'],\n config: ['auth', 'port'],\n },\n {\n name: 'cache',\n source: 'cache',\n summary: 'Bun.RedisClient behind a session store, degrading when absent.',\n requires: [],\n module: { klass: 'CacheModule', from: './cache/cache.module.js' },\n dependencies: ['@dunx/infra', 'zod'],\n config: ['redis'],\n service: 'Redis or Valkey',\n },\n {\n name: 'websockets',\n source: 'chat',\n summary: 'A @Gateway with @OnMessage events, PubSub and a Redis relay.',\n // `cache` joined this list for the same reason `files` joined health's: the gateway\n // injects `RedisConnection` for cross-process fan-out, and a module now has to\n // import the one that provides it. The summary already said \"and a Redis relay\".\n requires: ['cache'],\n module: { klass: 'ChatModule', from: './chat/chat.module.js' },\n dependencies: ['@dunx/infra'],\n config: [],\n service: 'Redis or Valkey, for multi-node fan-out only',\n },\n {\n name: 'images',\n source: 'pictures',\n summary: 'Bun.Image resizing and format conversion behind a route.',\n requires: [],\n module: { klass: 'PicturesModule', from: './pictures/pictures.module.js' },\n dependencies: ['@dunx/infra', 'zod'],\n config: ['images'],\n },\n {\n name: 'files',\n source: 'storage',\n summary: 'Uploads and downloads on Bun.file, with a workspace root.',\n requires: [],\n module: { klass: 'StorageModule', from: './storage/storage.module.js' },\n dependencies: ['@dunx/infra', 'zod'],\n config: [],\n },\n {\n name: 'jobs',\n source: 'jobs',\n summary: 'bullmq queues and a worker, over Bun.RedisClient.',\n requires: ['images'],\n module: { klass: 'JobsModule', from: './jobs/jobs.module.js' },\n dependencies: ['@dunx/infra', 'bullmq', 'ioredis', 'zod'],\n config: ['redis'],\n service: 'Redis or Valkey',\n },\n {\n name: 'health',\n source: 'health',\n summary: 'One endpoint reporting which parts are live and which degraded.',\n // `files` joined this list when module scoping made the dependency explicit: the\n // controller injects `Storage`, so the module has to import the one that provides\n // it. Selecting health without files used to typecheck and fail at boot.\n requires: ['cache', 'database', 'files'],\n module: { klass: 'HealthModule', from: './health/health.module.js' },\n dependencies: ['@dunx/infra'],\n config: ['appName'],\n },\n];\n\nexport const featureNames: readonly string[] = FEATURES.map(\n (feature) => feature.name,\n);\n\nconst byName = new Map(FEATURES.map((feature) => [feature.name, feature]));\n\nexport class UnknownFeatureError extends Error {\n override readonly name = 'UnknownFeatureError';\n}\n\n/**\n * The selection plus everything it requires, in **import order** - which is\n * construction order, and shutdown runs in reverse. A feature is emitted after\n * everything it requires, so the database outlives the features reading it, the\n * same ordering `examples/full/src/app.module.ts` states by hand.\n *\n * Depth-first over `requires`, with a visited set, so a diamond resolves once and\n * the result is stable whatever order the caller asked in.\n */\nexport const resolveFeatures = (\n requested: readonly string[],\n): readonly Feature[] => {\n const unknown = requested.filter((name) => !byName.has(name));\n if (unknown.length > 0) {\n throw new UnknownFeatureError(\n `Unknown feature${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}. ` +\n `Available: ${featureNames.join(', ')}.`,\n );\n }\n\n const ordered: Feature[] = [];\n const seen = new Set<string>();\n const rank = new Map(FEATURES.map((feature, at) => [feature.name, at]));\n\n const visit = (name: string): void => {\n if (seen.has(name)) return;\n seen.add(name);\n const feature = byName.get(name);\n if (!feature) return;\n // Requirements in **registry order**, not in the order this feature happens to\n // list them: two independent requirements would otherwise come out in the\n // order they were typed, which is not a statement about construction order and\n // would make `requires: ['cache', 'database']` build the cache first.\n for (const required of [...feature.requires].sort(\n (left, right) => (rank.get(left) ?? 0) - (rank.get(right) ?? 0),\n )) {\n visit(required);\n }\n ordered.push(feature);\n };\n\n // Registry order, not request order, so two runs asking for the same set in a\n // different order generate byte-identical files.\n for (const feature of FEATURES) {\n if (requested.includes(feature.name)) visit(feature.name);\n }\n\n return ordered;\n};\n\n/** Which of the resolved features the caller did not ask for. */\nexport const impliedBy = (\n requested: readonly string[],\n resolved: readonly Feature[],\n): readonly string[] =>\n resolved\n .map((feature) => feature.name)\n .filter((name) => !requested.includes(name));\n",
6
- "import { existsSync, readdirSync } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Glob } from 'bun';\nimport { resolveFeatures, type Feature } from './features.js';\nimport {\n appModule,\n bootstrap,\n config,\n configGroupsFor,\n envExample,\n main,\n manifest,\n readme,\n worker,\n} from './generate.js';\n\n/** The templates that ship with the package, as `templates/<name>/`. */\nexport const TEMPLATES = Object.freeze(['minimal'] as const);\nexport type TemplateName = (typeof TEMPLATES)[number];\n\n/**\n * Every `@dunx/*` version in a template manifest is this placeholder. Versioning\n * is lockstep, so the right version to install is whatever version of\n * `@dunx/create-app` is doing the scaffolding - resolved at run time rather than\n * written into the template, which would go stale on the next release.\n */\nexport const VERSION_PLACEHOLDER = '__DUNX_VERSION__';\n\n/**\n * Names a package cannot ship as-is, so they ship prefixed and are renamed on write.\n *\n * `.gitignore` is the known one: npm renames a published copy to `.npmignore`.\n *\n * **`bunfig.toml` is the one that was silently missing.** It is stripped from the\n * tarball entirely - presumably so a dependency cannot hijack the installing\n * project's Bun config - and it is the single file dunx asks an app to have. Every\n * app scaffolded from a published `@dunx/create-app` therefore had no\n * `@dunx/transform/preload`, and failed at boot with the very error the guide\n * describes. Measured with `bun pm pack`, and `pack.test.ts` now measures it on\n * every run rather than trusting this comment.\n */\nconst RENAMED = Object.freeze({\n _gitignore: '.gitignore',\n '_bunfig.toml': 'bunfig.toml',\n});\n\n/**\n * Entries that do not make a directory non-empty for scaffolding purposes.\n *\n * `.git` is the one that matters: `git init` then scaffold into the repo is the\n * documented way to start, and refusing it blocks the flow outright. `.gitkeep`\n * exists only so git can track an otherwise empty directory, so it *means* empty.\n * `.DS_Store` appears from merely opening the folder in Finder. `LICENSE` is what\n * GitHub's create-a-repository flow leaves in a fresh clone.\n *\n * The list is deliberately short, and the test for it is whether the template\n * writes that name. It does not write any of these four, so ignoring them can\n * never destroy anything. `.gitignore` and `README.md` are excluded for exactly\n * that reason: the template writes both, and silently overwriting a user's copy\n * is what `--force` exists to gate.\n */\nconst IGNORED_WHEN_EMPTY: ReadonlySet<string> = new Set([\n '.DS_Store',\n '.git',\n '.gitkeep',\n 'LICENSE',\n]);\n\nexport interface ScaffoldOptions {\n /** Directory to create. Relative paths resolve against `cwd`. */\n readonly target: string;\n /** Package name for the generated app. Defaults to the target's basename. */\n readonly name?: string;\n readonly template?: TemplateName;\n /**\n * Features to compose the app from, by name. Anything they require is pulled in.\n *\n * Passing any switches from copying a fixed template to generating the wiring\n * around the chosen feature directories - see `generate.ts`. An empty list, or\n * none at all, scaffolds `template` unchanged, so the default behaviour is exactly\n * what it was.\n */\n readonly features?: readonly string[];\n /** Write into a directory that already has files in it. */\n readonly force?: boolean;\n readonly cwd?: string;\n /** Overrides the version written into the generated manifest. */\n readonly version?: string;\n}\n\nexport interface ScaffoldResult {\n readonly directory: string;\n readonly name: string;\n readonly template: TemplateName | 'composed';\n /** Resolved feature names, in import order. Empty for a fixed template. */\n readonly features: readonly string[];\n readonly files: readonly string[];\n}\n\nexport class ScaffoldError extends Error {\n override readonly name = 'ScaffoldError';\n}\n\n/**\n * `dist/index.js` and `dist/cli.js` both sit one level under the package root, so\n * `../templates` resolves the same from either. In the source tree it resolves\n * from `src/`, which is the same depth - so tests exercise the real path rather\n * than a special case.\n *\n * `fileURLToPath`, not `new URL(...).pathname`: the latter stays percent-encoded,\n * so an install under a directory with a space in it looks for `space%20test/`\n * and reports the template missing. On Windows it is worse - it yields a\n * leading-slash, drive-lettered path that resolves nowhere.\n */\nconst templatesRoot = (): string =>\n resolve(dirname(fileURLToPath(import.meta.url)), '..', 'templates');\n\n/**\n * npm forbids uppercase and a leading dot or underscore, and a scope is legal.\n * Checked here because the failure would otherwise surface as a confusing\n * `bun install` error inside a directory the user just created.\n */\nconst isValidPackageName = (name: string): boolean =>\n /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);\n\nconst readPackageVersion = async (): Promise<string> => {\n const file = Bun.file(join(templatesRoot(), '..', 'package.json'));\n const json = (await file.json()) as { version?: string };\n return json.version ?? '0.0.0';\n};\n\n/** Placeholders are substituted in every written file, generated or copied. */\nconst fill = (contents: string, name: string, version: string): string =>\n contents\n .replaceAll(VERSION_PLACEHOLDER, version)\n .replaceAll('__DUNX_APP_NAME__', name);\n\n/**\n * The four files a subset of features cannot copy, because the full example states\n * every feature at once in each of them.\n */\nconst generated = (\n name: string,\n features: readonly Feature[],\n): Readonly<Record<string, string>> => {\n const groups = configGroupsFor(features);\n const files: Record<string, string> = {\n 'package.json': manifest(features),\n 'README.md': readme(name, features),\n '.env.example': envExample(groups),\n 'src/main.ts': main(name, features),\n 'src/bootstrap.ts': bootstrap(name, features),\n 'src/app.module.ts': appModule(name, features),\n 'src/config.ts': config(name, groups),\n };\n if (features.some((feature) => feature.name === 'jobs')) {\n files['src/worker.ts'] = worker(name);\n }\n return files;\n};\n\nexport const scaffold = async (\n options: ScaffoldOptions,\n): Promise<ScaffoldResult> => {\n const template = options.template ?? 'minimal';\n if (!TEMPLATES.includes(template)) {\n throw new ScaffoldError(\n `Unknown template \"${template}\". Available: ${TEMPLATES.join(', ')}.`,\n );\n }\n\n // Resolved before anything is written, so an unknown feature name fails with the\n // list of real ones rather than half a directory.\n const requested = options.features ?? [];\n let features: readonly Feature[] = [];\n try {\n features = resolveFeatures(requested);\n } catch (error) {\n throw new ScaffoldError(\n error instanceof Error ? error.message : String(error),\n );\n }\n const composing = features.length > 0;\n\n const directory = resolve(options.cwd ?? process.cwd(), options.target);\n const name = options.name ?? basename(directory);\n\n if (!isValidPackageName(name)) {\n throw new ScaffoldError(\n `\"${name}\" is not a usable package name. Pass --name to choose one.`,\n );\n }\n\n if (existsSync(directory) && options.force !== true) {\n const blocking = readdirSync(directory).filter(\n (entry) => !IGNORED_WHEN_EMPTY.has(entry),\n );\n if (blocking.length > 0) {\n // Naming what blocked it, because `.git` used to block it and the message\n // gave no way to tell that from a directory of real work.\n const shown = blocking.sort().slice(0, 3).join(', ');\n const rest = blocking.length > 3 ? `, +${blocking.length - 3} more` : '';\n throw new ScaffoldError(\n `${directory} is not empty (${shown}${rest}). ` +\n `Pass --force to write into it anyway.`,\n );\n }\n }\n\n const version = options.version ?? `^${await readPackageVersion()}`;\n const written: string[] = [];\n\n /** Copies a directory of the package's own templates into the new app. */\n const copyTree = async (from: string, into: string): Promise<void> => {\n // `**/*` with `dot: true` so a template can carry a dotfile that npm did not\n // rename; the explicit `_gitignore` mapping covers the one that it does.\n for await (const relative of new Glob('**/*').scan({\n cwd: from,\n dot: true,\n onlyFiles: true,\n })) {\n const base = relative.split('/').at(-1) ?? relative;\n const renamed = (RENAMED as Record<string, string | undefined>)[base];\n const target = join(\n into,\n renamed === undefined ? relative : join(dirname(relative), renamed),\n );\n\n const contents = await Bun.file(join(from, relative)).text();\n // `Bun.write` creates parent directories, so there is no mkdir pass.\n await Bun.write(join(directory, target), fill(contents, name, version));\n written.push(target);\n }\n };\n\n if (!composing) {\n const source = join(templatesRoot(), template);\n if (!existsSync(source)) {\n throw new ScaffoldError(\n `Template \"${template}\" is missing from ${source}.`,\n );\n }\n await copyTree(source, '.');\n return {\n directory,\n name,\n template,\n features: [],\n files: written.sort(),\n };\n }\n\n // The base carries what every composed app needs and no feature owns: the\n // tsconfig, the transform preload, and the gitignore.\n const base = join(templatesRoot(), 'base');\n if (!existsSync(base)) {\n throw new ScaffoldError(\n `The base template is missing from ${base}. Run \\`bun run sync:templates\\`.`,\n );\n }\n await copyTree(base, '.');\n\n for (const feature of features) {\n const from = join(templatesRoot(), 'features', feature.source);\n if (!existsSync(from)) {\n throw new ScaffoldError(\n `Feature \"${feature.name}\" is missing from ${from}. ` +\n 'Run `bun run sync:templates`.',\n );\n }\n await copyTree(from, join('src', feature.source));\n }\n\n for (const [target, contents] of Object.entries(generated(name, features))) {\n await Bun.write(join(directory, target), fill(contents, name, version));\n written.push(target);\n }\n\n return {\n directory,\n name,\n template: 'composed',\n features: features.map((feature) => feature.name),\n files: written.sort(),\n };\n};\n",
7
- "import { BASE_CONFIG, CONFIG_GROUPS, type Feature } from './features.js';\n\n/**\n * The wiring, generated from a feature selection.\n *\n * The full example states every feature at once in four files - `app.module.ts`,\n * `config.ts`, `bootstrap.ts` and `main.ts` - so those are the ones a subset cannot\n * copy. Everything else is the feature's own directory, copied verbatim.\n *\n * Generated rather than assembled by editing a copy on purpose: an edited copy\n * cannot be checked against the example it came from, and the byte-for-byte parity\n * test is what stops the vendored features drifting from the app CI actually boots.\n */\nconst HEADER = (name: string): string =>\n `// Generated by @dunx/create-app for ${name}. Yours to edit.\\n`;\n\nconst uniq = (values: readonly string[]): string[] => [...new Set(values)];\n\n/** Every config group the selection needs, base first, in a stable order. */\nexport const configGroupsFor = (\n features: readonly Feature[],\n): readonly string[] => {\n const wanted = uniq([\n ...BASE_CONFIG,\n ...features.flatMap((feature) => feature.config),\n ]);\n return Object.keys(CONFIG_GROUPS).filter((group) => wanted.includes(group));\n};\n\nexport const dependenciesFor = (\n features: readonly Feature[],\n): readonly string[] =>\n uniq([\n '@dunx/core',\n '@dunx/http',\n '@dunx/transform',\n '@dunx/infra',\n ...features.flatMap((feature) => feature.dependencies),\n ]).sort();\n\nconst DUNX = /^@dunx\\//;\n\nexport const manifest = (features: readonly Feature[]): string => {\n const deps = dependenciesFor(features);\n const dependencies: Record<string, string> = {};\n for (const dep of deps) {\n dependencies[dep] = DUNX.test(dep) ? '__DUNX_VERSION__' : versionOf(dep);\n }\n\n const scripts: Record<string, string> = {\n start: 'bun src/main.ts',\n test: 'bun test',\n typecheck: 'tsc --noEmit',\n };\n // A queue needs a process to drain it, and it is not the web one.\n if (features.some((feature) => feature.name === 'jobs')) {\n scripts['worker'] = 'bun src/worker.ts';\n }\n\n return `${JSON.stringify(\n {\n name: '__DUNX_APP_NAME__',\n version: '0.1.0',\n private: true,\n type: 'module',\n scripts,\n dependencies,\n devDependencies: {\n '@dunx/testing': '__DUNX_VERSION__',\n '@types/bun': '>=1.3.0',\n typescript: '^5.7.0',\n },\n engines: { bun: '>=1.3.0' },\n },\n null,\n 2,\n )}\\n`;\n};\n\n/**\n * Third-party ranges, pinned here rather than read off `examples/full` at run time:\n * the generated app installs from npm and the example installs from the workspace,\n * so the example's manifest is not a statement about what a consumer should take.\n * `features.test.ts` checks these against the example's, which is what stops them\n * silently diverging from a version combination that is actually exercised.\n */\nexport const THIRD_PARTY: Readonly<Record<string, string>> = Object.freeze({\n zod: '^4.4.3',\n 'drizzle-orm': '^0.45.2',\n 'better-auth': '^1.6.25',\n bullmq: '^6.0.5',\n ioredis: '^6.0.0',\n});\n\nconst versionOf = (dep: string): string => THIRD_PARTY[dep] ?? 'latest';\n\nexport const appModule = (\n name: string,\n features: readonly Feature[],\n): string => {\n const needsLogger = true;\n const imports = [\n \"import { ConfigModule, Module } from '@dunx/core';\",\n ...(needsLogger\n ? [\"import { LoggerModule } from '@dunx/infra/logger';\"]\n : []),\n \"import { AppConfigService, validate } from './config.js';\",\n ...features.map(\n (feature) =>\n `import { ${feature.module.klass} } from '${feature.module.from}';`,\n ),\n ];\n\n const moduleImports = [\n 'ConfigModule.forRoot({ validate, as: AppConfigService }),',\n '// The level comes from the validated config, which is the one thing a',\n '// zero-argument `forRoot` function cannot reach.',\n 'LoggerModule.forRootAsync(',\n ' {',\n ' useFactory: (config: AppConfigService) => ({',\n \" name: config.get('appName'),\",\n \" level: config.get('log').level,\",\n ' }),',\n ' inject: [AppConfigService] as const,',\n ' },',\n ' { captureGlobalErrors: true },',\n '),',\n ...features.map((feature) => `${feature.module.klass},`),\n ];\n\n return `${HEADER(name)}${imports.join('\\n')}\n\n/**\n * Import order is construction order, and shutdown runs in reverse - so config and\n * the logger are built first and torn down last, and anything a feature depends on\n * outlives it.\n */\n@Module({\n imports: [\n${moduleImports.map((line) => ` ${line}`).join('\\n')}\n ],\n})\nexport class AppModule {}\n`;\n};\n\nexport const config = (name: string, groups: readonly string[]): string => {\n const chosen = groups\n .map((group) => [group, CONFIG_GROUPS[group]] as const)\n .filter(\n (entry): entry is [string, (typeof CONFIG_GROUPS)[string]] =>\n entry[1] !== undefined,\n );\n\n const schema = chosen.flatMap(([, group]) => group.schema);\n const needsLogLevel = groups.includes('log');\n\n return `${HEADER(name)}import { ConfigService, type ConfigSource${\n needsLogLevel ? ', LogLevel' : ''\n } } from '@dunx/core';\nimport { z } from 'zod';\n\n/**\n * One validation function, which is the whole \\`ConfigModule\\` contract. dunx does\n * not pick the library - this is zod because the routes already use it, and a\n * hand-written function that throws would work identically.\n *\n * \\`.default()\\` is where a value comes from when the variable is unset, so a clean\n * checkout boots with no \\`.env\\` at all. Bun loads \\`.env\\` and \\`.env.local\\` itself,\n * so there is nothing here that reads a file.\n */\nconst envSchema = z.object({\n${schema.map((line) => ` ${line}`).join('\\n')}\n});\n\nexport interface AppConfig {\n${chosen.map(([, group]) => ` ${group.field}`).join('\\n')}\n}\n\n/**\n * One name for the typed config everywhere. A subclass rather than\n * \\`ConfigService<AppConfig>\\` at each site because a factory's \\`inject: [...]\\`\n * carries no type argument - the class does, and it is a real runtime value, so it\n * is both a precise token and a usable constructor annotation.\n */\nexport class AppConfigService extends ConfigService<AppConfig> {}\n\n/** The one broker channel the websocket relay carries every topic on. */\nexport const RELAY_CHANNEL = '__DUNX_APP_NAME__:ws';\n\n/** Flat variables in, a shaped object out. Nothing downstream reads \\`Bun.env\\`. */\nexport const validate = (env: ConfigSource): AppConfig => {\n const parsed = envSchema.safeParse(env);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((issue) => \\`\\${issue.path.join('.') || '(root)'}: \\${issue.message}\\`)\n .join('\\\\n - ');\n throw new Error(\\`Configuration is invalid:\\\\n - \\${issues}\\`);\n }\n const value = parsed.data;\n\n return {\n${chosen.map(([, group]) => ` ${group.map}`).join('\\n')}\n };\n};\n`;\n};\n\nconst has = (features: readonly Feature[], name: string): boolean =>\n features.some((feature) => feature.name === name);\n\nexport const bootstrap = (\n name: string,\n features: readonly Feature[],\n): string => {\n const openapi = has(features, 'openapi');\n const websockets = has(features, 'websockets');\n const http = has(features, 'http');\n\n const imports = [\n `import { HttpFactory${websockets ? ', RedisRelay' : ''}, type HttpApp } from '@dunx/http';`,\n ...(openapi ? [\"import { OpenApiModule } from '@dunx/openapi';\"] : []),\n \"import { AppModule } from './app.module.js';\",\n `import { ${[\n ...(http ? ['AppConfigService'] : []),\n ...(websockets ? ['RELAY_CHANNEL'] : []),\n ].join(', ')} } from './config.js';`,\n ...(http\n ? [\"import { RequestLoggerMiddleware } from './http/request-log.js';\"]\n : []),\n ].filter((line) => !line.includes('{ }'));\n\n const root = openapi\n ? `OpenApiModule.forRoot({\n title: '__DUNX_APP_NAME__',\n version: '0.1.0',\n root: AppModule,\n })`\n : 'AppModule';\n\n const options = websockets\n ? [\n '// Multi-node websocket fan-out on `Bun.RedisClient`, so it costs no',\n '// dependency. With no Redis running this degrades to single-process',\n '// behaviour, logs one warning, and the app still boots.',\n 'websocket: { idleTimeout: 30 },',\n 'relay: new RedisRelay({ connectionTimeout: 500 }),',\n 'relayChannel: RELAY_CHANNEL,',\n ]\n : [];\n\n /**\n * Everything between `create()` and `listen()`. The prefix is set whatever is\n * selected, because the copied controllers declare paths under it and the URLs\n * `main.ts` prints assume it.\n *\n * `app.use` takes the middleware **class**, not an instance: the container\n * constructs it, which is what lets it have dependencies of its own.\n */\n const shaping = [\n \"app.setGlobalPrefix('api');\",\n ...(http\n ? [\n 'app.use(RequestLoggerMiddleware);',\n \"app.set('trust proxy', true);\",\n 'app.enableCors({',\n \" origin: app.get(AppConfigService).get('corsOrigin'),\",\n ' credentials: true,',\n ' maxAge: 600,',\n '});',\n ]\n : []),\n ];\n\n return `${HEADER(name)}${imports.join('\\n')}\n\n/**\n * One app, built the same way for \\`bun start\\` and for the tests - so what the\n * tests exercise is what actually serves.\n *\n * \\`create()\\` boots the container and discovers routes and gateways; \\`listen()\\` is\n * what builds the \\`Bun.serve\\` route table. Everything between the two still gets to\n * shape it, and after \\`listen()\\` every one of those throws.\n */\nexport const createApp = async (): Promise<HttpApp> => {\n const app = await HttpFactory.create(\n ${root}${\n options.length === 0\n ? ',\\n'\n : `,\n {\n${options.map((line) => ` ${line}`).join('\\n')}\n },\n`\n } );\n\n${shaping.map((line) => ` ${line}`).join('\\n')}\n\n return app;\n};\n`;\n};\n\nexport const main = (name: string, features: readonly Feature[]): string => {\n const health = has(features, 'health');\n const openapi = has(features, 'openapi');\n\n const lines = [\n ...(openapi\n ? [\n \"logger.info(`docs ${new URL('api/docs', url).href}`);\",\n \"logger.info(`openapi ${new URL('api/openapi.json', url).href}`);\",\n ]\n : []),\n ...(health\n ? [\"logger.info(`health ${new URL('api/health', url).href}`);\"]\n : []),\n ];\n\n return `${HEADER(name)}import { Logger } from '@dunx/core';\nimport { createApp } from './bootstrap.js';\nimport { AppConfigService } from './config.js';\n\nasync function bootstrap(): Promise<void> {\n const app = await createApp();\n app.enableShutdownHooks();\n\n const config = app.get(AppConfigService);\n const logger = app.get(Logger);\n const url = await app.listen(config.get('port'));\n\n logger.info(\\`listening on \\${url}\\`);\n${lines.map((line) => ` ${line}`).join('\\n')}${lines.length > 0 ? '\\n' : ''}\n // Nothing else to do: the server holds the process open, and the shutdown hooks\n // resolve this once a signal arrives.\n await app.closed;\n}\n\nbootstrap().catch((error: unknown) => {\n console.error('failed to start', error);\n process.exit(1);\n});\n`;\n};\n\n/** The queue worker, only when a queue was asked for. */\nexport const worker = (name: string): string =>\n `${HEADER(name)}import { AppFactory } from '@dunx/core';\nimport { AppModule } from './app.module.js';\n\n/**\n * A queue needs a process to drain it, and it is deliberately not the web one: a\n * worker that shares the server's event loop competes with request handling.\n */\nconst app = await AppFactory.create(AppModule);\napp.enableShutdownHooks();\nawait app.closed;\n`;\n\nexport const envExample = (groups: readonly string[]): string => {\n const lines = groups\n .flatMap((group) => CONFIG_GROUPS[group]?.env ?? [])\n .map((entry) => `${entry.name}=${entry.value}`);\n\n return lines.length === 0\n ? '# Every variable has a default, so this file is optional.\\n'\n : `# Every variable here has a default, so the app boots with no .env at all.\\n${lines.join('\\n')}\\n`;\n};\n\nexport const readme = (name: string, features: readonly Feature[]): string => {\n const services = features.filter((feature) => feature.service !== undefined);\n\n return `# ${name}\n\nScaffolded with \\`bunx @dunx/create-app\\`.\n\n\\`\\`\\`bash\nbun install\nbun run start\n\\`\\`\\`\n\n## What is wired up\n\n${\n features.length === 0\n ? 'Nothing beyond the base app.'\n : features\n .map((feature) => `- **${feature.name}** - ${feature.summary}`)\n .join('\\n')\n}\n\n${\n services.length === 0\n ? ''\n : `## Services\n\nThese features need something running. Each one degrades rather than failing the\nboot, so the app still starts without them.\n\n${services.map((feature) => `- **${feature.name}** needs ${feature.service}`).join('\\n')}\n\n`\n}## Layout\n\n- \\`src/main.ts\\` - the entry point\n- \\`src/bootstrap.ts\\` - builds the app; shared by \\`start\\` and the tests\n- \\`src/app.module.ts\\` - the root module, importing every feature\n- \\`src/config.ts\\` - one validation function, flat env in and a shaped object out\n${features.map((feature) => `- \\`src/${feature.source}/\\` - ${feature.name}`).join('\\n')}\n\n\\`main.ts\\`, \\`bootstrap.ts\\`, \\`app.module.ts\\` and \\`config.ts\\` were generated for the\nfeatures you chose; everything else is copied from dunx's \\`examples/full\\`, which is\nrun and toured in CI on every push. The \\`*.demo.ts\\` files are that example's\nscripted walkthroughs - delete one and its \\`providers\\` entry when you do not want it.\n\n## Constructor injection\n\n\\`bunfig.toml\\` preloads \\`@dunx/transform\\`, which records each class's constructor\nparameter types so the container can resolve them. Without that line providers are\nbuilt with no arguments and boot fails saying so.\n`;\n};\n"
8
- ],
9
- "mappings": "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",
10
- "debugId": "4D36F643251D18C264756E2164756E21",
11
- "names": []
12
- }