@fougere/cli 0.1.0-alpha.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.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/app/commands/CallCommand.ts +68 -0
  4. package/app/commands/CheckCommand.ts +61 -0
  5. package/app/commands/CompletionCommand.ts +23 -0
  6. package/app/commands/GraphCommand.ts +61 -0
  7. package/app/commands/NewCommand.ts +122 -0
  8. package/app/commands/ServeCommand.ts +34 -0
  9. package/dist/bin.d.ts +3 -0
  10. package/dist/bin.d.ts.map +1 -0
  11. package/dist/bin.js +28 -0
  12. package/dist/bin.js.map +1 -0
  13. package/dist/bridge.d.ts +11 -0
  14. package/dist/bridge.d.ts.map +1 -0
  15. package/dist/bridge.js +54 -0
  16. package/dist/bridge.js.map +1 -0
  17. package/dist/completion.d.ts +9 -0
  18. package/dist/completion.d.ts.map +1 -0
  19. package/dist/completion.js +93 -0
  20. package/dist/completion.js.map +1 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +4 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/runner.d.ts +12 -0
  26. package/dist/runner.d.ts.map +1 -0
  27. package/dist/runner.js +113 -0
  28. package/dist/runner.js.map +1 -0
  29. package/fronds/analysis/entities/Check.ts +5 -0
  30. package/fronds/analysis/entities/Graph.ts +6 -0
  31. package/fronds/analysis/handlers/CheckHandler.ts +90 -0
  32. package/fronds/analysis/handlers/GraphHandler.ts +32 -0
  33. package/fronds/analysis/services/ProjectScan.ts +35 -0
  34. package/fronds/scaffold/entities/BuildFrond.ts +5 -0
  35. package/fronds/scaffold/entities/Call.ts +6 -0
  36. package/fronds/scaffold/entities/Completion.ts +5 -0
  37. package/fronds/scaffold/entities/New.ts +19 -0
  38. package/fronds/scaffold/entities/Serve.ts +7 -0
  39. package/fronds/scaffold/entities/Sync.ts +6 -0
  40. package/fronds/scaffold/handlers/BuildFrondHandler.ts +97 -0
  41. package/fronds/scaffold/handlers/CallHandler.ts +9 -0
  42. package/fronds/scaffold/handlers/CompletionHandler.ts +8 -0
  43. package/fronds/scaffold/handlers/NewHandler.ts +8 -0
  44. package/fronds/scaffold/handlers/ServeHandler.ts +9 -0
  45. package/fronds/scaffold/handlers/SyncHandler.ts +329 -0
  46. package/fronds/scaffold/services/ProjectWriter.ts +176 -0
  47. package/package.json +52 -0
  48. package/templates/admin/fronds/admin/entities/User.ts +10 -0
  49. package/templates/admin/fronds/admin/handlers/UserHandler.ts +29 -0
  50. package/templates/admin/fronds/admin/package.json +10 -0
  51. package/templates/api/fronds/api/entities/Task.ts +9 -0
  52. package/templates/api/fronds/api/handlers/TaskHandler.ts +29 -0
  53. package/templates/api/fronds/api/package.json +10 -0
  54. package/templates/apps/nuxt/app/app.vue +25 -0
  55. package/templates/apps/nuxt/app/pages/index.vue +33 -0
  56. package/templates/apps/nuxt/nuxt.config.ts +6 -0
  57. package/templates/apps/nuxt/package.json +19 -0
  58. package/templates/apps/nuxt/tsconfig.json +3 -0
  59. package/templates/blog/app/pages/index.vue +12 -0
  60. package/templates/blog/app/pages/posts/index.vue +21 -0
  61. package/templates/blog/app/pages/posts/manage.vue +47 -0
  62. package/templates/blog/app/pages/posts/new.vue +34 -0
  63. package/templates/blog/fronds/blog/entities/Post.ts +12 -0
  64. package/templates/blog/fronds/blog/handlers/PostHandler.ts +35 -0
  65. package/templates/blog/fronds/blog/package.json +10 -0
  66. package/templates/flat/CLAUDE.md +65 -0
  67. package/templates/flat/_gitignore +6 -0
  68. package/templates/flat/app/app.vue +25 -0
  69. package/templates/flat/app/pages/index.vue +7 -0
  70. package/templates/flat/fougere.config.ts +7 -0
  71. package/templates/flat/nuxt.config.ts +4 -0
  72. package/templates/flat/package.json +24 -0
  73. package/templates/flat/pnpm-workspace.yaml +6 -0
  74. package/templates/flat/tsconfig.frond.json +25 -0
  75. package/templates/flat/tsconfig.json +3 -0
  76. package/templates/frond/CLAUDE.md +65 -0
  77. package/templates/frond/_gitignore +4 -0
  78. package/templates/frond/fougere.config.ts +5 -0
  79. package/templates/frond/fronds/__name__/entities/Post.ts +10 -0
  80. package/templates/frond/fronds/__name__/handlers/PostHandler.ts +26 -0
  81. package/templates/frond/fronds/__name__/package.json +10 -0
  82. package/templates/frond/package.json +18 -0
  83. package/templates/frond/pnpm-workspace.yaml +2 -0
  84. package/templates/frond/serve.mjs +25 -0
  85. package/templates/fronds/blank/entities/Item.ts +8 -0
  86. package/templates/fronds/blank/handlers/ItemHandler.ts +5 -0
  87. package/templates/fronds/blank/package.json +14 -0
  88. package/templates/fronds/blog/entities/Post.ts +10 -0
  89. package/templates/fronds/blog/handlers/PostHandler.ts +38 -0
  90. package/templates/fronds/blog/package.json +14 -0
  91. package/templates/workspace/CLAUDE.md +65 -0
  92. package/templates/workspace/_gitignore +6 -0
  93. package/templates/workspace/fougere.config.ts +5 -0
  94. package/templates/workspace/fronds/tsconfig.json +18 -0
  95. package/templates/workspace/package.json +19 -0
  96. package/templates/workspace/pnpm-workspace.yaml +10 -0
@@ -0,0 +1,5 @@
1
+ import { Crud } from '@fougere/core';
2
+ import Item from '../entities/Item.js';
3
+
4
+ /** The five ops, derived. Redefine one to judge it, add a method to name an operation. */
5
+ export default class ItemHandler extends Crud(Item) {}
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@frond/__name__",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "exports": {
6
+ "./entities/*.js": "./entities/*.ts",
7
+ "./entities/*": "./entities/*.ts",
8
+ "./package.json": "./package.json"
9
+ },
10
+ "dependencies": {
11
+ "@fougere/schema": "latest",
12
+ "@fougere/core": "latest"
13
+ }
14
+ }
@@ -0,0 +1,10 @@
1
+ import { entity, primary, text, auto, oneOf, readOnly } from '@fougere/schema';
2
+
3
+ export default class Post extends entity({
4
+ id: primary(),
5
+ title: text({ min: 1, max: 200 }),
6
+ body: text(),
7
+ createdAt: auto(),
8
+ // Server-owned: born a draft, flipped by the publish operation.
9
+ status: readOnly(oneOf('draft', 'published', { default: 'draft' })),
10
+ }) {}
@@ -0,0 +1,38 @@
1
+ import { Crud, FougereError, ErrorCode } from '@fougere/core';
2
+ import Post from '../entities/Post.js';
3
+
4
+ /** What a client may propose when drafting — `status` is not its to write. */
5
+ export class NewPost extends Post.pick('title', 'body') {}
6
+
7
+ /** What the outside world reads in a list — the body stays home. */
8
+ export class PostCard extends Post.pick('id', 'title', 'status') {}
9
+
10
+ export default class PostHandler extends Crud(Post) {
11
+ /**
12
+ * Crud gives the five ops; this one narrows its contract. `readOnly` already
13
+ * bars `status` for the whole entity — `NewPost` says what *this* op accepts.
14
+ */
15
+ async create(input: NewPost): Promise<Post> {
16
+ return this.orm.create(input);
17
+ }
18
+
19
+ /** The draft→published transition — an operation, not a field write. */
20
+ async publish(id: string): Promise<Post> {
21
+ const post = await this.orm.findById(id);
22
+ if (!post) {
23
+ throw new FougereError({ code: ErrorCode.NOT_FOUND, message: `Post '${id}' not found`, entity: 'post', operation: 'publish' });
24
+ }
25
+ if (post.status === 'published') {
26
+ throw new FougereError({ code: ErrorCode.CONFLICT, message: 'Already published', entity: 'post', operation: 'publish' });
27
+ }
28
+ return this.orm.update(id, { status: 'published' });
29
+ }
30
+
31
+ /** Only published posts, projected to the card. */
32
+ async published(): Promise<PostCard[]> {
33
+ const all = await this.orm.list();
34
+ return all
35
+ .filter((p) => p.status === 'published')
36
+ .map(({ id, title, status }) => ({ id, title, status }));
37
+ }
38
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@frond/__name__",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "exports": {
6
+ "./entities/*.js": "./entities/*.ts",
7
+ "./entities/*": "./entities/*.ts",
8
+ "./package.json": "./package.json"
9
+ },
10
+ "dependencies": {
11
+ "@fougere/schema": "latest",
12
+ "@fougere/core": "latest"
13
+ }
14
+ }
@@ -0,0 +1,65 @@
1
+ # Working in this application
2
+
3
+ Built with **Fougere**. This file is read by coding agents before they touch anything — it states
4
+ the few facts that change what they should write.
5
+
6
+ ## One declaration, everything derives
7
+
8
+ A field belongs to its entity and to nowhere else. Adding it there alone produces the SQL column,
9
+ the validation, the input contract, the write and the output shape. Additive storage changes need no
10
+ hand-written migration; renames, removals and type changes still do. No DTO or permitted-params list
11
+ needs to stay in step.
12
+
13
+ ```ts
14
+ // fronds/<frond>/entities/Product.ts — and nothing else
15
+ sku: text({ min: 3, max: 32 }),
16
+ ```
17
+
18
+ Two consequences worth stating, because they are what makes it hold:
19
+
20
+ - **An input view derives from the entity** — `Order.omit('id', 'status').extend({ items })`, never
21
+ a parallel entity that repeats the same fields. A field added to the entity is then accepted
22
+ without touching the view.
23
+ - **A handler writes what the input carries** (`{ ...attributes }`), it does not enumerate its
24
+ fields — otherwise a new field is judged, then silently not written.
25
+
26
+ If you are about to write the same constraint in two places, you have missed the derivation.
27
+
28
+ ## A surface is a door, never a logic
29
+
30
+ Every door goes through the handler **façade**, which is the judge: validation, unknown-key refusal,
31
+ collectors. A resolver or route you wire yourself against the ORM — or worse, against the database —
32
+ is a second door with no judge behind it, and the rules declared in the entities stop applying there.
33
+
34
+ Before adding a surface, reach for its **projection**:
35
+
36
+ | surface | the call |
37
+ |---|---|
38
+ | REST | `generateRoutes(app)` then `registerRoutes(router, routes)` — `@fougere/schema-rest` |
39
+ | GraphQL | `registerAll(builder, app)` then `registerGraphQL(router, builder.toSchema())` — `@fougere/schema-graphql` |
40
+
41
+ Hand-writing the types (`buildSchema`, raw SDL, one Pothos resolver per field) rebuilds what the
42
+ projection already derives, and drops the judge on the way. `registerType` / `registerOperations`
43
+ exist to add what a projection cannot derive — never to replace it.
44
+
45
+ ## Reading data
46
+
47
+ `EntityOrm`, injected by type, is the only data access:
48
+
49
+ ```
50
+ list(options?) every row — `options.where` filters, plus paging and sorting
51
+ findById(id) one row by id
52
+ findBy(criteria) one row by criteria — findBy({ email })
53
+ findAllBy(criteria) every row of a criteria — findAllBy({ order_id }) IS a one-to-many relation
54
+ create / update / delete
55
+ ```
56
+
57
+ Read a relation with `findAllBy`. Never read a whole table to filter it in memory.
58
+
59
+ ## Checking your work
60
+
61
+ ```bash
62
+ pnpm typecheck # the compiler — free, immediate, and it catches most of it
63
+ ```
64
+
65
+ Run it. It is the first judge, and the cheapest.
@@ -0,0 +1,6 @@
1
+ node_modules
2
+ .nuxt
3
+ .output
4
+ .data
5
+ dist
6
+ *.log
@@ -0,0 +1,5 @@
1
+ import type { FougereConfig } from '@fougere/core';
2
+
3
+ export default {
4
+ db: { dialect: 'sqlite', path: '.data/app.db' },
5
+ } satisfies FougereConfig;
@@ -0,0 +1,18 @@
1
+ {
2
+ // A frond is autonomous TypeScript domain code — it typechecks on its own, against
3
+ // this file, without Nuxt and without `.nuxt/tsconfig.json`. `pnpm typecheck` at the
4
+ // workspace root runs it, and it is the cheapest judge you have.
5
+ "compilerOptions": {
6
+ "target": "ES2022",
7
+ "module": "Node16",
8
+ "moduleResolution": "Node16",
9
+ "lib": ["ESNext"],
10
+ "types": ["node"],
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "noEmit": true,
15
+ "rootDir": "."
16
+ },
17
+ "include": ["."]
18
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "fougere-workspace",
3
+ "private": true,
4
+ "type": "module",
5
+ "packageManager": "pnpm@11.20.0",
6
+ "scripts": {
7
+ "dev": "pnpm --filter './apps/*' --parallel dev",
8
+ "build": "pnpm --filter './apps/*' -r build",
9
+ "typecheck": "tsc -p fronds"
10
+ },
11
+ "dependencies": {
12
+ "@fougere/core": "latest",
13
+ "@fougere/schema": "latest"
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^22.19.15",
17
+ "typescript": "^5.9.3"
18
+ }
19
+ }
@@ -0,0 +1,10 @@
1
+ packages:
2
+ - fronds/*
3
+ - apps/*
4
+
5
+ # pnpm 11 fails an install rather than warning about a build script it was not
6
+ # told to run, and it reads the allowlist here — `pnpm.onlyBuiltDependencies` in
7
+ # package.json is the pnpm 10 location, now ignored in silence.
8
+ allowBuilds:
9
+ better-sqlite3: true
10
+ esbuild: true