@webda/create 0.0.0-stage → 4.0.0-beta.5

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.
Files changed (90) hide show
  1. package/LICENSE +166 -0
  2. package/README.md +27 -2
  3. package/agent/.claude-plugin/plugin.json +7 -0
  4. package/agent/AGENTS.md +47 -0
  5. package/agent/CLAUDE.md +3 -0
  6. package/agent/skills/webda-configuration/SKILL.md +61 -0
  7. package/agent/skills/webda-deploy/SKILL.md +103 -0
  8. package/agent/skills/webda-models/SKILL.md +137 -0
  9. package/agent/skills/webda-operations/SKILL.md +82 -0
  10. package/agent/skills/webda-services/SKILL.md +90 -0
  11. package/agent/skills/webda-stores/SKILL.md +66 -0
  12. package/agent/skills/webda-testing/SKILL.md +80 -0
  13. package/lib/agents.d.ts +21 -0
  14. package/lib/agents.d.ts.map +1 -0
  15. package/lib/agents.js +33 -0
  16. package/lib/agents.js.map +1 -0
  17. package/lib/bin.d.ts +3 -0
  18. package/lib/bin.d.ts.map +1 -0
  19. package/lib/bin.js +4 -0
  20. package/lib/bin.js.map +1 -0
  21. package/lib/compose.d.ts +32 -0
  22. package/lib/compose.d.ts.map +1 -0
  23. package/lib/compose.js +51 -0
  24. package/lib/compose.js.map +1 -0
  25. package/lib/generate.d.ts +39 -0
  26. package/lib/generate.d.ts.map +1 -0
  27. package/lib/generate.js +85 -0
  28. package/lib/generate.js.map +1 -0
  29. package/lib/index.d.ts +26 -0
  30. package/lib/index.d.ts.map +1 -0
  31. package/lib/index.js +97 -0
  32. package/lib/index.js.map +1 -0
  33. package/lib/install.d.ts +25 -0
  34. package/lib/install.d.ts.map +1 -0
  35. package/lib/install.js +34 -0
  36. package/lib/install.js.map +1 -0
  37. package/lib/merge.d.ts +8 -0
  38. package/lib/merge.d.ts.map +1 -0
  39. package/lib/merge.js +25 -0
  40. package/lib/merge.js.map +1 -0
  41. package/lib/options.d.ts +64 -0
  42. package/lib/options.d.ts.map +1 -0
  43. package/lib/options.js +125 -0
  44. package/lib/options.js.map +1 -0
  45. package/lib/prompts.d.ts +27 -0
  46. package/lib/prompts.d.ts.map +1 -0
  47. package/lib/prompts.js +73 -0
  48. package/lib/prompts.js.map +1 -0
  49. package/lib/template.d.ts +25 -0
  50. package/lib/template.d.ts.map +1 -0
  51. package/lib/template.js +46 -0
  52. package/lib/template.js.map +1 -0
  53. package/lib/versions.d.ts +22 -0
  54. package/lib/versions.d.ts.map +1 -0
  55. package/lib/versions.js +52 -0
  56. package/lib/versions.js.map +1 -0
  57. package/lib/versions.json +42 -0
  58. package/package.json +44 -3
  59. package/templates/base/files/README.md +16 -0
  60. package/templates/base/files/_gitignore +6 -0
  61. package/templates/base/files/src/models/Project.model.ts +19 -0
  62. package/templates/base/files/src/models/Task.model.ts +24 -0
  63. package/templates/base/files/src/services/task.service.ts +29 -0
  64. package/templates/base/files/test/app.spec.ts +57 -0
  65. package/templates/base/files/tsconfig.json +18 -0
  66. package/templates/base/files/vitest.config.ts +10 -0
  67. package/templates/base/package.json +35 -0
  68. package/templates/base/webda.config.json +19 -0
  69. package/templates/features/graphql/agents.md +1 -0
  70. package/templates/features/graphql/package.json +5 -0
  71. package/templates/features/graphql/webda.config.json +1 -0
  72. package/templates/features/grpc/agents.md +1 -0
  73. package/templates/features/grpc/package.json +5 -0
  74. package/templates/features/grpc/webda.config.json +6 -0
  75. package/templates/features/mcp/agents.md +1 -0
  76. package/templates/features/mcp/package.json +5 -0
  77. package/templates/features/mcp/webda.config.json +1 -0
  78. package/templates/features/store-file/agents.md +1 -0
  79. package/templates/features/store-file/package.json +5 -0
  80. package/templates/features/store-file/webda.config.json +1 -0
  81. package/templates/features/store-mongodb/agents.md +1 -0
  82. package/templates/features/store-mongodb/files/.env.example +1 -0
  83. package/templates/features/store-mongodb/files/docker-compose.yml +8 -0
  84. package/templates/features/store-mongodb/package.json +5 -0
  85. package/templates/features/store-mongodb/webda.config.json +1 -0
  86. package/templates/features/store-postgres/agents.md +1 -0
  87. package/templates/features/store-postgres/files/.env.example +5 -0
  88. package/templates/features/store-postgres/files/docker-compose.yml +9 -0
  89. package/templates/features/store-postgres/package.json +5 -0
  90. package/templates/features/store-postgres/webda.config.json +1 -0
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: webda-services
3
+ description: Use when adding or changing a Webda service, its parameters, its lifecycle or when wiring it in webda.config.json
4
+ ---
5
+
6
+ # Webda services
7
+
8
+ ## When to use
9
+
10
+ Behaviour that is not about a single model: integrations, scheduled work, computations across models. Model-specific behaviour belongs on the model.
11
+
12
+ ## Pattern
13
+
14
+ A service lives in `src/services/<name>.service.ts`, has a parameters class (the file must be named `*.service.ts`: the build fails on a service file without that suffix), and the `@WebdaModda` JSDoc tag so the compiler registers it. It is enabled in `webda.config.json` under `services`, where its parameters are set.
15
+
16
+ ```ts
17
+ import { Service, useLog } from "@webda/core";
18
+
19
+ export class ReminderServiceParameters extends Service.Parameters {
20
+ /**
21
+ * Hours before a reminder is sent
22
+ * @minimum 1
23
+ */
24
+ delayHours: number = 24;
25
+ }
26
+
27
+ /**
28
+ * Sends reminders for open tasks
29
+ *
30
+ * @WebdaModda
31
+ */
32
+ export class ReminderService<T extends ReminderServiceParameters = ReminderServiceParameters> extends Service<T> {
33
+ static Parameters = ReminderServiceParameters;
34
+
35
+ /**
36
+ * Validate configuration; synchronous, called before init
37
+ */
38
+ resolve(): this {
39
+ super.resolve();
40
+ useLog("INFO", "Reminder delay", this.parameters.delayHours);
41
+ return this;
42
+ }
43
+
44
+ /**
45
+ * Open connections or start timers
46
+ */
47
+ async init(): Promise<this> {
48
+ await super.init();
49
+ return this;
50
+ }
51
+
52
+ /**
53
+ * Release what init created
54
+ */
55
+ async stop(): Promise<void> {
56
+ await super.stop();
57
+ }
58
+ }
59
+ ```
60
+
61
+ ```json
62
+ {
63
+ "services": {
64
+ "ReminderService": { "type": "ReminderService", "delayHours": 12 }
65
+ }
66
+ }
67
+ ```
68
+
69
+ Lifecycle: constructor → `resolve()` (configuration, dependencies) → `init()` (connections) → running → `stop()`.
70
+
71
+ Another service is reached with `useService("ReminderService")`. Expose a method to clients with `@Operation()` (see `webda-operations`).
72
+
73
+ ## Common mistakes
74
+
75
+ ```text
76
+ new ReminderService(...) → never instantiate services; declare them in webda.config.json
77
+ Hardcoded URLs, credentials, delays → parameters in webda.config.json
78
+ console.log → useLog("INFO", ...)
79
+ Forgetting @WebdaModda → the type is unknown in webda.config.json
80
+ ```
81
+
82
+ ## Verify
83
+
84
+ `npm run build`, then `npm run debug` and check the service starts in the logs; add a test (see `webda-testing`).
85
+
86
+ ## Reference
87
+
88
+ https://docs.webda.io
89
+
90
+ Written for Webda 4.0.0-beta.
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: webda-stores
3
+ description: Use when reading or writing model data, querying models, or choosing or switching the store (memory, file, MongoDB, PostgreSQL) of a Webda app
4
+ ---
5
+
6
+ # Webda stores and data access
7
+
8
+ ## When to use
9
+
10
+ Reading, writing or querying models, reacting to data changes, or changing where the data is stored.
11
+
12
+ ## Pattern
13
+
14
+ Code never talks to a store: it uses the model. The store is configuration.
15
+
16
+ ```ts
17
+ import { useLog } from "@webda/core";
18
+ import { UuidModel, useRepository } from "@webda/models";
19
+
20
+ export class Note extends UuidModel {
21
+ text!: string;
22
+ archived!: boolean;
23
+ }
24
+
25
+ export async function examples() {
26
+ const note = await Note.create({ text: "Hello", archived: false });
27
+ const loaded = await Note.ref(note.getUUID()).get();
28
+ await Note.ref(note.getUUID()).patch({ archived: true });
29
+ const { results, continuationToken } = await Note.query("archived = false ORDER BY text LIMIT 10");
30
+ const same = await Note.query("text = ?", [loaded.text]);
31
+ for await (const item of Note.iterate("archived = true")) {
32
+ await item.delete();
33
+ }
34
+ useRepository(Note).on("Created", event => useLog("INFO", "Note created", event));
35
+ return { results, continuationToken, same };
36
+ }
37
+ ```
38
+
39
+ Pass user input as query parameters (`?` or `:name`), never by building the query string.
40
+
41
+ The default store is the `Registry` service. To switch, replace it in `webda.config.json` and add the package:
42
+
43
+ | Store | Package | `Registry` configuration | Connection |
44
+ | ---------------- | ----------------- | ---------------------------------------------------------- | -------------------------------------------------------- |
45
+ | memory (default) | `@webda/core` | none (persisted to `.registry`) | |
46
+ | files | `@webda/fs` | `{ "type": "Webda/FileStore", "folder": "./data" }` | |
47
+ | MongoDB | `@webda/mongo` | `{ "type": "Webda/MongoStore", "collection": "registry" }` | `WEBDA_MONGO_URL` |
48
+ | PostgreSQL | `@webda/postgres` | `{ "type": "Webda/PostgresStore", "table": "registry" }` | `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE` |
49
+
50
+ ## Common mistakes
51
+
52
+ ```text
53
+ @Inject("Registry") store; store.save(...) → Model.create / Model.ref(id).patch / Model.query
54
+ `text = '${input}'` in a query → Note.query("text = ?", [input])
55
+ Credentials in webda.config.json → environment variables (.env.example)
56
+ ```
57
+
58
+ ## Verify
59
+
60
+ `npm test`. For MongoDB or PostgreSQL, start the database (`docker compose up -d`) and `cp .env.example .env`: tests load `.env` automatically; for `npm run debug` or `npm run serve`, export the variables first (`set -a; . ./.env; set +a`).
61
+
62
+ ## Reference
63
+
64
+ https://docs.webda.io
65
+
66
+ Written for Webda 4.0.0-beta.
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: webda-testing
3
+ description: Use when writing, running or fixing tests of a Webda app, its models, services or operations
4
+ ---
5
+
6
+ # Testing a Webda app
7
+
8
+ ## When to use
9
+
10
+ Adding tests for a model, a service or an operation, or fixing a failing test.
11
+
12
+ ## Pattern
13
+
14
+ Tests are vitest files in `test/*.spec.ts`, written as classes with `@suite` and `@test` from `@webda/test`. `npm test` builds first (`pretest`), because the app is loaded from `lib/` and `webda.module.json`.
15
+
16
+ ```ts
17
+ import { suite, test } from "@webda/test";
18
+ import * as assert from "node:assert";
19
+ import { resolve } from "node:path";
20
+ import { TestApplication, WebdaApplicationTest } from "@webda/core/lib/test/index.js";
21
+ import { useModel, useService } from "@webda/core";
22
+
23
+ const appDir = resolve(import.meta.dirname, "..");
24
+
25
+ class AppTestApplication extends TestApplication {
26
+ getNamespace() {
27
+ return "MyApp";
28
+ }
29
+
30
+ filterModule(_filename: string): boolean {
31
+ return true;
32
+ }
33
+ }
34
+
35
+ @suite
36
+ class NoteTest extends WebdaApplicationTest {
37
+ getTestConfiguration(): string {
38
+ return appDir;
39
+ }
40
+
41
+ getApplication() {
42
+ return new AppTestApplication(this.getTestConfiguration());
43
+ }
44
+
45
+ @test
46
+ async createsANote() {
47
+ const Note = useModel<any>("MyApp/Note");
48
+ const note = await Note.create({ text: "Hello", archived: false });
49
+ assert.strictEqual((await Note.ref(note.getUUID()).get()).text, "Hello");
50
+ }
51
+
52
+ @test
53
+ async callsAService() {
54
+ const greeting = (useService("GreetingService" as any) as any).greet("Ada");
55
+ assert.strictEqual(greeting, "Hello Ada");
56
+ }
57
+ }
58
+ ```
59
+
60
+ - Get models with `useModel("<Namespace>/<Model>")` and services with `useService("<name>")`: importing classes from `src/` gives different class objects than the ones the app loaded from `lib/`.
61
+ - `getNamespace()` returns the `webda.namespace` of `package.json`.
62
+ - By default the test replaces the `Registry` store with an in-memory one. Override `async tweakApp() {}` to keep `webda.config.json` as is (then make assertions on data the test creates, since the store keeps data between runs).
63
+
64
+ ## Common mistakes
65
+
66
+ ```text
67
+ import { Note } from "../src/models/Note.model.js" → useModel("MyApp/Note")
68
+ Running vitest without building → npm test (runs webdac build first)
69
+ Assertions on global counts with a persistent store → assert on records created by the test
70
+ ```
71
+
72
+ ## Verify
73
+
74
+ `npm test`.
75
+
76
+ ## Reference
77
+
78
+ https://docs.webda.io
79
+
80
+ Written for Webda 4.0.0-beta.
@@ -0,0 +1,21 @@
1
+ import type { Store, Transport } from "./options.js";
2
+ /**
3
+ * Lines describing the generated app for the "This app" section of AGENTS.md
4
+ * @param options - selected store and transports
5
+ * @param options.store - selected store backend
6
+ * @param options.transports - selected transport protocols
7
+ * @returns one line per aspect
8
+ */
9
+ export declare function describeApp(options: {
10
+ store: Store;
11
+ transports: Transport[];
12
+ }): string[];
13
+ /**
14
+ * Fill the `<!-- WEBDA:APP -->` marker of the AGENTS.md template
15
+ * @param template - AGENTS.md template content
16
+ * @param appLines - bullet lines from `describeApp`
17
+ * @param notes - overlay notes, one paragraph each
18
+ * @returns AGENTS.md content
19
+ */
20
+ export declare function renderAgentsMd(template: string, appLines: string[], notes: string[]): string;
21
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAcrD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,SAAS,EAAE,CAAA;CAAE,GAAG,MAAM,EAAE,CAExF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAM5F"}
package/lib/agents.js ADDED
@@ -0,0 +1,33 @@
1
+ const MARKER = "<!-- WEBDA:APP -->";
2
+ const STORE_LINES = {
3
+ memory: "Store: in memory (`Webda/MemoryStore`), persisted to `.registry` between runs",
4
+ file: "Store: JSON files (`Webda/FileStore`) in `./data`",
5
+ mongodb: "Store: MongoDB (`Webda/MongoStore`), connection from `WEBDA_MONGO_URL`",
6
+ postgres: "Store: PostgreSQL (`Webda/PostgresStore`), connection from `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`"
7
+ };
8
+ const TRANSPORT_LABELS = { rest: "REST", graphql: "GraphQL", grpc: "gRPC", mcp: "MCP" };
9
+ /**
10
+ * Lines describing the generated app for the "This app" section of AGENTS.md
11
+ * @param options - selected store and transports
12
+ * @param options.store - selected store backend
13
+ * @param options.transports - selected transport protocols
14
+ * @returns one line per aspect
15
+ */
16
+ export function describeApp(options) {
17
+ return [STORE_LINES[options.store], `Transports: ${options.transports.map(t => TRANSPORT_LABELS[t]).join(", ")}`];
18
+ }
19
+ /**
20
+ * Fill the `<!-- WEBDA:APP -->` marker of the AGENTS.md template
21
+ * @param template - AGENTS.md template content
22
+ * @param appLines - bullet lines from `describeApp`
23
+ * @param notes - overlay notes, one paragraph each
24
+ * @returns AGENTS.md content
25
+ */
26
+ export function renderAgentsMd(template, appLines, notes) {
27
+ if (!template.includes(MARKER)) {
28
+ throw new Error(`AGENTS.md template has no ${MARKER} marker`);
29
+ }
30
+ const section = [appLines.map(line => `- ${line}`).join("\n"), ...notes].join("\n\n");
31
+ return template.replace(MARKER, section);
32
+ }
33
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,oBAAoB,CAAC;AAEpC,MAAM,WAAW,GAA0B;IACzC,MAAM,EAAE,+EAA+E;IACvF,IAAI,EAAE,mDAAmD;IACzD,OAAO,EAAE,wEAAwE;IACjF,QAAQ,EACN,qHAAqH;CACxH,CAAC;AAEF,MAAM,gBAAgB,GAA8B,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AAEnH;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,OAAkD;IAC5E,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,eAAe,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACpH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,QAAkB,EAAE,KAAe;IAClF,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,6BAA6B,MAAM,SAAS,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtF,OAAO,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC"}
package/lib/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/lib/bin.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { defaultDeps, main } from "./index.js";
3
+ process.exitCode = await main(await defaultDeps());
4
+ //# sourceMappingURL=bin.js.map
package/lib/bin.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,CAAC,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,WAAW,EAAE,CAAC,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { Store, Transport } from "./options.js";
2
+ import type { FileMap, Template } from "./template.js";
3
+ /**
4
+ * The templates cannot be combined; a bug in the templates, not user input
5
+ */
6
+ export declare class ComposeError extends Error {
7
+ }
8
+ /**
9
+ * Overlay folders to apply, in order: store first, then transports
10
+ * @param store - selected store; `memory` needs no overlay
11
+ * @param transports - selected transports; `rest` is part of the base
12
+ * @returns overlay folder names under `templates/features/`
13
+ */
14
+ export declare function overlayNames(store: Store, transports: Transport[]): string[];
15
+ export interface Composed {
16
+ files: FileMap;
17
+ packageJson: Record<string, any>;
18
+ config: Record<string, any>;
19
+ agentNotes: string[];
20
+ }
21
+ /**
22
+ * Apply overlays on top of the base template
23
+ * @param base - base app
24
+ * @param overlays - overlays in application order
25
+ * @param options - `rest: false` removes the base `RESTService`
26
+ * @param options.rest - whether to include REST service
27
+ * @returns merged files, package.json, configuration and agent notes
28
+ */
29
+ export declare function compose(base: Template, overlays: Template[], options: {
30
+ rest: boolean;
31
+ }): Composed;
32
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEvD;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;CAAG;AAI1C;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAG5E;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG,QAAQ,CAuBlG"}
package/lib/compose.js ADDED
@@ -0,0 +1,51 @@
1
+ import { deepMerge } from "./merge.js";
2
+ /**
3
+ * The templates cannot be combined; a bug in the templates, not user input
4
+ */
5
+ export class ComposeError extends Error {
6
+ }
7
+ const TRANSPORT_OVERLAYS = ["graphql", "grpc", "mcp"];
8
+ /**
9
+ * Overlay folders to apply, in order: store first, then transports
10
+ * @param store - selected store; `memory` needs no overlay
11
+ * @param transports - selected transports; `rest` is part of the base
12
+ * @returns overlay folder names under `templates/features/`
13
+ */
14
+ export function overlayNames(store, transports) {
15
+ const names = store === "memory" ? [] : [`store-${store}`];
16
+ return [...names, ...TRANSPORT_OVERLAYS.filter(name => transports.includes(name))];
17
+ }
18
+ /**
19
+ * Apply overlays on top of the base template
20
+ * @param base - base app
21
+ * @param overlays - overlays in application order
22
+ * @param options - `rest: false` removes the base `RESTService`
23
+ * @param options.rest - whether to include REST service
24
+ * @returns merged files, package.json, configuration and agent notes
25
+ */
26
+ export function compose(base, overlays, options) {
27
+ const files = new Map(base.files);
28
+ const owner = new Map();
29
+ let packageJson = base.packageJson;
30
+ let config = base.config;
31
+ const agentNotes = [];
32
+ for (const overlay of overlays) {
33
+ for (const [path, content] of overlay.files) {
34
+ if (owner.has(path)) {
35
+ throw new ComposeError(`Overlays "${owner.get(path)}" and "${overlay.name}" both write ${path}`);
36
+ }
37
+ owner.set(path, overlay.name);
38
+ files.set(path, content);
39
+ }
40
+ packageJson = deepMerge(packageJson, overlay.packageJson);
41
+ config = deepMerge(config, overlay.config);
42
+ if (overlay.agents.trim())
43
+ agentNotes.push(overlay.agents.trim());
44
+ }
45
+ if (!options.rest && config.services) {
46
+ config = structuredClone(config);
47
+ delete config.services.RESTService;
48
+ }
49
+ return { files, packageJson, config, agentNotes };
50
+ }
51
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIvC;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;CAAG;AAE1C,MAAM,kBAAkB,GAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAY,EAAE,UAAuB;IAChE,MAAM,KAAK,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,KAAK,EAAE,GAAG,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACrF,CAAC;AASD;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CAAC,IAAc,EAAE,QAAoB,EAAE,OAA0B;IACtF,MAAM,KAAK,GAAY,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,IAAI,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;IACnC,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACzB,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAC5C,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpB,MAAM,IAAI,YAAY,CAAC,aAAa,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,IAAI,gBAAgB,IAAI,EAAE,CAAC,CAAC;YACnG,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;YAC9B,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3B,CAAC;QACD,WAAW,GAAG,SAAS,CAAC,WAAW,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;QAC1D,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE;YAAE,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrC,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;IACrC,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACpD,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { CreateOptions } from "./options.js";
2
+ import { FileMap } from "./template.js";
3
+ import { VersionResolver } from "./versions.js";
4
+ /** npm 10 (bundled with Node 22) crashes resolving the optional peers of vitest/vite */
5
+ export declare const NPMRC = "# npm 10 crashes on the optional peer dependencies of vitest/vite: safe to remove with npm >= 11\nlegacy-peer-deps=true\n";
6
+ /**
7
+ * Rename `_gitignore` files and replace `__TOKEN__` placeholders
8
+ * @param files - generated files
9
+ * @param tokens - token name (without underscores) → value
10
+ * @returns a new map
11
+ */
12
+ export declare function finalizePaths(files: FileMap, tokens: Record<string, string>): FileMap;
13
+ /**
14
+ * Fail when `dir` exists and is not empty
15
+ * @param dir - target directory
16
+ */
17
+ export declare function checkTarget(dir: string): void;
18
+ /**
19
+ * Compose the application files in memory
20
+ * @param input - options, template folders and version resolver
21
+ * @param input.options - application creation options
22
+ * @param input.templatesDir - path to template directories
23
+ * @param input.agentDir - path to agent directory
24
+ * @param input.resolveVersion - function to resolve package versions
25
+ * @returns relative path → content
26
+ */
27
+ export declare function generate(input: {
28
+ options: CreateOptions;
29
+ templatesDir: string;
30
+ agentDir: string;
31
+ resolveVersion: VersionResolver;
32
+ }): Promise<FileMap>;
33
+ /**
34
+ * Write generated files under `dir`
35
+ * @param dir - target directory, created when missing
36
+ * @param files - relative path → content
37
+ */
38
+ export declare function writeFiles(dir: string, files: FileMap): Promise<void>;
39
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAA+B,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,OAAO,EAA0B,MAAM,eAAe,CAAC;AAChE,OAAO,EAAiB,eAAe,EAAE,MAAM,eAAe,CAAC;AAE/D,wFAAwF;AACxF,eAAO,MAAM,KAAK,8HAEjB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAWrF;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAO7C;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE;IACpC,OAAO,EAAE,aAAa,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,eAAe,CAAC;CACjC,GAAG,OAAO,CAAC,OAAO,CAAC,CAuBnB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAM3E"}
@@ -0,0 +1,85 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { describeApp, renderAgentsMd } from "./agents.js";
5
+ import { compose, overlayNames } from "./compose.js";
6
+ import { OptionsError, toPackageName } from "./options.js";
7
+ import { loadTemplate, readTree } from "./template.js";
8
+ import { applyVersions } from "./versions.js";
9
+ /** npm 10 (bundled with Node 22) crashes resolving the optional peers of vitest/vite */
10
+ export const NPMRC = `# npm 10 crashes on the optional peer dependencies of vitest/vite: safe to remove with npm >= 11
11
+ legacy-peer-deps=true
12
+ `;
13
+ /**
14
+ * Rename `_gitignore` files and replace `__TOKEN__` placeholders
15
+ * @param files - generated files
16
+ * @param tokens - token name (without underscores) → value
17
+ * @returns a new map
18
+ */
19
+ export function finalizePaths(files, tokens) {
20
+ const out = new Map();
21
+ for (const [path, content] of files) {
22
+ const finalPath = path.replace(/(^|\/)_gitignore$/, "$1.gitignore");
23
+ let finalContent = content;
24
+ for (const [name, value] of Object.entries(tokens)) {
25
+ finalContent = finalContent.replaceAll(`__${name}__`, value);
26
+ }
27
+ out.set(finalPath, finalContent);
28
+ }
29
+ return out;
30
+ }
31
+ /**
32
+ * Fail when `dir` exists and is not empty
33
+ * @param dir - target directory
34
+ */
35
+ export function checkTarget(dir) {
36
+ if (existsSync(dir) && !statSync(dir).isDirectory()) {
37
+ throw new OptionsError(`${dir} exists and is not a directory`);
38
+ }
39
+ if (existsSync(dir) && readdirSync(dir).length > 0) {
40
+ throw new OptionsError(`${dir} already exists and is not empty`);
41
+ }
42
+ }
43
+ /**
44
+ * Compose the application files in memory
45
+ * @param input - options, template folders and version resolver
46
+ * @param input.options - application creation options
47
+ * @param input.templatesDir - path to template directories
48
+ * @param input.agentDir - path to agent directory
49
+ * @param input.resolveVersion - function to resolve package versions
50
+ * @returns relative path → content
51
+ */
52
+ export async function generate(input) {
53
+ const { options, templatesDir, agentDir } = input;
54
+ const base = await loadTemplate(join(templatesDir, "base"), "base");
55
+ const overlays = await Promise.all(overlayNames(options.store, options.transports).map(name => loadTemplate(join(templatesDir, "features", name), name)));
56
+ const composed = compose(base, overlays, { rest: options.transports.includes("rest") });
57
+ const appName = toPackageName(basename(options.dir));
58
+ const pkg = composed.packageJson;
59
+ applyVersions(pkg, input.resolveVersion);
60
+ const files = new Map(composed.files);
61
+ files.set("package.json", JSON.stringify(pkg, null, 2) + "\n");
62
+ if (options.pm === "npm")
63
+ files.set(".npmrc", NPMRC);
64
+ files.set("webda.config.json", JSON.stringify(composed.config, null, 2) + "\n");
65
+ const agentsTemplate = await readFile(join(agentDir, "AGENTS.md"), "utf8");
66
+ files.set("AGENTS.md", renderAgentsMd(agentsTemplate, describeApp(options), composed.agentNotes));
67
+ files.set("CLAUDE.md", await readFile(join(agentDir, "CLAUDE.md"), "utf8"));
68
+ for (const [path, content] of await readTree(join(agentDir, "skills"))) {
69
+ files.set(`.agents/skills/${path}`, content);
70
+ }
71
+ return finalizePaths(files, { APP_NAME: appName, NAMESPACE: options.namespace });
72
+ }
73
+ /**
74
+ * Write generated files under `dir`
75
+ * @param dir - target directory, created when missing
76
+ * @param files - relative path → content
77
+ */
78
+ export async function writeFiles(dir, files) {
79
+ for (const [path, content] of files) {
80
+ const full = join(dir, path);
81
+ await mkdir(dirname(full), { recursive: true });
82
+ await writeFile(full, content);
83
+ }
84
+ }
85
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { Runner } from "./install.js";
2
+ import { Prompter } from "./prompts.js";
3
+ export interface MainDeps {
4
+ argv: string[];
5
+ cwd: string;
6
+ env: Record<string, string | undefined>;
7
+ interactive: boolean;
8
+ prompter: Prompter;
9
+ run: Runner;
10
+ log: (message: string) => void;
11
+ templatesDir: string;
12
+ agentDir: string;
13
+ versions: Record<string, string>;
14
+ }
15
+ /**
16
+ * Run the CLI
17
+ * @param deps - environment, injectable for tests
18
+ * @returns process exit code
19
+ */
20
+ export declare function main(deps: MainDeps): Promise<number>;
21
+ /**
22
+ * Dependencies for a real run
23
+ * @returns deps using the process, clack prompts and the packaged templates
24
+ */
25
+ export declare function defaultDeps(): Promise<MainDeps>;
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgC,MAAM,EAAe,MAAM,cAAc,CAAC;AAEjF,OAAO,EAAiB,QAAQ,EAAkB,MAAM,cAAc,CAAC;AAGvE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,QAAQ,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAYD;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAqD1D;AAED;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC,CAcrD"}