@dunx/create-app 3.0.2 → 3.0.3

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 CHANGED
@@ -21,11 +21,18 @@ bun run start # http://localhost:3000/greetings
21
21
  | Flag | Default | Meaning |
22
22
  | ------------------- | ------------------ | -------------------------------------------------- |
23
23
  | `--name <name>` | the directory name | Package name for the generated app |
24
+ | `--with <a,b,c>` | | Features to compose the app from |
25
+ | `--all` | off | Every feature |
26
+ | `--list` | | Print the features and exit |
24
27
  | `--template <name>` | `minimal` | Which template to write |
25
28
  | `--force` | off | Write into a directory that already has files |
26
- | `--yes`, `-y` | | Accepted and ignored; nothing here ever prompts |
29
+ | `--yes`, `-y` | off | Take the minimal template without prompting |
27
30
  | `--help` | | Print usage |
28
31
 
32
+ With neither `--with` nor `--yes`, and a terminal attached, it lists the features
33
+ and reads one line of stdin. Piped or in CI it never prompts, so **an agent or a
34
+ script should pass `--yes` or `--with`**.
35
+
29
36
  The name is validated against npm's rules **before** anything is created, because
30
37
  an invalid one would otherwise surface as a confusing `bun install` failure inside
31
38
  a directory you just made.
@@ -49,7 +56,12 @@ The `minimal` template, the same app as
49
56
  server, and the `bunfig.toml` preload line that makes constructor injection work.
50
57
 
51
58
  Its `src/` is a **byte-for-byte copy** of that example, and a test in this package
52
- fails if the two ever drift. The example is the one CI boots, so keeping them
59
+ fails if the two ever drift.
60
+
61
+ Every app also gets an `AGENTS.md` naming its layout, its commands and the rules
62
+ dunx fails at boot over, plus a `CLAUDE.md` pointing at it. Both link
63
+ <https://petarzarkov.github.io/dunx/setup.md>, which is served per release, rather
64
+ than copying the framework's own instructions into your repository. The example is the one CI boots, so keeping them
53
65
  identical is what makes the template trustworthy rather than merely plausible.
54
66
 
55
67
  ## Two details
@@ -0,0 +1,5 @@
1
+ import type { Feature } from './features.js';
2
+ export declare const CLAUDE_POINTER = "# CLAUDE.md\n\nThe instructions for this application live in `AGENTS.md`, imported below so every\nagent reads one file.\n\n@AGENTS.md\n";
3
+ export declare const agents: (name: string, features: readonly Feature[]) => string;
4
+ /** Both files, written for a fixed template and a composed app alike. */
5
+ export declare const agentFiles: (name: string, features: readonly Feature[]) => Readonly<Record<string, string>>;
@@ -318,6 +318,112 @@ import { basename, dirname, join, resolve } from "path";
318
318
  import { fileURLToPath } from "url";
319
319
  var {Glob } = globalThis.Bun;
320
320
 
321
+ // src/agents.ts
322
+ var SETUP_URL = "https://petarzarkov.github.io/dunx/setup.md";
323
+ var LLMS_URL = "https://petarzarkov.github.io/dunx/llms.txt";
324
+ var CLAUDE_POINTER = `# CLAUDE.md
325
+
326
+ The instructions for this application live in \`AGENTS.md\`, imported below so every
327
+ agent reads one file.
328
+
329
+ @AGENTS.md
330
+ `;
331
+ var RULES = `## Rules that produce a boot error when broken
332
+
333
+ - **No \`@Injectable()\`, no \`@Inject()\`.** Listing a class in a module's
334
+ \`providers\` is what makes it injectable. dunx uses TC39 standard decorators,
335
+ which have no parameter decorators. For a value with no constructor parameter to
336
+ hang off, use \`inject(Token)\` in a field initializer.
337
+ - **Do not add \`reflect-metadata\`, \`experimentalDecorators\` or
338
+ \`emitDecoratorMetadata\`.** \`bunfig.toml\` preloads \`@dunx/transform\`, which
339
+ records each class's constructor parameter types. Removing that line makes every
340
+ provider fail at boot.
341
+ - **A constructor parameter whose type is erased fails at boot, naming the
342
+ parameter.** An interface, a primitive, a union, a class type parameter, or a
343
+ \`import type\` at an injection site all record as \`unresolved\`. Inject a class,
344
+ and drop \`type\` from the import.
345
+ - **Relative imports carry \`.js\`**: \`'./users.service.js'\`, never
346
+ \`'./users.service'\`.
347
+ - **A module's \`exports\` is its public surface.** The container is scoped per
348
+ module, so a provider another module injects has to be exported by the module that
349
+ declares it.
350
+ - **\`bun\` only.** No \`npm\`, \`npx\`, \`yarn\` or \`pnpm\`; run tools with \`bunx\`.
351
+ `;
352
+ var layout = (features) => features.length === 0 ? `- \`src/main.ts\` - the entry point
353
+ - \`src/app.module.ts\` - the root module; \`controllers\` get routes, \`providers\` do not
354
+ - \`src/greetings.controller.ts\`, \`src/greetings.service.ts\` - one route and its provider
355
+ - \`src/app.test.ts\` - the whole app behind a real server on port 0
356
+ - \`bunfig.toml\` - the preload line constructor injection needs
357
+ ` : `- \`src/main.ts\` - the entry point
358
+ - \`src/bootstrap.ts\` - builds the app; shared by \`start\` and the tests
359
+ - \`src/app.module.ts\` - the root module, importing every feature
360
+ - \`src/config.ts\` - one validation function, flat env in and a shaped object out
361
+ ${features.map((feature) => `- \`src/${feature.source}/\` - ${feature.name}`).join(`
362
+ `)}
363
+ - \`bunfig.toml\` - the preload line constructor injection needs
364
+
365
+ \`main.ts\`, \`bootstrap.ts\`, \`app.module.ts\` and \`config.ts\` were generated for the
366
+ features chosen at scaffold time. Everything else was copied from dunx's
367
+ \`examples/full\`. The \`*.demo.ts\` files are that example's scripted walkthroughs;
368
+ delete one and its \`providers\` entry to drop it.
369
+ `;
370
+ var agents = (name, features) => {
371
+ const services = features.filter((feature) => feature.service !== undefined);
372
+ const hasJobs = features.some((feature) => feature.name === "jobs");
373
+ return `# ${name}
374
+
375
+ Notes for an agent working in this application. It is a
376
+ [dunx](https://github.com/petarzarkov/dunx) app, scaffolded by
377
+ \`bunx @dunx/create-app\`.
378
+
379
+ ## Commands
380
+
381
+ \`\`\`bash
382
+ bun install
383
+ bun run start # http://localhost:3000
384
+ bun test
385
+ bun run typecheck
386
+ ${hasJobs ? `bun run worker # drains the queues; the web process does not
387
+ ` : ""}\`\`\`
388
+
389
+ ## Layout
390
+
391
+ ${layout(features)}
392
+ ${features.length === 0 ? "" : `## What is wired up
393
+
394
+ ${features.map((feature) => `- **${feature.name}** - ${feature.summary}`).join(`
395
+ `)}
396
+
397
+ `}${services.length === 0 ? "" : `## Services
398
+
399
+ Each of these reports itself degraded rather than failing the boot, so the app
400
+ starts without them.
401
+
402
+ ${services.map((feature) => `- **${feature.name}** needs ${feature.service}`).join(`
403
+ `)}
404
+
405
+ `}${RULES}
406
+ ## Reading this app instead of grepping it
407
+
408
+ \`\`\`bash
409
+ bunx @dunx/mcp ./src/app.module.ts
410
+ \`\`\`
411
+
412
+ An MCP server over stdio answering what routes, providers, modules and gateways
413
+ exist, and which constructor parameters would fail to resolve. It reads the module
414
+ graph and never boots the app.
415
+
416
+ ## The framework's own instructions
417
+
418
+ - <${SETUP_URL}> - installing, wiring and verifying a dunx app
419
+ - <${LLMS_URL}> - every dunx document, as raw markdown
420
+ `;
421
+ };
422
+ var agentFiles = (name, features) => ({
423
+ "AGENTS.md": agents(name, features),
424
+ "CLAUDE.md": CLAUDE_POINTER
425
+ });
426
+
321
427
  // src/generate.ts
322
428
  var HEADER = (name) => `// Generated by @dunx/create-app for ${name}. Yours to edit.
323
429
  `;
@@ -751,12 +857,19 @@ var scaffold = async (options) => {
751
857
  written.push(target);
752
858
  }
753
859
  };
860
+ const writeAll = async (files) => {
861
+ for (const [target, contents] of Object.entries(files)) {
862
+ await Bun.write(join(directory, target), fill(contents, name, version));
863
+ written.push(target);
864
+ }
865
+ };
754
866
  if (!composing) {
755
867
  const source = join(templatesRoot(), template);
756
868
  if (!existsSync(source)) {
757
869
  throw new ScaffoldError(`Template "${template}" is missing from ${source}.`);
758
870
  }
759
871
  await copyTree(source, ".");
872
+ await writeAll(agentFiles(name, []));
760
873
  return {
761
874
  directory,
762
875
  name,
@@ -777,10 +890,10 @@ var scaffold = async (options) => {
777
890
  }
778
891
  await copyTree(from, join("src", feature.source));
779
892
  }
780
- for (const [target, contents] of Object.entries(generated(name, features))) {
781
- await Bun.write(join(directory, target), fill(contents, name, version));
782
- written.push(target);
783
- }
893
+ await writeAll({
894
+ ...generated(name, features),
895
+ ...agentFiles(name, features)
896
+ });
784
897
  return {
785
898
  directory,
786
899
  name,
package/dist/cli.js CHANGED
@@ -7,11 +7,21 @@ import {
7
7
  featureNames,
8
8
  impliedBy,
9
9
  scaffold
10
- } from "./chunk-nn9ekg83.js";
10
+ } from "./chunk-x7s3axkt.js";
11
11
 
12
12
  // src/cli.ts
13
13
  import { parseArgs } from "util";
14
14
  import { relative } from "path";
15
+
16
+ // src/stdin.ts
17
+ var readLine = async () => {
18
+ for await (const line of console) {
19
+ return typeof line === "string" ? line.trim() : "";
20
+ }
21
+ return "";
22
+ };
23
+
24
+ // src/cli.ts
15
25
  var USAGE = `Scaffold a new dunx application.
16
26
 
17
27
  bunx @dunx/create-app <directory> [options]
@@ -82,8 +92,7 @@ ${featureList()}
82
92
  `);
83
93
  console.log("Names or numbers, comma separated. `all` for everything.");
84
94
  process.stdout.write("> ");
85
- const line = (await console[Symbol.asyncIterator]().next()).value;
86
- const answer = typeof line === "string" ? line.trim() : "";
95
+ const answer = await readLine();
87
96
  if (answer === "")
88
97
  return [];
89
98
  if (answer === "all")
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  TEMPLATES,
5
5
  VERSION_PLACEHOLDER,
6
6
  scaffold
7
- } from "./chunk-nn9ekg83.js";
7
+ } from "./chunk-x7s3axkt.js";
8
8
  export {
9
9
  ScaffoldError,
10
10
  TEMPLATES,
@@ -0,0 +1,14 @@
1
+ /**
2
+ * One line of stdin, with the iteration ended before the value comes back.
3
+ *
4
+ * `console[Symbol.asyncIterator]().next()` on its own leaves stdin referenced for
5
+ * the life of the process: the scaffold wrote its files, printed the next steps and
6
+ * then sat there until the user pressed Ctrl+C. Ending the iteration runs the
7
+ * iterator's `return()`, which releases the handle, so the process exits on its own.
8
+ * Measured on Bun 1.4.0 - docs/bun-apis.md, "One line of stdin keeps the process
9
+ * alive".
10
+ *
11
+ * A function rather than a class because it is the whole module: no state, no
12
+ * configuration, and nothing to hold between calls.
13
+ */
14
+ export declare const readLine: () => Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dunx/create-app",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "Scaffold a new dunx application - bunx @dunx/create-app my-api",
5
5
  "keywords": [
6
6
  "bun",