@fougere/cli 0.9.3-alpha.0 → 0.11.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 (39) hide show
  1. package/app/commands/CheckCommand.ts +3 -2
  2. package/app/commands/ExplainCommand.ts +1 -1
  3. package/app/commands/LoadCommand.ts +2 -2
  4. package/app/commands/ServeCommand.ts +1 -1
  5. package/dist/bridge.d.ts.map +1 -1
  6. package/dist/bridge.js +24 -4
  7. package/dist/bridge.js.map +1 -1
  8. package/dist/machine.d.ts +1 -1
  9. package/dist/machine.js +1 -1
  10. package/dist/runner.d.ts +9 -1
  11. package/dist/runner.d.ts.map +1 -1
  12. package/dist/runner.js +13 -6
  13. package/dist/runner.js.map +1 -1
  14. package/dist/typescript/EntityTypes.d.ts.map +1 -1
  15. package/dist/typescript/EntityTypes.js.map +1 -1
  16. package/dist/typescript/FacadeTypes.d.ts +5 -5
  17. package/dist/typescript/FacadeTypes.d.ts.map +1 -1
  18. package/fronds/analysis/entities/Load.ts +1 -1
  19. package/fronds/analysis/handlers/BuildHandler.ts +25 -2
  20. package/fronds/analysis/handlers/CheckHandler.ts +39 -30
  21. package/fronds/analysis/handlers/LoadHandler.ts +6 -6
  22. package/fronds/scaffold/handlers/BuildFrondHandler.ts +1 -1
  23. package/fronds/scaffold/handlers/SyncHandler.ts +55 -16
  24. package/fronds/scaffold/services/ProjectWriter.ts +61 -3
  25. package/package.json +15 -9
  26. package/src/bridge.ts +27 -4
  27. package/src/machine.ts +1 -1
  28. package/src/runner.ts +13 -6
  29. package/src/typescript/EntityTypes.ts +5 -5
  30. package/src/typescript/FacadeTypes.ts +6 -6
  31. package/templates/blog/fronds/blog/entities/Post.ts +1 -1
  32. package/templates/flat/CLAUDE.md +3 -3
  33. package/templates/frond/CLAUDE.md +3 -3
  34. package/templates/workspace/CLAUDE.md +3 -3
  35. package/templates/apps/nuxt/app/app.vue +0 -25
  36. package/templates/apps/nuxt/app/pages/index.vue +0 -33
  37. package/templates/apps/nuxt/nuxt.config.ts +0 -6
  38. package/templates/apps/nuxt/package.json +0 -19
  39. package/templates/apps/nuxt/tsconfig.json +0 -3
@@ -1,4 +1,5 @@
1
- import type { CheckResult, Finding } from '../../fronds/analysis/handlers/CheckHandler.js';
1
+ import type { CheckResult } from '../../fronds/analysis/handlers/CheckHandler.js';
2
+ import type { Diagnostic } from '@fougere/core';
2
3
  import type { App } from '@fougere/core';
3
4
  import { createAppRunner } from '@fougere/core';
4
5
  import type { ui as createUi } from '../../src/ui.js';
@@ -45,7 +46,7 @@ export default class CheckCommand {
45
46
  }
46
47
  }
47
48
 
48
- function render(f: Finding): string {
49
+ function render(f: Diagnostic): string {
49
50
  const mark = f.severity === 'blocking' ? pc.red('✗') : pc.yellow('⚠');
50
51
  const where = relative(process.cwd(), f.filePath) || f.filePath;
51
52
  const what = f.subject ? ` ${f.subject}` : '';
@@ -19,7 +19,7 @@ export default class ExplainCommand {
19
19
  const operation = (raw.operation as string | undefined)?.trim();
20
20
 
21
21
  // No operation named — the question is what this project serves at all. The same
22
- // model answers both, so the door is the one that was asked, never a guess.
22
+ // model answers both, so the facade is the one that was asked, never a guess.
23
23
  if (names || !operation) return this.listing(raw, names);
24
24
 
25
25
  const result = await this.ask('execute', raw) as ExplainResult;
@@ -27,7 +27,7 @@ export default class LoadCommand {
27
27
  if (machineWanted(raw)) return printMachine(result);
28
28
 
29
29
  if (result.operations.length === 0) {
30
- this.ui.warn('No operation answers the default door — nothing to put under load.');
30
+ this.ui.warn('No operation answers the default facade — nothing to put under load.');
31
31
  return;
32
32
  }
33
33
 
@@ -36,7 +36,7 @@ export default class LoadCommand {
36
36
  return;
37
37
  }
38
38
 
39
- this.ui.step(`${pc.bold(String(result.operations.length))} operation(s) against ${pc.dim(result.door)}`);
39
+ this.ui.step(`${pc.bold(String(result.operations.length))} operation(s) against ${pc.dim(result.facade)}`);
40
40
  this.ui.note(result.operations.map((one) => ` ${one}`).join('\n'), 'Under load');
41
41
  this.ui.info(`${pc.dim('Written to')} ${result.file} ${pc.dim('— edit the weights, the stages and the thresholds.')}`);
42
42
  this.ui.info(`${pc.dim('Then:')} k6 run ${result.file}`);
@@ -1,7 +1,7 @@
1
1
  import { createLocalRunner, identityFromEnv } from '@fougere/core';
2
2
  import { watchPathsOf } from '@fougere/compiler';
3
3
  import { installLoader } from '../../src/loader.js';
4
- import type { Conventions } from '@fougere/core';
4
+
5
5
  import { bootApp } from '@fougere/defaults';
6
6
  import { serve } from '@fougere/transport-http';
7
7
  import { watch } from 'node:fs';
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AACA,6BAA6B;AAC7B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,KAAK,EAAE,OAAO,EAAU,MAAM,OAAO,CAAC;AAM7C,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA4CpD"}
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AACA,6BAA6B;AAC7B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,KAAK,EAAE,OAAO,EAAU,MAAM,OAAO,CAAC;AA0B7C,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA+CpD"}
package/dist/bridge.js CHANGED
@@ -3,22 +3,42 @@ import { Shapes, Visibility } from '@fougere/schema';
3
3
  function toKebab(name) {
4
4
  return name.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
5
5
  }
6
+ /**
7
+ * What a caller supplies: the fields the axes admit, or the PRIMARY when they admit nothing.
8
+ *
9
+ * `Visibility.input` answers who may WRITE a field, and a primary is the server's — right for a
10
+ * form, wrong for an operation whose whole input is `Product.pick('id')`. That contract asks the
11
+ * caller to NAME a row, and the axes leave it with no argument at all: `product:archive abc-9`
12
+ * answered `Unexpected argument`.
13
+ *
14
+ * Only when nothing else remains, so `create` over a full entity is untouched — its input holds
15
+ * twelve writable fields and the primary stays the server's.
16
+ */
17
+ function suppliedIn(fields) {
18
+ const written = Visibility.of(fields).input;
19
+ if (Object.keys(written).length > 0)
20
+ return written;
21
+ return Object.fromEntries(Object.entries(fields).filter(([, field]) => Role.of(field).isPrimary));
22
+ }
6
23
  /** Convert an Entity's fields into citty args definition. */
7
24
  export function entityToArgs(fields) {
8
25
  const args = {};
9
26
  let positionalIndex = 0;
10
- // Axes-derived ingress membership; the CLI additionally skips ALL relations
11
- // (a ref is not a flag — supplying related rows is not a CLI gesture).
12
- for (const [key, field] of Object.entries(Visibility.of(fields).input)) {
27
+ // The CLI additionally skips ALL relations: a ref is not a flag — supplying related rows is
28
+ // not a CLI gesture.
29
+ for (const [key, field] of Object.entries(suppliedIn(fields))) {
13
30
  if (Role.of(field).isRelation)
14
31
  continue;
15
32
  // A `default(v)` travels as the create rule `{ value }` — citty shows it.
16
33
  const defaultValue = Lifecycle.of(field).literal?.value;
17
34
  const { base: shape, nullable } = Shapes.of(field.shape);
18
35
  const type = Shapes.typeOf(field.shape);
36
+ // Naming a row is required by definition: a generator fills a primary the server writes,
37
+ // never one a caller hands in to designate.
38
+ const designates = Role.of(field).isPrimary;
19
39
  const common = {
20
40
  description: field.meta?.description,
21
- required: !nullable && Lifecycle.of(field).requiredAtCreate,
41
+ required: designates || (!nullable && Lifecycle.of(field).requiredAtCreate),
22
42
  };
23
43
  // A closed set is citty's `enum`: the shape already names the legal values, so the
24
44
  // refusal and the `--help` listing come from the declaration rather than a check
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAGlD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAkB,MAAM,iBAAiB,CAAC;AAGrE,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,IAAI,GAAY,EAAE,CAAC;IACzB,IAAI,eAAe,GAAG,CAAC,CAAC;IAExB,4EAA4E;IAC5E,uEAAuE;IACvE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACvE,IAAI,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,UAAU;YAAE,SAAS;QAExC,0EAA0E;QAC1E,MAAM,YAAY,GAAG,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC;QACxD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG;YACb,WAAW,EAAE,KAAK,CAAC,IAAI,EAAE,WAAW;YACpC,QAAQ,EAAE,CAAC,QAAQ,IAAI,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,gBAAgB;SAC5D,CAAC;QAEF,mFAAmF;QACnF,iFAAiF;QACjF,qBAAqB;QACrB,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,MAAM;YAC5D,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC;YAC/D,CAAC,CAAC,SAAS,CAAC;QACd,oFAAoF;QACpF,qCAAqC;QACrC,MAAM,UAAU,GAAG,eAAe,KAAK,CAAC;eACnC,MAAM,CAAC,QAAQ;eACf,GAAG,KAAK,OAAO;eACf,OAAO,KAAK,SAAS;eACrB,IAAI,KAAK,SAAS;eAClB,IAAI,KAAK,MAAM,CAAC;QACrB,IAAI,UAAU;YAAE,eAAe,EAAE,CAAC;QAElC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CACtD,IAAI,EACJ,MAAM,EACN,YAAY,EACZ,OAAO,EACP,UAAU,CACX,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,eAAe,GAA+B;IAClD,MAAM,EAAE,IAAI;IACZ,OAAO,EAAE,IAAI;IACb,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,MAAM,EAAE,IAAI;IACZ,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,KAAK;IACb,KAAK,EAAE,KAAK;CACb,CAAC;AAEF,sCAAsC;AACtC,SAAS,MAAM,CACb,IAA2B,EAC3B,MAAmD,EACnD,YAAqB,EACrB,OAA6B,EAC7B,UAAmB;IAEnB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,YAAY,KAAK,SAAS;YAC/B,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;YAChC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,YAAuB,EAAE,CAAC;IACvE,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,YAAY,KAAK,SAAS;QAC7E,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,YAAY,CAAC,EAAE;QACnC,CAAC,CAAC,EAAE,CAAC;IAEP,IAAI,OAAO;QAAE,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACzE,IAAI,UAAU;QAAE,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAEzE,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACvD,CAAC"}
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
package/dist/machine.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /** A command's machine output — the one shape a pipe reads. */
2
2
  export declare function machineWanted(raw: Record<string, unknown>): boolean;
3
3
  /**
4
- * A `Map` serializes to `{}`, so the door converts it rather than each command flattening its own
4
+ * A `Map` serializes to `{}`, so the facade converts it rather than each command flattening its own
5
5
  * result: `GraphResult.nodes` is a Map, and `graph --json` would have printed a report with an
6
6
  * empty graph in it.
7
7
  */
package/dist/machine.js CHANGED
@@ -3,7 +3,7 @@ export function machineWanted(raw) {
3
3
  return raw.json === true || typeof raw.names === 'string';
4
4
  }
5
5
  /**
6
- * A `Map` serializes to `{}`, so the door converts it rather than each command flattening its own
6
+ * A `Map` serializes to `{}`, so the facade converts it rather than each command flattening its own
7
7
  * result: `GraphResult.nodes` is a Map, and `graph --json` would have printed a report with an
8
8
  * empty graph in it.
9
9
  */
package/dist/runner.d.ts CHANGED
@@ -3,5 +3,13 @@
3
3
  * via citty.
4
4
  */
5
5
  import type { App } from '@fougere/core';
6
- export declare function run(app: App): Promise<void>;
6
+ /**
7
+ * Every operation of an app, as a terminal command.
8
+ *
9
+ * `root` is where the PRESENTATION classes are looked for — `app/commands/`, one per command
10
+ * that wants to print something of its own. It defaults to this package, which is how
11
+ * `npx fougere` finds its fifteen; a project passes its own and gets the same treatment for
12
+ * the operations it declares, with no class at all where a default rendering will do.
13
+ */
14
+ export declare function run(app: App, root?: string): Promise<void>;
7
15
  //# sourceMappingURL=runner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAwCzC,wBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAgGjD"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAwCzC;;;;;;;GAOG;AACH,wBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,SAA0C,GAAG,OAAO,CAAC,IAAI,CAAC,CA+FjG"}
package/dist/runner.js CHANGED
@@ -13,9 +13,9 @@ function toCamel(kebab) {
13
13
  return kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
14
14
  }
15
15
  /** Scan app/commands/ for command classes. */
16
- async function loadAppCommands(cliRoot, loader) {
16
+ async function loadAppCommands(root, loader) {
17
17
  const map = new Map();
18
- const dir = join(cliRoot, 'app', 'commands');
18
+ const dir = join(root, 'app', 'commands');
19
19
  const files = await readdir(dir, { withFileTypes: true }).catch(() => []);
20
20
  for (const f of files) {
21
21
  if (!f.isFile() || !(f.name.endsWith('.ts') || f.name.endsWith('.js')))
@@ -29,13 +29,20 @@ async function loadAppCommands(cliRoot, loader) {
29
29
  }
30
30
  return map;
31
31
  }
32
- export async function run(app) {
32
+ /**
33
+ * Every operation of an app, as a terminal command.
34
+ *
35
+ * `root` is where the PRESENTATION classes are looked for — `app/commands/`, one per command
36
+ * that wants to print something of its own. It defaults to this package, which is how
37
+ * `npx fougere` finds its fifteen; a project passes its own and gets the same treatment for
38
+ * the operations it declares, with no class at all where a default rendering will do.
39
+ */
40
+ export async function run(app, root = new URL('..', import.meta.url).pathname) {
33
41
  const terminal = ui();
34
- const cliRoot = new URL('..', import.meta.url).pathname;
35
42
  const { createJiti } = await import('jiti');
36
43
  const jiti = createJiti(import.meta.url, { interopDefault: true });
37
44
  const loader = (path) => jiti.import(path);
38
- const appCommands = await loadAppCommands(cliRoot, loader);
45
+ const appCommands = await loadAppCommands(root, loader);
39
46
  const subCommands = {};
40
47
  for (const frond of app.fronds) {
41
48
  const handlerMap = new Map(frond.handlers.map((h) => [h.address, h]));
@@ -69,7 +76,7 @@ export async function run(app) {
69
76
  meta: {
70
77
  name: cmdName,
71
78
  // `--help` reads the operation's own doc sentence, which the scan already
72
- // carries for every door (`OperationContract.description`). A table here
79
+ // carries for every facade (`OperationContract.description`). A table here
73
80
  // would be the same fact written twice, and it drifted: it described `add`
74
81
  // and `doctor`, which do not exist, and had nothing for `call` or `serve`.
75
82
  description: handlerEntry.operations.get('execute')?.description,
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"EntityTypes.d.ts","sourceRoot":"","sources":["../../src/typescript/EntityTypes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AA2C1F,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAcD,qBAAa,WAAW;IACF,OAAO,CAAC,QAAQ,CAAC,UAAU;IAA/C,OAAO,eAA8D;IAErE,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,gBAAgB,GAAG,WAAW,CAEnD;IAED,MAAM,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAS/C;CACF"}
1
+ {"version":3,"file":"EntityTypes.d.ts","sourceRoot":"","sources":["../../src/typescript/EntityTypes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAG1F,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsDD,qBAAa,WAAW;IACF,OAAO,CAAC,QAAQ,CAAC,UAAU;IAA/C,OAAO,eAA8D;IAErE,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,gBAAgB,GAAG,WAAW,CAEnD;IAED,MAAM,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAS/C;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"EntityTypes.js","sourceRoot":"","sources":["../../src/typescript/EntityTypes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EntityTypes.js","sourceRoot":"","sources":["../../src/typescript/EntityTypes.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,15 @@
1
1
  import type { SchemaDescriptor } from '@fougere/schema';
2
- export interface FacadeTypesOptions {
3
- name?: string;
4
- exported?: boolean;
5
- rowType?: string;
6
- }
7
2
  export interface OpDescriptor {
8
3
  name: string;
9
4
  description?: string;
10
5
  output?: SchemaDescriptor;
11
6
  cardinality?: 'one' | 'maybe' | 'many' | 'page' | 'none';
12
7
  }
8
+ export interface FacadeTypesOptions {
9
+ name?: string;
10
+ exported?: boolean;
11
+ rowType?: string;
12
+ }
13
13
  export declare class FacadeTypes {
14
14
  private readonly operations;
15
15
  private constructor();
@@ -1 +1 @@
1
- {"version":3,"file":"FacadeTypes.d.ts","sourceRoot":"","sources":["../../src/typescript/FacadeTypes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,WAAW,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CAC1D;AAcD,qBAAa,WAAW;IACF,OAAO,CAAC,QAAQ,CAAC,UAAU;IAA/C,OAAO,eAAqE;IAE5E,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,YAAY,EAAE,GAAG,WAAW,CAE1D;IAED,MAAM,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAW/C;CACF"}
1
+ {"version":3,"file":"FacadeTypes.d.ts","sourceRoot":"","sources":["../../src/typescript/FacadeTypes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,WAAW,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CAC1D;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAcD,qBAAa,WAAW;IACF,OAAO,CAAC,QAAQ,CAAC,UAAU;IAA/C,OAAO,eAAqE;IAE5E,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,YAAY,EAAE,GAAG,WAAW,CAE1D;IAED,MAAM,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAW/C;CACF"}
@@ -2,7 +2,7 @@ import { entity, bool, text } from '@fougere/schema';
2
2
 
3
3
  export default class Load extends entity({
4
4
  root: text({ description: 'Project root directory (default: cwd)' }),
5
- door: text({ description: 'Where the calls go (default: http://127.0.0.1:3000/_fougere/call)' }),
5
+ facade: text({ description: 'Where the calls go (default: http://127.0.0.1:3000/_fougere/call)' }),
6
6
  out: text({ description: 'Where to write it, or empty to print it on stdout (default: <root>/load.js)' }),
7
7
  json: bool({ default: false, description: 'Print the scenario as JSON, with nothing else on stdout' }),
8
8
  }) {}
@@ -1,6 +1,7 @@
1
1
  import { mkdir, writeFile } from 'node:fs/promises';
2
- import { dirname, relative, resolve } from 'node:path';
3
- import { emitScan } from '@fougere/compiler';
2
+ import { loadConfig } from '@fougere/core/node';
3
+ import { dirname, join, relative, resolve } from 'node:path';
4
+ import { emitFacade, emitNames, emitScan } from '@fougere/compiler';
4
5
  import ProjectScan from '../services/ProjectScan.js';
5
6
  import type Build from '../entities/Build.js';
6
7
 
@@ -12,12 +13,20 @@ import type Build from '../entities/Build.js';
12
13
  * This is the same scan the next build performs again, so it is gitignored.
13
14
  */
14
15
  export const DEFAULT_OUT = '.fougere/scan.generated.ts';
16
+ /** Types only, beside the module — what lets a client narrow a refusal it might meet. */
17
+ export const FACADE_OUT = 'facade.generated.ts';
18
+ /** Types only — what a config may NAME, so a string cannot designate a class that is not there. */
19
+ export const NAMES_OUT = 'names.generated.d.ts';
15
20
 
16
21
  export interface BuildReport {
17
22
  /** Absolute, so a caller can print it or read it back. */
18
23
  out: string;
19
24
  /** Relative to the project root — what a human recognizes. */
20
25
  path: string;
26
+ /** Where the facades were written — one export per address, the page's facade in. */
27
+ facade: string;
28
+ /** Where the names went — the unions a config is judged against. */
29
+ names: string;
21
30
  fronds: string[];
22
31
  entities: number;
23
32
  handlers: number;
@@ -52,9 +61,23 @@ export default class BuildHandler {
52
61
  await mkdir(dirname(out), { recursive: true });
53
62
  await writeFile(out, emitScan(scan, { outFile: out }));
54
63
 
64
+ // The same scan, projected a third way. It holds no value and reaches no bundle: what a
65
+ // client cannot otherwise know is which refusals one facade can answer, because TypeScript
66
+ // records nothing about what a function throws.
67
+ const facade = join(dirname(out), FACADE_OUT);
68
+ await writeFile(facade, emitFacade(scan, { outFile: facade }));
69
+
70
+ // And a fourth, for the only place a name is still written as a string: a config file. The
71
+ // sources are the config's own keys, which no scan can find.
72
+ const config = await loadConfig(scan.root).catch(() => ({}) as Awaited<ReturnType<typeof loadConfig>>);
73
+ const names = join(dirname(out), NAMES_OUT);
74
+ await writeFile(names, emitNames(scan, { sources: Object.keys(config.sources ?? {}) }));
75
+
55
76
  return {
56
77
  out,
57
78
  path: relative(scan.root, out),
79
+ facade: relative(scan.root, facade),
80
+ names: relative(scan.root, names),
58
81
  fronds: scan.fronds.map((frond) => frond.name),
59
82
  entities: scan.fronds.reduce((total, frond) => total + frond.entities.length, 0),
60
83
  handlers: scan.fronds.reduce((total, frond) => total + frond.handlers.length, 0),
@@ -4,33 +4,19 @@ import {
4
4
  buildGraph,
5
5
  clusterEntities,
6
6
  resolveEffectiveOperations,
7
- type ScanDiagnostic,
7
+ type Diagnostic,
8
8
  } from '@fougere/core';
9
9
  import { adaptersOf, crossFrondImports, handlerDeclarations, outsideConventions } from '@fougere/compiler';
10
10
  import { resolveConventions } from '@fougere/core';
11
11
  import ProjectScan from '../services/ProjectScan.js';
12
-
13
- /** One thing that does not hold, in the terms of whoever has to fix it. */
14
- export interface Finding {
15
- severity: 'blocking' | 'warning';
16
- /** Stable rule name — the same vocabulary a scan diagnostic uses. */
17
- code: string;
18
- /** Where to go and look. */
19
- filePath: string;
20
- /**
21
- * What the finding is ABOUT — `PostHandler.whoNull(user)` — when the rule holds it
22
- * as a fact rather than inside its sentence. Two ops of one handler breaking the
23
- * same rule read as one repeated line without it.
24
- */
25
- subject?: string;
26
- /** What is wrong, and what it costs. One sentence. */
27
- message: string;
28
- }
12
+ import { statSync } from 'node:fs';
13
+ import { dirname, join } from 'node:path';
14
+ import { FACADE_OUT, DEFAULT_OUT } from './BuildHandler.js';
29
15
 
30
16
  export interface CheckResult {
31
17
  fronds: number;
32
18
  handlers: number;
33
- findings: Finding[];
19
+ findings: Diagnostic[];
34
20
  }
35
21
 
36
22
  /** Shared with `fougere graph` — one threshold, so the two never disagree. */
@@ -63,7 +49,7 @@ export default class CheckHandler {
63
49
  remotes: config.remotes,
64
50
  adapters: config.adapters,
65
51
  });
66
- const findings: Finding[] = model.diagnostics.map(asFinding);
52
+ const findings: Diagnostic[] = [...model.diagnostics];
67
53
  const handlers = fronds.reduce((count, frond) => count + frond.handlers.length, 0);
68
54
 
69
55
  /**
@@ -121,7 +107,7 @@ export default class CheckHandler {
121
107
 
122
108
  /**
123
109
  * A `pattern` that backtracks super-linearly. The pattern is fixed at declaration and
124
- * the string tested against it is not: whoever calls the door chooses it, so the cost
110
+ * the string tested against it is not: whoever calls the facade chooses it, so the cost
125
111
  * of one match is theirs to set. Judged HERE because a regex says the same thing on
126
112
  * every run — checking it per row would pay for a verdict that never changes.
127
113
  *
@@ -191,17 +177,40 @@ export default class CheckHandler {
191
177
  });
192
178
  }
193
179
 
180
+ findings.push(...facadeFindings(root, fronds));
181
+
194
182
  return { fronds: fronds.length, handlers, findings };
195
183
  }
196
184
  }
197
185
 
198
- /** A scan diagnostic IS a finding — same vocabulary, so the renderer has one shape. */
199
- function asFinding(d: ScanDiagnostic): Finding {
200
- return {
201
- severity: d.severity,
202
- code: d.code,
203
- filePath: d.filePath,
204
- subject: d.subject,
205
- message: d.message,
206
- };
186
+ /**
187
+ * A facade module older than the handlers it was read off.
188
+ *
189
+ * An ABSENT module needs no finding: a page importing one fails to resolve, loudly, and a
190
+ * backend project never imports one at all — warning there would fire on every project that
191
+ * does not want a facade, which is most of them.
192
+ *
193
+ * A STALE one is the silent case. The import resolves, the operations are the old ones, and a
194
+ * renamed op compiles against a name nothing serves any more. An mtime is the whole check.
195
+ */
196
+ function facadeFindings(root: string, fronds: readonly { handlers: readonly { filePath: string }[] }[]): Diagnostic[] {
197
+ const facade = join(root, dirname(DEFAULT_OUT), FACADE_OUT);
198
+ const written = statSync(facade, { throwIfNoEntry: false })?.mtimeMs;
199
+ if (written === undefined) return [];
200
+
201
+ const newer = fronds
202
+ .flatMap((frond) => frond.handlers)
203
+ .filter((handler) => (statSync(handler.filePath, { throwIfNoEntry: false })?.mtimeMs ?? 0) > written);
204
+
205
+ if (newer.length === 0) return [];
206
+
207
+ return [{
208
+ severity: 'warning',
209
+ code: 'facade-stale',
210
+ filePath: facade,
211
+ subject: `${newer.length} handler${newer.length > 1 ? 's' : ''}`,
212
+ message: 'The facade module is older than the handlers it was read off, so a page checks its '
213
+ + 'calls against operations that may no longer exist. Run `fougere build`.',
214
+ }];
207
215
  }
216
+
@@ -6,7 +6,7 @@ import ProjectScan from '../services/ProjectScan.js';
6
6
  export interface LoadScenario {
7
7
  /** Where it was written, or `null` when it was only printed. */
8
8
  file: string | null;
9
- door: string;
9
+ facade: string;
10
10
  /** Every operation the scenario calls, in the order it lists them. */
11
11
  operations: string[];
12
12
  script: string;
@@ -21,21 +21,21 @@ export interface LoadScenario {
21
21
  export default class LoadHandler {
22
22
  constructor(private projectScan: ProjectScan) {}
23
23
 
24
- /** Generate a k6 scenario covering every operation the default door answers. */
25
- async execute(input: { root?: string; door?: string; out?: string }): Promise<LoadScenario> {
24
+ /** Generate a k6 scenario covering every operation the default facade answers. */
25
+ async execute(input: { root?: string; facade?: string; out?: string }): Promise<LoadScenario> {
26
26
  const { root, fronds, config } = await this.projectScan.at(input.root);
27
- const door = input.door?.trim() || undefined;
27
+ const facade = input.facade?.trim() || undefined;
28
28
  // The topology statement travels with it: an op that crosses a process is not held to the
29
29
  // same figure as one that never leaves, and `remotes:` is what says which is which.
30
30
  const remotes = config.remotes ?? {};
31
- const script = loadScript({ fronds }, { ...(door ? { door } : {}), remotes });
31
+ const script = loadScript({ fronds }, { ...(facade ? { facade } : {}), remotes });
32
32
 
33
33
  const file = input.out === undefined ? join(root, 'load.js') : input.out || null;
34
34
  if (file) await writeFile(file, script, 'utf8');
35
35
 
36
36
  return {
37
37
  file,
38
- door: door ?? 'http://127.0.0.1:3000/_fougere/call',
38
+ facade: facade ?? 'http://127.0.0.1:3000/_fougere/call',
39
39
  operations: reachableOps({ fronds }, {}, remotes).map((one) => one.method),
40
40
  script,
41
41
  };
@@ -1,5 +1,5 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync } from 'node:fs';
2
+ import { existsSync, writeFileSync, readFileSync, readdirSync, unlinkSync } from 'node:fs';
3
3
  import { join, basename } from 'node:path';
4
4
  import { resolveConventions, frondPackage } from '@fougere/core';
5
5
  import { loadConfig } from '@fougere/core/node';
@@ -9,6 +9,23 @@ import { FacadeTypes } from '../../../src/typescript/FacadeTypes.js';
9
9
  import { assertIdentityCard, type IdentityCard } from '@fougere/core';
10
10
  import { type Conventions, resolveConventions, frondPackage } from '@fougere/core';
11
11
  import { loadConfig } from '@fougere/core/node';
12
+ import { facadeModule, type Served } from '@fougere/compiler';
13
+ import { ErrorCode } from '@fougere/core/contract';
14
+
15
+ /**
16
+ * A union of enum MEMBERS, from what the card said this operation refuses.
17
+ *
18
+ * `ErrorCode` is a string enum, so `'CONFLICT'` is not assignable to it: a literal reads as the
19
+ * right thing and then refuses to narrow `FougereError<Code>`, which is the whole point. A code
20
+ * this version does not know is dropped rather than written — the far side may be newer, and a
21
+ * name that resolves to nothing would stop the consumer's build.
22
+ */
23
+ function codesOf(errors: readonly string[] | undefined): string {
24
+ const known = (errors ?? []).filter((code) => code in ErrorCode);
25
+ if (known.length === 0) return 'never';
26
+
27
+ return known.map((code) => `ErrorCode.${code}`).join(' | ');
28
+ }
12
29
 
13
30
  function assertSafeName(kind: string, name: string): void {
14
31
  if (typeof name !== 'string' || !/^[A-Za-z_$][A-Za-z0-9_$-]*$/.test(name)) {
@@ -36,10 +53,10 @@ export function entityClassName(name: string): string {
36
53
  }
37
54
 
38
55
  /**
39
- * One entry — a door or a fact — validated the same way, because sync consumes the same two
56
+ * One entry — a facade or a fact — validated the same way, because sync consumes the same two
40
57
  * values from both: a name it can turn into a class, and a descriptor it can rebuild.
41
58
  *
42
- * A missing descriptor is legal on either side and means different things: a door that
59
+ * A missing descriptor is legal on either side and means different things: a facade that
43
60
  * stores nothing (a health check, a search across shapes), or a fact whose announced type
44
61
  * is not a declared entity. Neither produces a row class, and demanding one here refused
45
62
  * the WHOLE card over a single entry.
@@ -72,7 +89,7 @@ function assertEntry(kind: string, frondName: string, entry: { name: string; sch
72
89
 
73
90
  function identityCardOf(value: unknown): IdentityCard {
74
91
  // The card's own shape is validated by the package that declares it — `fronds`, and each
75
- // frond's `doors`. What stays here is what only a writer of files needs: a name safe to
92
+ // frond's `facades`. What stays here is what only a writer of files needs: a name safe to
76
93
  // become one, and the descriptor a class is generated from.
77
94
  const card = assertIdentityCard(value, 'Remote rpc.discover');
78
95
  for (const frond of card.fronds) {
@@ -83,7 +100,7 @@ function identityCardOf(value: unknown): IdentityCard {
83
100
  if (frond.facts !== undefined && !Array.isArray(frond.facts)) {
84
101
  throw new Error(`Remote frond '${frond.name}' has no valid facts array`);
85
102
  }
86
- for (const door of frond.doors) assertEntry('door', frond.name, door);
103
+ for (const facade of frond.facades) assertEntry('facade', frond.name, facade);
87
104
  for (const fact of frond.facts ?? []) assertEntry('fact', frond.name, fact);
88
105
  }
89
106
  return card;
@@ -142,17 +159,17 @@ export default class SyncHandler {
142
159
  /**
143
160
  * What was written under each name — the barrel below is a projection of exactly this.
144
161
  *
145
- * Three combinations, and all three occur: a door with rows behind it (both files), a
146
- * door with none (the façade type alone), and a fact (the row class alone, because
162
+ * Three combinations, and all three occur: a facade with rows behind it (both files), a
163
+ * facade with none (the façade type alone), and a fact (the row class alone, because
147
164
  * nothing calls a fact).
148
165
  */
149
- const generated = new Map<string, { row: boolean; door: boolean }>();
166
+ const generated = new Map<string, { row: boolean; facade: boolean }>();
150
167
  /** Absolute paths written by THIS run — anything else generated here is now stale. */
151
168
  const written = new Set<string>();
152
169
  const claim = (name: string): string => {
153
170
  const className = entityClassName(name);
154
171
  if (generated.has(className)) throw new Error(`Remote declares duplicate entity '${className}'`);
155
- generated.set(className, { row: false, door: false });
172
+ generated.set(className, { row: false, facade: false });
156
173
  return className;
157
174
  };
158
175
 
@@ -179,16 +196,24 @@ export default class SyncHandler {
179
196
  generated.get(className)!.row = true;
180
197
  };
181
198
 
182
- for (const { name, schema: descriptor, ops } of target.doors) {
183
- const className = claim(name);
184
- generated.get(className)!.door = true;
199
+ /** What the card says of each address — the same three facts a scan gives. */
200
+ const served: Served[] = [];
185
201
 
186
- // No shape behind this door: its operations still travel, its rows do not exist.
202
+ for (const { name, schema: descriptor, ops } of target.facades) {
203
+ const className = claim(name);
204
+ generated.get(className)!.facade = true;
205
+ served.push({
206
+ at: name,
207
+ handler: `import('./${handlers}/${className}Handler.js').${className}Handler`,
208
+ ops: (ops ?? []).map((op) => ({ name: op.name, codes: codesOf(op.errors) })),
209
+ });
210
+
211
+ // No shape behind this facade: its operations still travel, its rows do not exist.
187
212
  // `rowType` falls back to `unknown`, which is the truth rather than an empty class.
188
213
  if (descriptor !== undefined) writeRow(className, descriptor as SchemaDescriptor);
189
214
 
190
215
  /**
191
- * The door's type, next to the row's — what `Facade<T>` needs and what nothing
216
+ * The facade's type, next to the row's — what `Facade<T>` needs and what nothing
192
217
  * carried across a repository boundary.
193
218
  *
194
219
  * Writing `Facade<ArticleHandler>` used to require importing the handler's class.
@@ -229,12 +254,26 @@ export default class SyncHandler {
229
254
  writeRow(className, descriptor as SchemaDescriptor);
230
255
  }
231
256
 
257
+ /**
258
+ * The facade module, written from the CARD rather than from a scan.
259
+ *
260
+ * A frond in another repository cannot be scanned — there are no sources here. What the
261
+ * card carries is exactly the three facts the module needs: the addresses, the operations,
262
+ * and what each one refuses. The handler TYPE is the one thing it cannot carry, so the
263
+ * synthetic interface written above stands in its place: it names the same operations with
264
+ * the same answers, which is what a page reads.
265
+ *
266
+ * `facadeModule` is the compiler's own, not a copy: two spellings of one format would
267
+ * drift the day either gained a member.
268
+ */
269
+ writeFileSync(join(frondDir, 'facade.ts'), facadeModule(served, `${baseUrl} — a card, not a scan`));
270
+
232
271
  // Barrel index
233
272
  // One binding carries the value AND the type, because a class is both — the pair of
234
273
  // re-exports that stood here was the price of declaring them separately.
235
- const indexLines = [...generated].flatMap(([name, { row, door }]) => [
274
+ const indexLines = [...generated].flatMap(([name, { row, facade }]) => [
236
275
  ...(row ? [`export { default as ${name} } from './${entities}/${name}.js';`] : []),
237
- ...(door ? [`export type { ${name}Handler } from './${handlers}/${name}Handler.js';`] : []),
276
+ ...(facade ? [`export type { ${name}Handler } from './${handlers}/${name}Handler.js';`] : []),
238
277
  ]);
239
278
  writeFileSync(join(frondDir, 'index.ts'), indexLines.join('\n') + '\n');
240
279
 
@@ -265,7 +304,7 @@ export default class SyncHandler {
265
304
  * — but the FILE stayed, and the generated `package.json` exports `'./entities/*'` as
266
305
  * a wildcard, so `@fronds/blog/entities/Ticket.js` kept resolving to a class nothing
267
306
  * behind it answers for. The consumer compiles, its local validator accepts, and the call
268
- * comes back NOT_FOUND at the door — or never leaves, because the page dropped the
307
+ * comes back NOT_FOUND at the facade — or never leaves, because the page dropped the
269
308
  * call and kept the type.
270
309
  */
271
310
  const removed = [...this.prune(entitiesDir, written), ...this.prune(handlersDir, written)];
@@ -30,6 +30,52 @@ function monorepoPackages(): string | undefined {
30
30
  */
31
31
  const TEMPLATES = fileURLToPath(new URL('../../../templates/', import.meta.url));
32
32
 
33
+ /**
34
+ * Where a HOST keeps its starter, or nothing when it ships none.
35
+ *
36
+ * The package that owns the wiring owns the files: `@fougere/nuxt` knows what a
37
+ * `nuxt.config.ts` must say, and a copy of it here would be that knowledge written twice —
38
+ * which is how `templates/apps/` came to hold one host while six were published.
39
+ *
40
+ * Resolved from the main entry and walked up to the manifest, because a host does not export
41
+ * its own `package.json` and has no reason to.
42
+ *
43
+ * `import.meta.resolve` and not `createRequire().resolve`: a host's `exports` states only the
44
+ * `import` condition, so the CJS resolver answers `ERR_PACKAGE_PATH_NOT_EXPORTED` for every
45
+ * one of them.
46
+ */
47
+ function starterOf(pkg: string): string | undefined {
48
+ let dir: string;
49
+ try {
50
+ dir = dirname(fileURLToPath(import.meta.resolve(pkg)));
51
+ } catch { return undefined; }
52
+
53
+ while (!existsSync(join(dir, 'package.json')) && dir !== dirname(dir)) dir = dirname(dir);
54
+ const template = join(dir, 'template');
55
+
56
+ return existsSync(template) ? template : undefined;
57
+ }
58
+
59
+ /**
60
+ * The hosts this CLI can scaffold: its own `@fougere/*` dependencies that ship a starter.
61
+ *
62
+ * The dependency list IS the registry — nothing to declare, and a host added to the family
63
+ * appears here the day the CLI depends on it.
64
+ */
65
+ function hosts(): Map<string, string> {
66
+ const manifest = JSON.parse(readFileSync(fileURLToPath(new URL('../../../package.json', import.meta.url)), 'utf8')) as
67
+ { dependencies?: Record<string, string> };
68
+ const found = new Map<string, string>();
69
+
70
+ for (const pkg of Object.keys(manifest.dependencies ?? {})) {
71
+ if (!pkg.startsWith('@fougere/')) continue;
72
+ const starter = starterOf(pkg);
73
+ if (starter) found.set(pkg.slice('@fougere/'.length), starter);
74
+ }
75
+
76
+ return found;
77
+ }
78
+
33
79
  /** The version that scaffolds is the version the templates were written for. */
34
80
  const scaffoldVersion = (): string =>
35
81
  (JSON.parse(readFileSync(fileURLToPath(new URL('../../../package.json', import.meta.url)), 'utf8')) as
@@ -102,19 +148,31 @@ export default class ProjectWriter {
102
148
  return { path: dest };
103
149
  }
104
150
 
105
- /** Add an app (consumer) under apps/<name>. */
151
+ /** Add an app (consumer) under apps/<name>, from the host package that owns its wiring. */
106
152
  addApp(wsDir: string, template: string, name: string): { path: string } {
107
153
  const dest = join(wsDir, 'apps', name);
108
- cpSync(join(TEMPLATES, 'apps', template), dest, { recursive: true });
154
+ const starter = hosts().get(template);
155
+ if (!starter) throw new Error(`No host ships a starter for '${template}'. Served: ${[...hosts().keys()].join(', ')}.`);
156
+
157
+ cpSync(starter, dest, { recursive: true });
109
158
  restoreGitignore(dest);
110
159
  setPackageName(dest, name);
111
160
  return { path: dest };
112
161
  }
113
162
 
114
- /** Available templates of a kind ('fronds' | 'apps') — the directory is the registry. */
163
+ /**
164
+ * What can be scaffolded, of a kind.
165
+ *
166
+ * A FROND comes from this package: it is Fougere's own vocabulary, entities and handlers,
167
+ * and no other package owns it. An APP comes from its HOST — the registry is the hosts this
168
+ * CLI depends on, so `fougere new` offers what is published rather than what was copied here.
169
+ */
115
170
  listTemplates(kind: 'fronds' | 'apps'): string[] {
171
+ if (kind === 'apps') return [...hosts().keys()].sort();
172
+
116
173
  const dir = join(TEMPLATES, kind);
117
174
  if (!existsSync(dir)) return [];
175
+
118
176
  return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
119
177
  }
120
178
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fougere/cli",
3
- "version": "0.9.3-alpha.0",
3
+ "version": "0.11.0-alpha.0",
4
4
  "description": "The Fougere CLI — compose a workspace, serve a frond, call an operation.",
5
5
  "keywords": [
6
6
  "fougere",
@@ -42,14 +42,20 @@
42
42
  "jiti": "^2.4.2",
43
43
  "picocolors": "^1.1.1",
44
44
  "safe-regex": "^2.1.1",
45
- "@fougere/transport-http": "0.9.3-alpha.0",
46
- "@fougere/adapter-sql": "0.9.3-alpha.0",
47
- "@fougere/testing": "0.9.3-alpha.0",
48
- "@fougere/defaults": "0.9.3-alpha.0",
49
- "@fougere/container": "0.9.3-alpha.0",
50
- "@fougere/schema": "0.9.3-alpha.0",
51
- "@fougere/compiler": "0.9.3-alpha.0",
52
- "@fougere/core": "0.9.3-alpha.0"
45
+ "@fougere/adapter-sql": "0.11.0-alpha.0",
46
+ "@fougere/core": "0.11.0-alpha.0",
47
+ "@fougere/compiler": "0.11.0-alpha.0",
48
+ "@fougere/container": "0.11.0-alpha.0",
49
+ "@fougere/defaults": "0.11.0-alpha.0",
50
+ "@fougere/nuxt": "0.11.0-alpha.0",
51
+ "@fougere/schema": "0.11.0-alpha.0",
52
+ "@fougere/testing": "0.11.0-alpha.0",
53
+ "@fougere/svelte": "0.11.0-alpha.0",
54
+ "@fougere/transport-http": "0.11.0-alpha.0",
55
+ "@fougere/next": "0.11.0-alpha.0",
56
+ "@fougere/oclif": "0.11.0-alpha.0",
57
+ "@fougere/admin": "0.11.0-alpha.0",
58
+ "@fougere/react": "0.11.0-alpha.0"
53
59
  },
54
60
  "devDependencies": {
55
61
  "vitest": "^4.1.0"
package/src/bridge.ts CHANGED
@@ -8,23 +8,46 @@ function toKebab(name: string): string {
8
8
  return name.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
9
9
  }
10
10
 
11
+ /**
12
+ * What a caller supplies: the fields the axes admit, or the PRIMARY when they admit nothing.
13
+ *
14
+ * `Visibility.input` answers who may WRITE a field, and a primary is the server's — right for a
15
+ * form, wrong for an operation whose whole input is `Product.pick('id')`. That contract asks the
16
+ * caller to NAME a row, and the axes leave it with no argument at all: `product:archive abc-9`
17
+ * answered `Unexpected argument`.
18
+ *
19
+ * Only when nothing else remains, so `create` over a full entity is untouched — its input holds
20
+ * twelve writable fields and the primary stays the server's.
21
+ */
22
+ function suppliedIn(fields: Fields): Fields {
23
+ const written = Visibility.of(fields).input;
24
+ if (Object.keys(written).length > 0) return written;
25
+
26
+ return Object.fromEntries(
27
+ Object.entries(fields).filter(([, field]) => Role.of(field).isPrimary),
28
+ ) as Fields;
29
+ }
30
+
11
31
  /** Convert an Entity's fields into citty args definition. */
12
32
  export function entityToArgs(fields: Fields): ArgsDef {
13
33
  const args: ArgsDef = {};
14
34
  let positionalIndex = 0;
15
35
 
16
- // Axes-derived ingress membership; the CLI additionally skips ALL relations
17
- // (a ref is not a flag — supplying related rows is not a CLI gesture).
18
- for (const [key, field] of Object.entries(Visibility.of(fields).input)) {
36
+ // The CLI additionally skips ALL relations: a ref is not a flag — supplying related rows is
37
+ // not a CLI gesture.
38
+ for (const [key, field] of Object.entries(suppliedIn(fields))) {
19
39
  if (Role.of(field).isRelation) continue;
20
40
 
21
41
  // A `default(v)` travels as the create rule `{ value }` — citty shows it.
22
42
  const defaultValue = Lifecycle.of(field).literal?.value;
23
43
  const { base: shape, nullable } = Shapes.of(field.shape);
24
44
  const type = Shapes.typeOf(field.shape);
45
+ // Naming a row is required by definition: a generator fills a primary the server writes,
46
+ // never one a caller hands in to designate.
47
+ const designates = Role.of(field).isPrimary;
25
48
  const common = {
26
49
  description: field.meta?.description,
27
- required: !nullable && Lifecycle.of(field).requiredAtCreate,
50
+ required: designates || (!nullable && Lifecycle.of(field).requiredAtCreate),
28
51
  };
29
52
 
30
53
  // A closed set is citty's `enum`: the shape already names the legal values, so the
package/src/machine.ts CHANGED
@@ -4,7 +4,7 @@ export function machineWanted(raw: Record<string, unknown>): boolean {
4
4
  }
5
5
 
6
6
  /**
7
- * A `Map` serializes to `{}`, so the door converts it rather than each command flattening its own
7
+ * A `Map` serializes to `{}`, so the facade converts it rather than each command flattening its own
8
8
  * result: `GraphResult.nodes` is a Map, and `graph --json` would have printed a report with an
9
9
  * empty graph in it.
10
10
  */
package/src/runner.ts CHANGED
@@ -22,11 +22,11 @@ function toCamel(kebab: string): string {
22
22
 
23
23
  /** Scan app/commands/ for command classes. */
24
24
  async function loadAppCommands(
25
- cliRoot: string,
25
+ root: string,
26
26
  loader: (path: string) => Promise<Record<string, unknown>>,
27
27
  ): Promise<Map<string, new (...args: unknown[]) => { run: (raw: Record<string, unknown>) => Promise<void> }>> {
28
28
  const map = new Map();
29
- const dir = join(cliRoot, 'app', 'commands');
29
+ const dir = join(root, 'app', 'commands');
30
30
  const files = await readdir(dir, { withFileTypes: true }).catch(() => []);
31
31
 
32
32
  for (const f of files) {
@@ -42,14 +42,21 @@ async function loadAppCommands(
42
42
  return map;
43
43
  }
44
44
 
45
- export async function run(app: App): Promise<void> {
45
+ /**
46
+ * Every operation of an app, as a terminal command.
47
+ *
48
+ * `root` is where the PRESENTATION classes are looked for — `app/commands/`, one per command
49
+ * that wants to print something of its own. It defaults to this package, which is how
50
+ * `npx fougere` finds its fifteen; a project passes its own and gets the same treatment for
51
+ * the operations it declares, with no class at all where a default rendering will do.
52
+ */
53
+ export async function run(app: App, root = new URL('..', import.meta.url).pathname): Promise<void> {
46
54
  const terminal = ui();
47
- const cliRoot = new URL('..', import.meta.url).pathname;
48
55
 
49
56
  const { createJiti } = await import('jiti');
50
57
  const jiti = createJiti(import.meta.url, { interopDefault: true });
51
58
  const loader = (path: string) => jiti.import(path) as Promise<Record<string, unknown>>;
52
- const appCommands = await loadAppCommands(cliRoot, loader);
59
+ const appCommands = await loadAppCommands(root, loader);
53
60
 
54
61
  const subCommands: Record<string, ReturnType<typeof defineCommand>> = {};
55
62
 
@@ -86,7 +93,7 @@ export async function run(app: App): Promise<void> {
86
93
  meta: {
87
94
  name: cmdName,
88
95
  // `--help` reads the operation's own doc sentence, which the scan already
89
- // carries for every door (`OperationContract.description`). A table here
96
+ // carries for every facade (`OperationContract.description`). A table here
90
97
  // would be the same fact written twice, and it drifted: it described `add`
91
98
  // and `doctor`, which do not exist, and had nothing for `call` or `serve`.
92
99
  description: handlerEntry.operations.get('execute')?.description,
@@ -1,6 +1,11 @@
1
1
  import { upperFirst, type FieldDescriptor, type SchemaDescriptor } from '@fougere/schema';
2
2
  import { docCommentOf, propertyKey } from './syntax.js';
3
3
 
4
+ export interface EntityTypesOptions {
5
+ name?: string;
6
+ exported?: boolean;
7
+ }
8
+
4
9
  /** So a nullable field lands as a union. */
5
10
  function typeOf(field: FieldDescriptor): string {
6
11
  const types = Array.isArray(field.type) ? field.type : field.type ? [field.type] : [];
@@ -41,11 +46,6 @@ function objectTypeOf(properties: Record<string, FieldDescriptor>, required: rea
41
46
  return `{ ${members.join('; ')} }`;
42
47
  }
43
48
 
44
- export interface EntityTypesOptions {
45
- name?: string;
46
- exported?: boolean;
47
- }
48
-
49
49
  /** So the generated class carries its row type. */
50
50
  function shapeTypeOf(descriptor: SchemaDescriptor, indent = ''): string {
51
51
  const entries = Object.entries(descriptor.properties ?? {});
@@ -1,12 +1,6 @@
1
1
  import type { SchemaDescriptor } from '@fougere/schema';
2
2
  import { docCommentOf, propertyKey } from './syntax.js';
3
3
 
4
- export interface FacadeTypesOptions {
5
- name?: string;
6
- exported?: boolean;
7
- rowType?: string;
8
- }
9
-
10
4
  export interface OpDescriptor {
11
5
  name: string;
12
6
  description?: string;
@@ -14,6 +8,12 @@ export interface OpDescriptor {
14
8
  cardinality?: 'one' | 'maybe' | 'many' | 'page' | 'none';
15
9
  }
16
10
 
11
+ export interface FacadeTypesOptions {
12
+ name?: string;
13
+ exported?: boolean;
14
+ rowType?: string;
15
+ }
16
+
17
17
  /** So a consumer sees the cardinality in the type, not in a doc line. */
18
18
  function returnTypeOf(op: OpDescriptor, rowType: string): string {
19
19
  switch (op.cardinality) {
@@ -7,6 +7,6 @@ export default class Post extends entity({
7
7
  createdAt: created(),
8
8
  // Server-owned: a post is born a draft and flipped by the publish
9
9
  // operation, never by a client writing the field. readOnly closes
10
- // the inbound door — the field is projected out, never accepted in.
10
+ // the inbound facade — the field is projected out, never accepted in.
11
11
  status: readOnly(oneOf('draft', 'published', { default: 'draft' })),
12
12
  }) {}
@@ -25,11 +25,11 @@ Two consequences worth stating, because they are what makes it hold:
25
25
 
26
26
  If you are about to write the same constraint in two places, you have missed the derivation.
27
27
 
28
- ## A surface is a door, never a logic
28
+ ## A surface is a facade, never a logic
29
29
 
30
- Every door goes through the handler **façade**, which is where validation sits: unknown-key refusal,
30
+ Every facade goes through the handler **façade**, which is where validation sits: unknown-key refusal,
31
31
  collectors. A resolver or route you wire yourself against the storage — or worse, against the database —
32
- is a second door with no validator behind it, and the rules declared in the entities stop applying there.
32
+ is a second facade with no validator behind it, and the rules declared in the entities stop applying there.
33
33
 
34
34
  Before adding a surface, reach for its **projection**:
35
35
 
@@ -25,11 +25,11 @@ Two consequences worth stating, because they are what makes it hold:
25
25
 
26
26
  If you are about to write the same constraint in two places, you have missed the derivation.
27
27
 
28
- ## A surface is a door, never a logic
28
+ ## A surface is a facade, never a logic
29
29
 
30
- Every door goes through the handler **façade**, which is where validation sits: unknown-key refusal,
30
+ Every facade goes through the handler **façade**, which is where validation sits: unknown-key refusal,
31
31
  collectors. A resolver or route you wire yourself against the storage — or worse, against the database —
32
- is a second door with no validator behind it, and the rules declared in the entities stop applying there.
32
+ is a second facade with no validator behind it, and the rules declared in the entities stop applying there.
33
33
 
34
34
  Before adding a surface, reach for its **projection**:
35
35
 
@@ -25,11 +25,11 @@ Two consequences worth stating, because they are what makes it hold:
25
25
 
26
26
  If you are about to write the same constraint in two places, you have missed the derivation.
27
27
 
28
- ## A surface is a door, never a logic
28
+ ## A surface is a facade, never a logic
29
29
 
30
- Every door goes through the handler **façade**, which is where validation sits: unknown-key refusal,
30
+ Every facade goes through the handler **façade**, which is where validation sits: unknown-key refusal,
31
31
  collectors. A resolver or route you wire yourself against the storage — or worse, against the database —
32
- is a second door with no validator behind it, and the rules declared in the entities stop applying there.
32
+ is a second facade with no validator behind it, and the rules declared in the entities stop applying there.
33
33
 
34
34
  Before adding a surface, reach for its **projection**:
35
35
 
@@ -1,25 +0,0 @@
1
- <template>
2
- <NuxtPage />
3
- </template>
4
-
5
- <style>
6
- :root { --fg: #1a1a1a; --muted: #6b7280; --border: #e5e7eb; --accent: #2563eb; }
7
- * { box-sizing: border-box; }
8
- body { margin: 0; font-family: system-ui, -apple-system, sans-serif; color: var(--fg); line-height: 1.6; }
9
- .wrap { max-width: 720px; margin: 3rem auto; padding: 0 1.5rem; }
10
- a { color: var(--accent); text-decoration: none; }
11
- a:hover { text-decoration: underline; }
12
- h1 { font-size: 1.6rem; margin-bottom: 0.25rem; }
13
- code { background: #f3f4f6; padding: 0.1rem 0.35rem; border-radius: 4px; font-size: 0.9em; }
14
- table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
15
- th, td { text-align: left; padding: 0.6rem 0.5rem; border-bottom: 1px solid var(--border); }
16
- th { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }
17
- label { display: block; font-size: 0.85rem; margin: 0.9rem 0 0.3rem; color: var(--muted); }
18
- input, textarea { width: 100%; padding: 0.5rem 0.6rem; border: 1px solid var(--border); border-radius: 6px; font: inherit; }
19
- button { padding: 0.5rem 0.9rem; border: 0; border-radius: 6px; background: var(--accent); color: #fff; font: inherit; cursor: pointer; }
20
- button:disabled { opacity: 0.5; cursor: default; }
21
- .badge { font-size: 0.72rem; padding: 0.15rem 0.55rem; border-radius: 999px; background: #f3f4f6; color: var(--muted); }
22
- .badge.published { background: #dcfce7; color: #166534; }
23
- .muted { color: var(--muted); }
24
- .err { color: #b91c1c; font-size: 0.9rem; }
25
- </style>
@@ -1,33 +0,0 @@
1
- <script setup lang="ts">
2
- // The four primitives are auto-imported by @fougere/nuxt — no import line to write.
3
- // They take an ENTITY, so this page names none: what is composed here is yours, and a
4
- // scaffold that guessed at an entity shipped a page that could not run.
5
- //
6
- // import Post from '@fronds/<your-frond>/entities/Post'
7
- // const { items, loading } = await useQuery(Post, 'list')
8
- // const { values, errors, submit } = useFormFor(Post)
9
- // const { execute } = useCommand(Post, 'publish')
10
- const { user } = useCurrentUser();
11
- </script>
12
-
13
- <template>
14
- <main class="wrap">
15
- <h1>🌿 Fougere</h1>
16
- <p class="muted">
17
- The app boots, the fronds are scanned, the surface is mounted. Write a page against
18
- one of your entities — the commented block in this file is the whole API.
19
- </p>
20
- <p class="muted">Reader: <strong>{{ user?.name ?? 'nobody' }}</strong></p>
21
- <p class="muted">What to write, and why: <code>CLAUDE.md</code> at the workspace root.</p>
22
- </main>
23
- </template>
24
-
25
- <style>
26
- :root { --fg: #1a1a1a; --muted: #6b7280; }
27
- * { box-sizing: border-box; }
28
- body { margin: 0; font-family: system-ui, -apple-system, sans-serif; color: var(--fg); line-height: 1.6; }
29
- .wrap { max-width: 720px; margin: 3rem auto; padding: 0 1.5rem; }
30
- h1 { font-size: 1.6rem; margin-bottom: 0.25rem; }
31
- code { background: #f3f4f6; padding: 0.1rem 0.35rem; border-radius: 4px; font-size: 0.9em; }
32
- .muted { color: var(--muted); }
33
- </style>
@@ -1,6 +0,0 @@
1
- export default defineNuxtConfig({
2
- modules: ['@fougere/nuxt'],
3
- // Fronds are shared at the workspace root, two levels up from apps/<name>.
4
- fougere: { root: '../..' },
5
- compatibilityDate: '2026-07-20',
6
- });
@@ -1,19 +0,0 @@
1
- {
2
- "name": "fougere-app",
3
- "private": true,
4
- "type": "module",
5
- "scripts": {
6
- "dev": "nuxt dev",
7
- "build": "nuxt build"
8
- },
9
- "dependencies": {
10
- "@fougere/core": "latest",
11
- "@fougere/nuxt": "latest",
12
- "@fougere/schema": "latest",
13
- "@fougere/defaults": "latest",
14
- "better-sqlite3": "^13.0.3",
15
- "kysely": "^0.28.17",
16
- "nuxt": "^4.5.1",
17
- "@fougere/container": "latest"
18
- }
19
- }
@@ -1,3 +0,0 @@
1
- {
2
- "extends": "./.nuxt/tsconfig.json"
3
- }