@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 +14 -2
- package/dist/agents.d.ts +5 -0
- package/dist/{chunk-nn9ekg83.js → chunk-x7s3axkt.js} +117 -4
- package/dist/cli.js +12 -3
- package/dist/index.js +1 -1
- package/dist/stdin.d.ts +14 -0
- package/package.json +1 -1
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` |
|
|
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.
|
|
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
|
package/dist/agents.d.ts
ADDED
|
@@ -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
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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-
|
|
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
|
|
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
package/dist/stdin.d.ts
ADDED
|
@@ -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>;
|