@fougere/cli 0.2.0-alpha.2 → 0.3.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 (62) hide show
  1. package/README.md +10 -1
  2. package/app/commands/BuildCommand.ts +39 -0
  3. package/app/commands/CallCommand.ts +2 -2
  4. package/app/commands/CheckCommand.ts +2 -1
  5. package/app/commands/ExplainCommand.ts +77 -0
  6. package/app/commands/FreezeCommand.ts +107 -0
  7. package/app/commands/GrantCommand.ts +44 -0
  8. package/app/commands/KeysCommand.ts +56 -0
  9. package/app/commands/MigrateCommand.ts +54 -0
  10. package/app/commands/NewCommand.ts +5 -5
  11. package/app/commands/ServeCommand.ts +83 -7
  12. package/app/commands/grant-material.ts +5 -0
  13. package/dist/bin.js +53 -7
  14. package/dist/bin.js.map +1 -1
  15. package/dist/bridge.d.ts.map +1 -1
  16. package/dist/bridge.js +4 -4
  17. package/dist/bridge.js.map +1 -1
  18. package/dist/runner.d.ts.map +1 -1
  19. package/dist/runner.js +6 -6
  20. package/dist/runner.js.map +1 -1
  21. package/fronds/analysis/entities/Build.ts +7 -0
  22. package/fronds/analysis/entities/Explain.ts +8 -0
  23. package/fronds/analysis/entities/Freeze.ts +7 -0
  24. package/fronds/analysis/entities/Migrate.ts +7 -0
  25. package/fronds/analysis/handlers/BuildHandler.ts +60 -0
  26. package/fronds/analysis/handlers/CheckHandler.ts +40 -41
  27. package/fronds/analysis/handlers/ExplainHandler.ts +214 -0
  28. package/fronds/analysis/handlers/FreezeHandler.ts +172 -0
  29. package/fronds/analysis/handlers/MigrateHandler.ts +97 -0
  30. package/fronds/analysis/services/ProjectScan.ts +23 -7
  31. package/fronds/analysis/versions.ts +58 -0
  32. package/fronds/scaffold/entities/Grant.ts +6 -0
  33. package/fronds/scaffold/entities/Keys.ts +4 -0
  34. package/fronds/scaffold/entities/Serve.ts +2 -1
  35. package/fronds/scaffold/handlers/BuildFrondHandler.ts +11 -13
  36. package/fronds/scaffold/handlers/GrantHandler.ts +8 -0
  37. package/fronds/scaffold/handlers/KeysHandler.ts +8 -0
  38. package/fronds/scaffold/handlers/SyncHandler.ts +27 -24
  39. package/fronds/scaffold/services/ProjectWriter.ts +9 -8
  40. package/package.json +8 -7
  41. package/templates/admin/fronds/admin/handlers/UserHandler.ts +3 -5
  42. package/templates/admin/fronds/admin/package.json +1 -1
  43. package/templates/api/fronds/api/handlers/TaskHandler.ts +3 -5
  44. package/templates/api/fronds/api/package.json +1 -1
  45. package/templates/apps/nuxt/app/pages/index.vue +1 -1
  46. package/templates/blog/app/pages/posts/index.vue +1 -1
  47. package/templates/blog/app/pages/posts/manage.vue +1 -1
  48. package/templates/blog/app/pages/posts/new.vue +1 -1
  49. package/templates/blog/fronds/blog/handlers/PostHandler.ts +3 -5
  50. package/templates/blog/fronds/blog/package.json +1 -1
  51. package/templates/flat/AGENTS.md +14 -0
  52. package/templates/flat/CLAUDE.md +25 -3
  53. package/templates/frond/AGENTS.md +14 -0
  54. package/templates/frond/CLAUDE.md +25 -3
  55. package/templates/frond/fronds/__name__/handlers/PostHandler.ts +3 -5
  56. package/templates/frond/fronds/__name__/package.json +1 -1
  57. package/templates/frond/serve.mjs +3 -2
  58. package/templates/fronds/blank/package.json +1 -1
  59. package/templates/fronds/blog/handlers/PostHandler.ts +2 -4
  60. package/templates/fronds/blog/package.json +1 -1
  61. package/templates/workspace/AGENTS.md +14 -0
  62. package/templates/workspace/CLAUDE.md +25 -3
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AACH,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;AAC5C,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;AACnE,eAAe,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAqC,CAAC,CAAC;AAEzF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;AACxD,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;AACpC,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;AACpC,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AAE9C,+EAA+E;AAC/E,OAAO,CAAC,GAAG,CAAC,iBAAiB,KAAK,MAAM,CAAC;AAEzC,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC;IAC1B,IAAI,EAAE,OAAO;IACb,eAAe,EAAE,GAAG,EAAE,CAAC,SAAS;CACjC,CAAC,CAAC;AAEH,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAEpC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC"}
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,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,CAgDpD"}
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AACH,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,CA+CpD"}
package/dist/bridge.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { Lifecycle, Role } from '@fougere/schema';
1
2
  import { Anatomy, inputFields } from '@fougere/schema';
2
3
  function toKebab(name) {
3
4
  return name.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
@@ -9,16 +10,15 @@ export function entityToArgs(fields) {
9
10
  // Axes-derived ingress membership; the CLI additionally skips ALL relations
10
11
  // (a ref is not a flag — supplying related rows is not a CLI gesture).
11
12
  for (const [key, field] of Object.entries(inputFields(fields))) {
12
- if (field.role?.relation)
13
+ if (Role.of(field).relation)
13
14
  continue;
14
15
  // A `default(v)` travels as the create rule `{ value }` — citty shows it.
15
- const create = field.lifecycle?.create;
16
- const defaultValue = typeof create === 'object' && 'value' in create ? create.value : undefined;
16
+ const defaultValue = Lifecycle.of(field).literal?.value;
17
17
  const { base: shape, nullable } = Anatomy.of(field.shape);
18
18
  const kebab = toKebab(key);
19
19
  const def = {
20
20
  description: field.meta?.description,
21
- required: !nullable && field.lifecycle?.create === undefined,
21
+ required: !nullable && Lifecycle.of(field).requiredAtCreate,
22
22
  };
23
23
  switch (shape?.type) {
24
24
  case 'boolean':
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGvD,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,WAAW,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAC/D,IAAI,KAAK,CAAC,IAAI,EAAE,QAAQ;YAAE,SAAS;QAEnC,0EAA0E;QAC1E,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC;QACvC,MAAM,YAAY,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QAChG,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAE1D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,GAAG,GAAW;YAClB,WAAW,EAAE,KAAK,CAAC,IAAI,EAAE,WAAW;YACpC,QAAQ,EAAE,CAAC,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,MAAM,KAAK,SAAS;SAC7D,CAAC;QAEF,QAAQ,KAAK,EAAE,IAAI,EAAE,CAAC;YACpB,KAAK,SAAS;gBACX,GAA+B,CAAC,IAAI,GAAG,SAAS,CAAC;gBAClD,IAAI,YAAY,KAAK,SAAS;oBAAE,GAAG,CAAC,OAAO,GAAG,YAAuB,CAAC;gBACtE,MAAM;YACR,KAAK,QAAQ,CAAC;YACd,KAAK,SAAS,CAAC;YACf,KAAK,QAAQ;gBACX,oEAAoE;gBACpE,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;oBAC3D,GAA+B,CAAC,IAAI,GAAG,QAAQ,CAAC;gBACnD,CAAC;qBAAM,IAAI,eAAe,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;oBACpE,mDAAmD;oBAClD,GAA+B,CAAC,IAAI,GAAG,YAAY,CAAC;oBACrD,eAAe,EAAE,CAAC;gBACpB,CAAC;qBAAM,CAAC;oBACL,GAA+B,CAAC,IAAI,GAAG,QAAQ,CAAC;gBACnD,CAAC;gBACD,IAAI,YAAY,KAAK,SAAS;oBAAE,GAAG,CAAC,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;gBACnE,MAAM;YACR;gBACG,GAA+B,CAAC,IAAI,GAAG,QAAQ,CAAC;QACrD,CAAC;QAED,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;IAC1C,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAQlD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGvD,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,WAAW,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAC/D,IAAI,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,QAAQ;YAAE,SAAS;QAEtC,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,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAE1D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,GAAG,GAAW;YAClB,WAAW,EAAE,KAAK,CAAC,IAAI,EAAE,WAAW;YACpC,QAAQ,EAAE,CAAC,QAAQ,IAAI,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,gBAAgB;SAC5D,CAAC;QAEF,QAAQ,KAAK,EAAE,IAAI,EAAE,CAAC;YACpB,KAAK,SAAS;gBACX,GAA+B,CAAC,IAAI,GAAG,SAAS,CAAC;gBAClD,IAAI,YAAY,KAAK,SAAS;oBAAE,GAAG,CAAC,OAAO,GAAG,YAAuB,CAAC;gBACtE,MAAM;YACR,KAAK,QAAQ,CAAC;YACd,KAAK,SAAS,CAAC;YACf,KAAK,QAAQ;gBACX,oEAAoE;gBACpE,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;oBAC3D,GAA+B,CAAC,IAAI,GAAG,QAAQ,CAAC;gBACnD,CAAC;qBAAM,IAAI,eAAe,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;oBACpE,mDAAmD;oBAClD,GAA+B,CAAC,IAAI,GAAG,YAAY,CAAC;oBACrD,eAAe,EAAE,CAAC;gBACpB,CAAC;qBAAM,CAAC;oBACL,GAA+B,CAAC,IAAI,GAAG,QAAQ,CAAC;gBACnD,CAAC;gBACD,IAAI,YAAY,KAAK,SAAS;oBAAE,GAAG,CAAC,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;gBACnE,MAAM;YACR;gBACG,GAA+B,CAAC,IAAI,GAAG,QAAQ,CAAC;QACrD,CAAC;QAED,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;IAC1C,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AA2CzC,wBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFjD"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAuCzC,wBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAyFjD"}
package/dist/runner.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createAppRunner } from '@fougere/core';
2
- import { toRegistrationName } from '@fougere/core/contract';
2
+ import { registrationKeyOf } from '@fougere/core/contract';
3
3
  import { defineCommand, runMain } from 'citty';
4
4
  import { ui } from './ui.js';
5
5
  import { entityToArgs } from './bridge.js';
@@ -8,9 +8,6 @@ import { join } from 'node:path';
8
8
  function toKebab(name) {
9
9
  return name.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase()).replace(/^-/, '');
10
10
  }
11
- function capitalize(name) {
12
- return name.charAt(0).toUpperCase() + name.slice(1);
13
- }
14
11
  function toCamel(kebab) {
15
12
  return kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
16
13
  }
@@ -79,7 +76,10 @@ export async function run(app) {
79
76
  },
80
77
  args,
81
78
  run: async ({ args: parsed }) => {
82
- if (cmdName !== 'completion')
79
+ // JSON is a protocol: a branded intro before `{` makes it unparsable. The
80
+ // explain command owns its machine output and therefore gets a clean stdout.
81
+ const machineOutput = cmdName === 'explain' && parsed.json === true;
82
+ if (cmdName !== 'completion' && !machineOutput)
83
83
  terminal.intro();
84
84
  // citty adds `_` (raw positionals) and `--` (passthrough); strip them
85
85
  // so only the entity's own fields reach the handler.
@@ -93,7 +93,7 @@ export async function run(app) {
93
93
  }
94
94
  else {
95
95
  // Ride the call contract — the same envelope every consumer uses.
96
- await createAppRunner(app)({ entity: toRegistrationName(entity.name), op: 'execute' }, { params: {}, query: {}, body: input, state: {} });
96
+ await createAppRunner(app)({ entity: registrationKeyOf(entity.name), op: 'execute' }, { params: {}, query: {}, body: input, state: {} });
97
97
  }
98
98
  }
99
99
  catch (err) {
@@ -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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"}
@@ -0,0 +1,7 @@
1
+ import { entity, text, optional } from "@fougere/schema";
2
+
3
+ /** `fougere build` — write down what a scan found, so a deployment reads no disk. */
4
+ export default class Build extends entity({
5
+ root: optional(text({ description: "Project to read. Default: the current directory" })),
6
+ out: optional(text({ description: "Where to write it. Default: .fougere/scan.generated.ts" })),
7
+ }) {}
@@ -0,0 +1,8 @@
1
+ import { bool, entity, optional, text } from '@fougere/schema';
2
+
3
+ /** `fougere explain <Operation>` — print the operation contract Fougere will serve. */
4
+ export default class Explain extends entity({
5
+ operation: text({ min: 1, description: 'Operation to inspect (e.g. Post.publish)' }),
6
+ root: optional(text({ description: 'Project to read. Default: the current directory' })),
7
+ json: bool({ default: false, description: 'Print stable machine-readable JSON' }),
8
+ }) {}
@@ -0,0 +1,7 @@
1
+ import { entity, text, optional } from "@fougere/schema";
2
+
3
+ /** `fougere freeze <version>` — record today's shapes under a name, for good. */
4
+ export default class Freeze extends entity({
5
+ version: text({ min: 1, description: "Name to record these shapes under (e.g. v2)" }),
6
+ root: optional(text({ description: "Project to read. Default: the current directory" })),
7
+ }) {}
@@ -0,0 +1,7 @@
1
+ import { entity, bool, optional, text } from "@fougere/schema";
2
+
3
+ /** `fougere migrate` — realise the frozen steps the database has not caught up with. */
4
+ export default class Migrate extends entity({
5
+ apply: bool({ default: false, description: "Run it. Without this the plan is printed and nothing moves" }),
6
+ root: optional(text({ description: "Project to read. Default: the current directory" })),
7
+ }) {}
@@ -0,0 +1,60 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { dirname, relative, resolve } from 'node:path';
3
+ import { emitScan } from '@fougere/core/node';
4
+ import ProjectScan from '../services/ProjectScan.js';
5
+ import type Build from '../entities/Build.js';
6
+
7
+ /**
8
+ * Where the module lands when nobody says otherwise.
9
+ *
10
+ * `.fougere/` because the artefact is REGENERABLE — which is exactly the test `freeze`
11
+ * failed: a recorded version is a past nobody can recompute, so it lives in the tree.
12
+ * This is the same scan the next build performs again, so it is gitignored.
13
+ */
14
+ export const DEFAULT_OUT = '.fougere/scan.generated.ts';
15
+
16
+ export interface BuildReport {
17
+ /** Absolute, so a caller can print it or read it back. */
18
+ out: string;
19
+ /** Relative to the project root — what a human recognizes. */
20
+ path: string;
21
+ fronds: string[];
22
+ entities: number;
23
+ handlers: number;
24
+ /**
25
+ * What the scan could not settle, carried rather than swallowed: the module holds the
26
+ * same diagnostics, so a boot from it warns exactly as a boot from a disk would.
27
+ */
28
+ diagnostics: string[];
29
+ }
30
+
31
+ /**
32
+ * Writing the scan down — the half of the gradient a runtime without a disk needs.
33
+ *
34
+ * Producing the description reads the project; consuming it does not. `createApp` takes
35
+ * `scan:` and never looks for a filesystem, so this command is what stands between a
36
+ * project and a Worker: it runs where `node:fs` exists, once, at build time.
37
+ */
38
+ export default class BuildHandler {
39
+ constructor(private projectScan: ProjectScan) {}
40
+
41
+ /** Write down what a scan found, so a deployment reads no disk. */
42
+ async execute(input: Build): Promise<BuildReport> {
43
+ const scan = await this.projectScan.at(input.root ?? undefined);
44
+ const out = resolve(scan.root, input.out || DEFAULT_OUT);
45
+
46
+ // The emitter writes every import relative to where the module will SIT, so the
47
+ // directory has to be settled before the source is produced, not after.
48
+ await mkdir(dirname(out), { recursive: true });
49
+ await writeFile(out, emitScan(scan, { outFile: out }));
50
+
51
+ return {
52
+ out,
53
+ path: relative(scan.root, out),
54
+ fronds: scan.fronds.map((frond) => frond.name),
55
+ entities: scan.fronds.reduce((total, frond) => total + frond.entities.length, 0),
56
+ handlers: scan.fronds.reduce((total, frond) => total + frond.handlers.length, 0),
57
+ diagnostics: scan.diagnostics.map((d) => `${d.severity} ${d.code}: ${d.message}`),
58
+ };
59
+ }
60
+ }
@@ -1,4 +1,10 @@
1
- import { buildGraph, clusterEntities, crossFrondImports, resolveContracts, type ScanDiagnostic } from '@fougere/core';
1
+ import {
2
+ buildGraph,
3
+ clusterEntities,
4
+ resolveEffectiveOperations,
5
+ type ScanDiagnostic,
6
+ } from '@fougere/core';
7
+ import { crossFrondImports } from '@fougere/core/node';
2
8
  import ProjectScan from '../services/ProjectScan.js';
3
9
 
4
10
  /** One thing that does not hold, in the terms of whoever has to fix it. */
@@ -8,6 +14,12 @@ export interface Finding {
8
14
  code: string;
9
15
  /** Where to go and look. */
10
16
  filePath: string;
17
+ /**
18
+ * What the finding is ABOUT — `PostHandler.whoNull(user)` — when the rule holds it
19
+ * as a fact rather than inside its sentence. Two ops of one handler breaking the
20
+ * same rule read as one repeated line without it.
21
+ */
22
+ subject?: string;
11
23
  /** What is wrong, and what it costs. One sentence. */
12
24
  message: string;
13
25
  }
@@ -18,57 +30,38 @@ export interface CheckResult {
18
30
  findings: Finding[];
19
31
  }
20
32
 
33
+ /** Shared with `fougere graph` — one threshold, so the two never disagree. */
34
+ const DOMAIN_SPLIT_MIN_ENTITIES = 6;
35
+
21
36
  /**
22
37
  * What does not hold in this app — derived from its declarations, not from tests.
23
38
  *
24
39
  * It scans rather than boots — see `ProjectScan`, which states what that costs.
25
40
  *
26
- * Two rules today, and the shape for the rest:
41
+ * It raises no operation-resolution rule of its own: those findings come from the same
42
+ * EffectiveOperation resolver boot consumes. The remaining findings come from their
43
+ * existing owners — scan diagnostics, the import reader and domain clustering. A second
44
+ * opinion here would report what the runtime does not serve, precisely what a checker
45
+ * must avoid.
27
46
  *
28
- * - what the scan could not do (`ScanResult.diagnostics`) an unreadable
29
- * directory, a handler that would not parse, an `extends` it could not follow;
30
- * - an operation whose parameters have no binding plan — it is served, and it
31
- * receives nothing;
32
- * - a relative import that resolves into another frond — a colocation constraint
33
- * nothing declares, which holds until the day the other frond is not on this disk.
34
- *
35
- * A rule about an ABSENCE is only sound if the analysis attests it looked, which
36
- * is why the first bullet had to exist before this command could.
47
+ * A rule about an ABSENCE is only sound if the analysis attests it looked, which is
48
+ * why the scan's diagnostics had to exist before this command could.
37
49
  */
38
- /** Shared with `fougere graph` — one threshold, so the two never disagree. */
39
- const DOMAIN_SPLIT_MIN_ENTITIES = 6;
40
-
41
50
  export default class CheckHandler {
42
51
  constructor(private projectScan: ProjectScan) {}
43
52
 
44
53
  /** Report what does not hold in a Fougere app, without booting it. */
45
54
  async execute(input: { root?: string }): Promise<CheckResult> {
46
- const { fronds, diagnostics } = await this.projectScan.at(input.root);
47
- const findings: Finding[] = diagnostics.map(asFinding);
48
- let handlers = 0;
49
-
50
- for (const frond of fronds) {
51
- const collectorEntityNames = new Set(frond.collectors.map((c) => c.entityName));
52
- for (const handler of frond.handlers) {
53
- handlers++;
54
- // The same merge the façade performs — asked for, not redone. A second
55
- // opinion here would report a contract the runtime does not serve.
56
- const contracts = resolveContracts(handler, frond.operationsOverrides, collectorEntityNames);
57
-
58
- for (const [op, contract] of contracts) {
59
- const params = contract.signature?.params.length ?? 0;
60
- if (params > 0 && !contract.binding) {
61
- findings.push({
62
- severity: 'blocking',
63
- code: 'operation-unbound',
64
- filePath: handler.filePath,
65
- message: `${handler.ctor.name}.${op} declares ${params} parameter(s) and has no `
66
- + `binding plan — it is served, and it receives none of them.`,
67
- });
68
- }
69
- }
70
- }
71
- }
55
+ const { fronds, diagnostics, config } = await this.projectScan.at(input.root);
56
+ // This is the same pure resolution the boot consumes. No app lifecycle, database,
57
+ // migration, seed or adapter mount is needed for a global semantic check.
58
+ const model = resolveEffectiveOperations(fronds, {
59
+ diagnostics,
60
+ remotes: config.remotes,
61
+ adapters: config.adapters,
62
+ });
63
+ const findings: Finding[] = model.diagnostics.map(asFinding);
64
+ const handlers = fronds.reduce((count, frond) => count + frond.handlers.length, 0);
72
65
 
73
66
  /**
74
67
  * Entity groups with no `ref()` between them, in one frond. Reported as a FACT, not a
@@ -113,5 +106,11 @@ export default class CheckHandler {
113
106
 
114
107
  /** A scan diagnostic IS a finding — same vocabulary, so the renderer has one shape. */
115
108
  function asFinding(d: ScanDiagnostic): Finding {
116
- return { severity: d.severity, code: d.code, filePath: d.filePath, message: d.message };
109
+ return {
110
+ severity: d.severity,
111
+ code: d.code,
112
+ filePath: d.filePath,
113
+ subject: d.subject,
114
+ message: d.message,
115
+ };
117
116
  }
@@ -0,0 +1,214 @@
1
+ import {
2
+ resolveEffectiveOperations,
3
+ type EffectiveOperation,
4
+ type OperationContract,
5
+ } from '@fougere/core';
6
+ import { relative } from 'node:path';
7
+ import ProjectScan from '../services/ProjectScan.js';
8
+
9
+ type Cardinality = NonNullable<OperationContract['cardinality']>;
10
+ type Binding = EffectiveOperation['binding'][number];
11
+
12
+ export type ExplainedBinding =
13
+ | { kind: 'collector'; typeName: string }
14
+ | { kind: 'fact'; factName: string }
15
+ | { kind: 'param'; name: string; coerce?: 'number' | 'boolean' }
16
+ | { kind: 'body' }
17
+ | { kind: 'context' }
18
+ | { kind: 'query' };
19
+
20
+ export interface ExplainedParameter {
21
+ name: string;
22
+ type: string | null;
23
+ optional: boolean;
24
+ nullable: boolean;
25
+ undefinable: boolean;
26
+ binding: ExplainedBinding | null;
27
+ }
28
+
29
+ export interface ExplainedCollector {
30
+ typeName: string;
31
+ class: string;
32
+ file: string;
33
+ }
34
+
35
+ /** Stable, JSON-shaped projection of core's canonical EffectiveOperation. */
36
+ export interface ExplainResult {
37
+ operation: string;
38
+ handler: {
39
+ class: string;
40
+ address: string;
41
+ method: string;
42
+ file: string | null;
43
+ };
44
+ kind: 'query' | 'command';
45
+ description: string | null;
46
+ input: string | null;
47
+ output: { type: string; cardinality: Cardinality | null } | null;
48
+ parameters: ExplainedParameter[];
49
+ collectors: ExplainedCollector[];
50
+ contexts: string[];
51
+ semantics: EffectiveOperation['semantics'];
52
+ exposure: {
53
+ surfaces: string[];
54
+ adapters: string[];
55
+ };
56
+ placement: {
57
+ frond: string;
58
+ runtime: 'local' | 'remote';
59
+ remote: string | null;
60
+ };
61
+ }
62
+
63
+ interface Selector {
64
+ frond?: string;
65
+ surface?: string;
66
+ address: string;
67
+ op: string;
68
+ }
69
+
70
+ /** Read one operation directly from the model boot and check also consume. */
71
+ export default class ExplainHandler {
72
+ constructor(private projectScan: ProjectScan) {}
73
+
74
+ async execute(input: { operation?: string; root?: string; json?: boolean }): Promise<ExplainResult> {
75
+ const requested = input.operation?.trim();
76
+ if (!requested) throw new Error('Usage: fougere explain <Operation> [--json] [--root <directory>]');
77
+
78
+ const selector = parseSelector(requested);
79
+ const scan = await this.projectScan.at(input.root);
80
+ const model = resolveEffectiveOperations(scan.fronds, {
81
+ diagnostics: scan.diagnostics,
82
+ remotes: scan.config.remotes,
83
+ adapters: scan.config.adapters,
84
+ });
85
+ const candidates = model.operations.filter((operation) => matches(operation, selector));
86
+
87
+ if (candidates.length === 0) {
88
+ const failed = model.resolutionDiagnostics.find((diagnostic) =>
89
+ diagnostic.subject?.toLowerCase().includes(`.${selector.op.toLowerCase()}`)
90
+ && diagnostic.subject.toLowerCase().includes(selector.address.toLowerCase()));
91
+ if (failed) throw new Error(`[${failed.code}] ${failed.message}`);
92
+
93
+ const available = model.operations.map((operation) => operation.id).sort();
94
+ throw new Error(
95
+ `Unknown operation '${requested}'. `
96
+ + (available.length > 0
97
+ ? `Available operations: ${available.join(', ')}.`
98
+ : 'This project exposes no resolved operation.'),
99
+ );
100
+ }
101
+
102
+ if (candidates.length > 1) {
103
+ throw new Error(
104
+ `Ambiguous operation '${requested}'. Matches: `
105
+ + `${candidates.map((operation) => operation.id).sort().join(', ')}. `
106
+ + 'Use one of these qualified names.',
107
+ );
108
+ }
109
+
110
+ const selected = candidates[0]!;
111
+ const invalid = model.resolutionDiagnostics.find((diagnostic) =>
112
+ diagnostic.subject?.startsWith(`${selected.handler.className}.${selected.name}`));
113
+ if (invalid) throw new Error(`[${invalid.code}] ${invalid.message}`);
114
+
115
+ return project(selected, scan.root);
116
+ }
117
+ }
118
+
119
+ function project(operation: EffectiveOperation, root: string): ExplainResult {
120
+ return {
121
+ operation: operation.operation,
122
+ handler: {
123
+ class: operation.implementation.className,
124
+ address: operation.handler.address,
125
+ method: operation.implementation.method,
126
+ file: operation.implementation.filePath
127
+ ? relative(root, operation.implementation.filePath)
128
+ : null,
129
+ },
130
+ kind: operation.kind,
131
+ description: operation.description ?? null,
132
+ input: inputTypeOf(operation),
133
+ output: outputOf(operation),
134
+ parameters: operation.parameters.map((parameter) => ({
135
+ name: parameter.name,
136
+ type: parameter.type,
137
+ optional: parameter.optional,
138
+ nullable: parameter.nullable,
139
+ undefinable: parameter.undefinable,
140
+ binding: bindingOf(parameter.binding),
141
+ })),
142
+ collectors: operation.collectors.map((collector) => ({
143
+ typeName: collector.typeName,
144
+ class: collector.className,
145
+ file: relative(root, collector.filePath),
146
+ })),
147
+ contexts: operation.contexts,
148
+ semantics: operation.semantics,
149
+ exposure: operation.exposure,
150
+ placement: {
151
+ frond: operation.placement.frond,
152
+ runtime: operation.placement.runtime,
153
+ remote: operation.placement.remote ?? null,
154
+ },
155
+ };
156
+ }
157
+
158
+ function parseSelector(value: string): Selector {
159
+ const dot = value.lastIndexOf('.');
160
+ if (dot <= 0 || dot === value.length - 1) {
161
+ throw new Error(`Invalid operation '${value}'. Expected '<Handler>.<method>', for example 'Post.publish'.`);
162
+ }
163
+
164
+ const path = value.slice(0, dot).split('/').filter(Boolean);
165
+ if (path.length === 0 || path.length > 3) {
166
+ throw new Error(`Invalid operation '${value}'. Expected 'Post.publish', 'blog/Post.publish', or 'blog/public/Post.publish'.`);
167
+ }
168
+
169
+ const qualifiers = path.slice(0, -1);
170
+ return {
171
+ ...(qualifiers[0] ? { frond: qualifiers[0] } : {}),
172
+ ...(qualifiers[1] ? { surface: qualifiers[1] } : {}),
173
+ address: addressOf(path.at(-1)!),
174
+ op: value.slice(dot + 1),
175
+ };
176
+ }
177
+
178
+ function matches(operation: EffectiveOperation, selector: Selector): boolean {
179
+ return operation.handler.address.toLowerCase() === selector.address.toLowerCase()
180
+ && operation.name === selector.op
181
+ && (!selector.frond || operation.placement.frond === selector.frond)
182
+ && (!selector.surface || operation.exposure.surfaces.includes(selector.surface));
183
+ }
184
+
185
+ function addressOf(value: string): string {
186
+ const base = value.endsWith('Handler') ? value.slice(0, -'Handler'.length) : value;
187
+ return base ? base[0]!.toLowerCase() + base.slice(1) : base;
188
+ }
189
+
190
+ function inputTypeOf(operation: EffectiveOperation): string | null {
191
+ const body = operation.parameters.find((parameter) => parameter.binding.source.kind === 'body');
192
+ return body?.type ?? schemaName(operation.input) ?? null;
193
+ }
194
+
195
+ function outputOf(operation: EffectiveOperation): ExplainResult['output'] {
196
+ const type = schemaName(operation.output) ?? parsedOutput(operation.signature?.returnType?.raw);
197
+ return type ? { type, cardinality: operation.cardinality ?? null } : null;
198
+ }
199
+
200
+ function schemaName(schema: unknown): string | undefined {
201
+ const view = schema as { name?: string; source?: { name?: string } } | undefined;
202
+ if (!view) return undefined;
203
+ if (view.name && view.name !== 'Schema') return view.name;
204
+ return view.source?.name;
205
+ }
206
+
207
+ function parsedOutput(raw: string | undefined): string | undefined {
208
+ if (!raw) return undefined;
209
+ return raw.startsWith('Promise<') && raw.endsWith('>') ? raw.slice(8, -1) : raw;
210
+ }
211
+
212
+ function bindingOf(binding: Binding): ExplainedBinding {
213
+ return { ...binding.source } as ExplainedBinding;
214
+ }