@fougere/cli 0.3.0-alpha.0 → 0.5.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 (61) hide show
  1. package/README.md +1 -1
  2. package/app/commands/BuildCommand.ts +3 -0
  3. package/app/commands/CallCommand.ts +3 -3
  4. package/app/commands/CheckCommand.ts +11 -8
  5. package/app/commands/DevtoolsCommand.ts +92 -0
  6. package/app/commands/ExplainCommand.ts +51 -8
  7. package/app/commands/FreezeCommand.ts +6 -1
  8. package/app/commands/GraphCommand.ts +3 -0
  9. package/app/commands/KeysCommand.ts +1 -1
  10. package/app/commands/MigrateCommand.ts +3 -0
  11. package/app/commands/ServeCommand.ts +7 -16
  12. package/dist/bin.js +4 -5
  13. package/dist/bin.js.map +1 -1
  14. package/dist/bridge.d.ts.map +1 -1
  15. package/dist/bridge.js +11 -4
  16. package/dist/bridge.js.map +1 -1
  17. package/dist/completion.d.ts +4 -1
  18. package/dist/completion.d.ts.map +1 -1
  19. package/dist/completion.js +54 -20
  20. package/dist/completion.js.map +1 -1
  21. package/dist/loader.d.ts +11 -0
  22. package/dist/loader.d.ts.map +1 -0
  23. package/dist/loader.js +23 -0
  24. package/dist/loader.js.map +1 -0
  25. package/dist/machine.d.ts +16 -0
  26. package/dist/machine.d.ts.map +1 -0
  27. package/dist/machine.js +22 -0
  28. package/dist/machine.js.map +1 -0
  29. package/dist/runner.d.ts.map +1 -1
  30. package/dist/runner.js +16 -7
  31. package/dist/runner.js.map +1 -1
  32. package/fronds/analysis/entities/Build.ts +2 -1
  33. package/fronds/analysis/entities/Check.ts +2 -1
  34. package/fronds/analysis/entities/Devtools.ts +8 -0
  35. package/fronds/analysis/entities/Explain.ts +2 -1
  36. package/fronds/analysis/entities/Freeze.ts +2 -1
  37. package/fronds/analysis/entities/Graph.ts +2 -1
  38. package/fronds/analysis/entities/Migrate.ts +1 -0
  39. package/fronds/analysis/handlers/BuildHandler.ts +2 -1
  40. package/fronds/analysis/handlers/DevtoolsHandler.ts +86 -0
  41. package/fronds/analysis/handlers/ExplainHandler.ts +52 -12
  42. package/fronds/analysis/handlers/FreezeHandler.ts +18 -13
  43. package/fronds/analysis/handlers/MigrateHandler.ts +4 -3
  44. package/fronds/scaffold/entities/BuildFrond.ts +1 -1
  45. package/fronds/scaffold/entities/Call.ts +1 -1
  46. package/fronds/scaffold/entities/Sync.ts +1 -1
  47. package/fronds/scaffold/handlers/BuildFrondHandler.ts +5 -5
  48. package/fronds/scaffold/handlers/SyncHandler.ts +17 -17
  49. package/package.json +9 -8
  50. package/src/bin.ts +83 -0
  51. package/src/bridge.ts +70 -0
  52. package/src/completion.ts +152 -0
  53. package/src/index.ts +3 -0
  54. package/src/loader.ts +28 -0
  55. package/src/machine.ts +23 -0
  56. package/src/runner.ts +146 -0
  57. package/src/theme.ts +19 -0
  58. package/src/ui.ts +131 -0
  59. package/templates/blog/fronds/blog/handlers/PostHandler.ts +1 -1
  60. package/templates/frond/fronds/__name__/handlers/PostHandler.ts +1 -1
  61. package/templates/fronds/blog/handlers/PostHandler.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"completion.js","sourceRoot":"","sources":["../src/completion.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,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,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AAChF,CAAC;AAOD,SAAS,kBAAkB,CAAC,GAAQ;IAClC,MAAM,QAAQ,GAAkB,EAAE,CAAC;IAEnC,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAEtE,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACpC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,CAAC,OAAO;gBAAE,SAAS;YAEvB,IAAI,MAAgC,CAAC;YACrC,IAAI,CAAC;gBAAC,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,SAAS;YAAC,CAAC;YAC/D,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,UAAU;gBAAE,SAAS;YAEnD,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;YAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;iBAC3C,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAErC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAQ,EAAE,OAAO,GAAG,SAAS;IACjE,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEvD,MAAM,SAAS,GAAG,QAAQ;SACvB,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACX,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClC,OAAO,SAAS,GAAG,CAAC,IAAI,YAAY,KAAK,MAAM,CAAC;IAClD,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;;GAEN,OAAO;;cAEI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;;;;;;;;;EASxD,SAAS;;;;;;;;;WASA,OAAO,IAAI,OAAO;CAC5B,CAAC;AACF,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,GAAQ,EAAE,OAAO,GAAG,SAAS;IAClE,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEvD,MAAM,SAAS,GAAG,QAAQ;SACvB,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACX,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClC,OAAO,SAAS,GAAG,CAAC,IAAI,+BAA+B,KAAK,sBAAsB,CAAC;IACrF,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;GACN,OAAO;;;;;;;gCAOsB,QAAQ;;;;;EAKtC,SAAS;;;;eAII,OAAO,gBAAgB,OAAO;CAC5C,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"completion.js","sourceRoot":"","sources":["../src/completion.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Conventions } from '@fougere/core/node';
2
+ /**
3
+ * The loader every command needs: `alias` is what makes `@fronds/user/entities/User.js`
4
+ * resolve when one frond names its neighbour, in any command that loads user code.
5
+ *
6
+ * Two jitis, because the config is read BEFORE the aliases — it names the scope they are
7
+ * built from. `reread` drops the module cache: every loader caches by specifier, so a
8
+ * second boot in one process would be handed what the first one read.
9
+ */
10
+ export declare function installLoader(root: string, reread?: boolean): Promise<Conventions>;
11
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,UAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,CActF"}
package/dist/loader.js ADDED
@@ -0,0 +1,23 @@
1
+ import { setModuleLoader, loadConfig, resolveConventions, frondAliases, } from '@fougere/core/node';
2
+ /**
3
+ * The loader every command needs: `alias` is what makes `@fronds/user/entities/User.js`
4
+ * resolve when one frond names its neighbour, in any command that loads user code.
5
+ *
6
+ * Two jitis, because the config is read BEFORE the aliases — it names the scope they are
7
+ * built from. `reread` drops the module cache: every loader caches by specifier, so a
8
+ * second boot in one process would be handed what the first one read.
9
+ */
10
+ export async function installLoader(root, reread = false) {
11
+ const { createJiti } = await import('jiti');
12
+ const bare = createJiti(import.meta.url, { interopDefault: true });
13
+ setModuleLoader((filePath) => bare.import(filePath));
14
+ const conventions = resolveConventions((await loadConfig(root)).conventions);
15
+ const jiti = createJiti(import.meta.url, {
16
+ interopDefault: true,
17
+ alias: await frondAliases(root, conventions),
18
+ ...(reread ? { moduleCache: false } : {}),
19
+ });
20
+ setModuleLoader((filePath) => jiti.import(filePath));
21
+ return conventions;
22
+ }
23
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAAE,UAAU,EAAE,kBAAkB,EAAE,YAAY,GAC9D,MAAM,oBAAoB,CAAC;AAG5B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY,EAAE,MAAM,GAAG,KAAK;IAC9D,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;IACnE,eAAe,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAqC,CAAC,CAAC;IAEzF,MAAM,WAAW,GAAG,kBAAkB,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAC7E,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE;QACvC,cAAc,EAAE,IAAI;QACpB,KAAK,EAAE,MAAM,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC;QAC5C,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1C,CAAC,CAAC;IACH,eAAe,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAqC,CAAC,CAAC;IAEzF,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A command's machine output — the one shape a pipe reads.
3
+ *
4
+ * There is no list of commands here: `json` is declared by an entity like any other
5
+ * field, so a command that declares it gets the door. The runner used to name `explain`
6
+ * in an `if`, and `graph --json` announced a flag it then ignored.
7
+ */
8
+ export declare function machineWanted(raw: Record<string, unknown>): boolean;
9
+ /**
10
+ * A `Map` serializes to `{}`, so the door converts it rather than each command flattening
11
+ * its own result: `GraphResult.nodes` is a Map, and `graph --json` would have printed a
12
+ * report with an empty graph in it.
13
+ */
14
+ export declare function machineText(value: unknown): string;
15
+ export declare function printMachine(value: unknown): void;
16
+ //# sourceMappingURL=machine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../src/machine.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAElD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAEjD"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A command's machine output — the one shape a pipe reads.
3
+ *
4
+ * There is no list of commands here: `json` is declared by an entity like any other
5
+ * field, so a command that declares it gets the door. The runner used to name `explain`
6
+ * in an `if`, and `graph --json` announced a flag it then ignored.
7
+ */
8
+ export function machineWanted(raw) {
9
+ return raw.json === true || typeof raw.names === 'string';
10
+ }
11
+ /**
12
+ * A `Map` serializes to `{}`, so the door converts it rather than each command flattening
13
+ * its own result: `GraphResult.nodes` is a Map, and `graph --json` would have printed a
14
+ * report with an empty graph in it.
15
+ */
16
+ export function machineText(value) {
17
+ return JSON.stringify(value, (_key, held) => (held instanceof Map ? Object.fromEntries(held) : held), 2);
18
+ }
19
+ export function printMachine(value) {
20
+ process.stdout.write(machineText(value) + '\n');
21
+ }
22
+ //# sourceMappingURL=machine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine.js","sourceRoot":"","sources":["../src/machine.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,GAA4B;IACxD,OAAO,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,CAAC;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3G,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AAClD,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;AAuCzC,wBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAyFjD"}
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;AAwCzC,wBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAgGjD"}
package/dist/runner.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { createAppRunner } from '@fougere/core';
2
- import { registrationKeyOf } from '@fougere/core/contract';
2
+ import { lowerFirst } from '@fougere/core/contract';
3
3
  import { defineCommand, runMain } from 'citty';
4
4
  import { ui } from './ui.js';
5
+ import { machineWanted } from './machine.js';
5
6
  import { entityToArgs } from './bridge.js';
6
7
  import { readdir } from 'node:fs/promises';
7
8
  import { join } from 'node:path';
@@ -31,7 +32,6 @@ async function loadAppCommands(cliRoot, loader) {
31
32
  export async function run(app) {
32
33
  const terminal = ui();
33
34
  const cliRoot = new URL('..', import.meta.url).pathname;
34
- // Load app commands (presentation layer)
35
35
  const { createJiti } = await import('jiti');
36
36
  const jiti = createJiti(import.meta.url, { interopDefault: true });
37
37
  const loader = (path) => jiti.import(path);
@@ -76,9 +76,12 @@ export async function run(app) {
76
76
  },
77
77
  args,
78
78
  run: async ({ args: parsed }) => {
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;
79
+ // JSON is a protocol: a branded intro before `{` makes it unparsable. Read from
80
+ // the invocation's own args, so any command declaring `json` gets a clean stdout
81
+ // naming `explain` here is what let `graph --json` print its decorated box.
82
+ const machineOutput = machineWanted(parsed);
83
+ // `completion` is exempt by nature, not by flag: its output IS a shell script,
84
+ // there is no invocation of it that wants decoration.
82
85
  if (cmdName !== 'completion' && !machineOutput)
83
86
  terminal.intro();
84
87
  // citty adds `_` (raw positionals) and `--` (passthrough); strip them
@@ -93,11 +96,17 @@ export async function run(app) {
93
96
  }
94
97
  else {
95
98
  // Ride the call contract — the same envelope every consumer uses.
96
- await createAppRunner(app)({ entity: registrationKeyOf(entity.name), op: 'execute' }, { params: {}, query: {}, body: input, state: {} });
99
+ await createAppRunner(app)({ entity: lowerFirst(entity.name), op: 'execute' }, { params: {}, query: {}, body: input, state: {} });
97
100
  }
98
101
  }
99
102
  catch (err) {
100
- terminal.error(err instanceof Error ? err.message : String(err));
103
+ const said = err instanceof Error ? err.message : String(err);
104
+ // A machine reader parses stdout: a refusal printed there is a refusal that
105
+ // breaks the parse instead of being read. stderr is where it belongs.
106
+ if (machineOutput)
107
+ process.stderr.write(said + '\n');
108
+ else
109
+ terminal.error(said);
101
110
  process.exit(1);
102
111
  }
103
112
  },
@@ -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,7 +1,8 @@
1
- import { entity, text, optional } from "@fougere/schema";
1
+ import { entity, bool, text, optional } from "@fougere/schema";
2
2
 
3
3
  /** `fougere build` — write down what a scan found, so a deployment reads no disk. */
4
4
  export default class Build extends entity({
5
5
  root: optional(text({ description: "Project to read. Default: the current directory" })),
6
6
  out: optional(text({ description: "Where to write it. Default: .fougere/scan.generated.ts" })),
7
+ json: bool({ default: false, description: "Print the report as JSON, with nothing else on stdout" }),
7
8
  }) {}
@@ -1,5 +1,6 @@
1
- import { entity, text } from '@fougere/schema';
1
+ import { entity, bool, text } from '@fougere/schema';
2
2
 
3
3
  export default class Check extends entity({
4
4
  root: text({ description: 'Project root directory (default: cwd)' }),
5
+ json: bool({ default: false, description: 'Print the report as JSON, with nothing else on stdout' }),
5
6
  }) {}
@@ -0,0 +1,8 @@
1
+ import { entity, bool, text, optional } from '@fougere/schema';
2
+
3
+ /** `fougere devtools` — what the running apps of this project are dispatching. */
4
+ export default class Devtools extends entity({
5
+ url: optional(text({ description: 'One app to read. Absent: every address this project declares' })),
6
+ root: optional(text({ description: 'Project to read the addresses from. Default: the current directory' })),
7
+ json: bool({ default: false, description: 'Print one page as JSON and stop, instead of following' }),
8
+ }) {}
@@ -1,8 +1,9 @@
1
- import { bool, entity, optional, text } from '@fougere/schema';
1
+ import { bool, entity, oneOf, optional, text } from '@fougere/schema';
2
2
 
3
3
  /** `fougere explain <Operation>` — print the operation contract Fougere will serve. */
4
4
  export default class Explain extends entity({
5
5
  operation: text({ min: 1, description: 'Operation to inspect (e.g. Post.publish)' }),
6
6
  root: optional(text({ description: 'Project to read. Default: the current directory' })),
7
7
  json: bool({ default: false, description: 'Print stable machine-readable JSON' }),
8
+ names: optional(oneOf('operations', 'fronds', { description: 'Print one name per line — what shell completion reads' })),
8
9
  }) {}
@@ -1,7 +1,8 @@
1
- import { entity, text, optional } from "@fougere/schema";
1
+ import { entity, bool, text, optional } from "@fougere/schema";
2
2
 
3
3
  /** `fougere freeze <version>` — record today's shapes under a name, for good. */
4
4
  export default class Freeze extends entity({
5
5
  version: text({ min: 1, description: "Name to record these shapes under (e.g. v2)" }),
6
6
  root: optional(text({ description: "Project to read. Default: the current directory" })),
7
+ json: bool({ default: false, description: "Print the report as JSON, with nothing else on stdout" }),
7
8
  }) {}
@@ -1,6 +1,7 @@
1
- import { entity, text, number } from '@fougere/schema';
1
+ import { entity, bool, text, number } from '@fougere/schema';
2
2
 
3
3
  export default class Graph extends entity({
4
4
  root: text({ description: 'Project root directory (default: cwd)' }),
5
5
  minEntities: number({ default: 6, description: 'Minimum entities before suggesting split' }),
6
+ json: bool({ default: false, description: 'Print the report as JSON, with nothing else on stdout' }),
6
7
  }) {}
@@ -4,4 +4,5 @@ import { entity, bool, optional, text } from "@fougere/schema";
4
4
  export default class Migrate extends entity({
5
5
  apply: bool({ default: false, description: "Run it. Without this the plan is printed and nothing moves" }),
6
6
  root: optional(text({ description: "Project to read. Default: the current directory" })),
7
+ json: bool({ default: false, description: "Print the report as JSON, with nothing else on stdout" }),
7
8
  }) {}
@@ -39,7 +39,8 @@ export default class BuildHandler {
39
39
  constructor(private projectScan: ProjectScan) {}
40
40
 
41
41
  /** Write down what a scan found, so a deployment reads no disk. */
42
- async execute(input: Build): Promise<BuildReport> {
42
+ /** `json` is the presentation's, so it is not part of what this operation receives. */
43
+ async execute(input: Omit<Build, 'json'>): Promise<BuildReport> {
43
44
  const scan = await this.projectScan.at(input.root ?? undefined);
44
45
  const out = resolve(scan.root, input.out || DEFAULT_OUT);
45
46
 
@@ -0,0 +1,86 @@
1
+ import { createHttpTransport } from '@fougere/transport-http/client';
2
+ import type { CallPage, CallRecord } from '@fougere/core';
3
+ import ProjectScan from '../services/ProjectScan.js';
4
+
5
+ /** The default address of an app in development — Nuxt, Next and the site all sit there. */
6
+ const LOCAL = 'http://127.0.0.1:3000';
7
+
8
+ /** One address that was asked, and what came back from it. */
9
+ export interface CallSource {
10
+ url: string;
11
+ /** The frond `remotes:` names at this address, when that is where it came from. */
12
+ frond?: string;
13
+ cursor: number;
14
+ inFlight: number;
15
+ dropped: number;
16
+ /** Present instead of the numbers when the address did not answer, or serves no log. */
17
+ refusal?: string;
18
+ }
19
+
20
+ /**
21
+ * Every app of this project, read at once.
22
+ *
23
+ * A reader pulls: no app registers anywhere, and an app started alone depends on nothing.
24
+ * The price is knowing the addresses, which the project already states — `remotes:` says
25
+ * where a call goes, so it also says where the other half of a call can be watched.
26
+ */
27
+ export interface CallsView {
28
+ sources: CallSource[];
29
+ /** Every call, oldest first, each carrying the address it was read from. */
30
+ calls: (CallRecord & { source: string })[];
31
+ }
32
+
33
+ export default class DevtoolsHandler {
34
+ constructor(private projectScan: ProjectScan) {}
35
+
36
+ /** Read what the running apps have dispatched since a cursor per address. */
37
+ async execute(input: { url?: string; root?: string; since?: Record<string, number> }): Promise<CallsView> {
38
+ const asked = input.url ? [{ url: trimmed(input.url) }] : await this.addresses(input.root);
39
+ const since = input.since ?? {};
40
+
41
+ const read = await Promise.all(asked.map(async (one): Promise<CallSource & { calls: CallRecord[] }> => {
42
+ try {
43
+ const page = await createHttpTransport(one.url)(
44
+ { entity: 'rpc', op: 'calls' },
45
+ { params: {}, query: {}, body: { since: since[one.url] ?? 0 }, state: {} },
46
+ ) as CallPage;
47
+
48
+ return { ...one, cursor: page.cursor, inFlight: page.inFlight, dropped: page.dropped, calls: page.calls };
49
+ } catch (err) {
50
+ // An address that does not answer is a source with a reason, not a missing source:
51
+ // an app not started and an app without the package are different facts, and both
52
+ // are worth seeing beside the ones that answered.
53
+ return { ...one, cursor: since[one.url] ?? 0, inFlight: 0, dropped: 0, calls: [], refusal: said(err) };
54
+ }
55
+ }));
56
+
57
+ return {
58
+ sources: read.map(({ calls: _calls, ...source }) => source),
59
+ calls: read
60
+ .flatMap(({ url, calls }) => calls.map((call) => ({ ...call, source: url })))
61
+ .sort((a, b) => a.startedAt - b.startedAt || a.seq - b.seq),
62
+ };
63
+ }
64
+
65
+ /**
66
+ * The addresses this project declares, plus the local one.
67
+ *
68
+ * `remotes:` is the operator's statement of where a call goes; reading it here is not a
69
+ * second source of truth, it is the same one read for the other direction.
70
+ */
71
+ private async addresses(root?: string): Promise<{ url: string; frond?: string }[]> {
72
+ const { config } = await this.projectScan.at(root);
73
+ const remotes = Object.entries(config.remotes ?? {})
74
+ .map(([frond, url]) => ({ url: trimmed(url), frond }));
75
+
76
+ return [{ url: LOCAL }, ...remotes.filter((one) => one.url !== LOCAL)];
77
+ }
78
+ }
79
+
80
+ function trimmed(url: string): string {
81
+ return url.replace(/\/$/, '');
82
+ }
83
+
84
+ function said(err: unknown): string {
85
+ return err instanceof Error ? err.message : String(err);
86
+ }
@@ -5,6 +5,7 @@ import {
5
5
  } from '@fougere/core';
6
6
  import { relative } from 'node:path';
7
7
  import ProjectScan from '../services/ProjectScan.js';
8
+ import { ANONYMOUS_SCHEMA_NAME, lowerFirst, type SchemaView } from '@fougere/schema';
8
9
 
9
10
  type Cardinality = NonNullable<OperationContract['cardinality']>;
10
11
  type Binding = EffectiveOperation['binding'][number];
@@ -60,6 +61,17 @@ export interface ExplainResult {
60
61
  };
61
62
  }
62
63
 
64
+ /** What this project serves, when no single operation was named. */
65
+ export interface ExplainListing {
66
+ fronds: {
67
+ name: string;
68
+ runtime: 'local' | 'remote';
69
+ remote: string | null;
70
+ operations: number;
71
+ }[];
72
+ operations: string[];
73
+ }
74
+
63
75
  interface Selector {
64
76
  frond?: string;
65
77
  surface?: string;
@@ -71,17 +83,46 @@ interface Selector {
71
83
  export default class ExplainHandler {
72
84
  constructor(private projectScan: ProjectScan) {}
73
85
 
86
+ /** The names this project serves — the list `explain` used to spell in a refusal only. */
87
+ async list(input: { root?: string }): Promise<ExplainListing> {
88
+ const { scan, model } = await this.modelOf(input.root);
89
+ const remotes = scan.config.remotes ?? {};
90
+ const counted = new Map<string, number>();
91
+ for (const operation of model.operations) {
92
+ const frond = operation.placement.frond;
93
+ counted.set(frond, (counted.get(frond) ?? 0) + 1);
94
+ }
95
+
96
+ return {
97
+ fronds: scan.fronds.map((frond) => ({
98
+ name: frond.name,
99
+ runtime: remotes[frond.name] ? 'remote' as const : 'local' as const,
100
+ remote: remotes[frond.name] ?? null,
101
+ operations: counted.get(frond.name) ?? 0,
102
+ })).sort((a, b) => a.name.localeCompare(b.name)),
103
+ operations: model.operations.map((operation) => operation.id).sort(),
104
+ };
105
+ }
106
+
107
+ private async modelOf(root?: string) {
108
+ const scan = await this.projectScan.at(root);
109
+ return {
110
+ scan,
111
+ model: resolveEffectiveOperations(scan.fronds, {
112
+ diagnostics: scan.diagnostics,
113
+ remotes: scan.config.remotes,
114
+ adapters: scan.config.adapters,
115
+ }),
116
+ };
117
+ }
118
+
119
+ /** Print the contract one operation will be served under. */
74
120
  async execute(input: { operation?: string; root?: string; json?: boolean }): Promise<ExplainResult> {
75
121
  const requested = input.operation?.trim();
76
122
  if (!requested) throw new Error('Usage: fougere explain <Operation> [--json] [--root <directory>]');
77
123
 
78
124
  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
- });
125
+ const { scan, model } = await this.modelOf(input.root);
85
126
  const candidates = model.operations.filter((operation) => matches(operation, selector));
86
127
 
87
128
  if (candidates.length === 0) {
@@ -184,7 +225,7 @@ function matches(operation: EffectiveOperation, selector: Selector): boolean {
184
225
 
185
226
  function addressOf(value: string): string {
186
227
  const base = value.endsWith('Handler') ? value.slice(0, -'Handler'.length) : value;
187
- return base ? base[0]!.toLowerCase() + base.slice(1) : base;
228
+ return lowerFirst(base);
188
229
  }
189
230
 
190
231
  function inputTypeOf(operation: EffectiveOperation): string | null {
@@ -197,11 +238,10 @@ function outputOf(operation: EffectiveOperation): ExplainResult['output'] {
197
238
  return type ? { type, cardinality: operation.cardinality ?? null } : null;
198
239
  }
199
240
 
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;
241
+ function schemaName(schema: SchemaView | undefined): string | undefined {
242
+ if (!schema) return undefined;
243
+ if (schema.name && schema.name !== ANONYMOUS_SCHEMA_NAME) return schema.name;
244
+ return schema.derivation?.sourceName;
205
245
  }
206
246
 
207
247
  function parsedOutput(raw: string | undefined): string | undefined {
@@ -1,6 +1,6 @@
1
1
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
- import { describeSet, diffSet, registrationKeyOf, type SchemaBundle, type SetDiff } from '@fougere/schema';
3
+ import { Bundle, lowerFirst, type SchemaBundle, type SchemaView, type SetDiff } from '@fougere/schema';
4
4
  import ProjectScan from '../services/ProjectScan.js';
5
5
  import { VERSIONS, chainOf } from '../versions.js';
6
6
  import type Freeze from '../entities/Freeze.js';
@@ -13,7 +13,7 @@ export interface FreezeInspection {
13
13
  /** What the step contains — absent when there is nothing before to step from. */
14
14
  step?: SetDiff;
15
15
  /** Per entity, the pairs the calculation refuses to decide. Empty means it was written. */
16
- ambiguous: Record<string, Array<{ removed: string; added: string }>>;
16
+ ambiguous: Record<string, { removed: string; added: string }[]>;
17
17
  /** Whether anything reached the disk — false while a question stands. */
18
18
  written: boolean;
19
19
  }
@@ -40,7 +40,8 @@ export default class FreezeHandler {
40
40
  * ambiguities and calls again with `renamed`. Splitting it would let a caller write
41
41
  * a version it never inspected.
42
42
  */
43
- async execute(input: Freeze & { renamed?: Record<string, Record<string, string>> }): Promise<FreezeInspection> {
43
+ /** `json` is the presentation's, so it is not part of what this operation receives. */
44
+ async execute(input: Omit<Freeze, 'json'> & { renamed?: Record<string, Record<string, string>> }): Promise<FreezeInspection> {
44
45
  const fronds = await this.read(input);
45
46
  const version = input.version;
46
47
  const entities = fronds.flatMap(({ bundle }) => Object.keys(bundle.$defs ?? {}));
@@ -54,7 +55,9 @@ export default class FreezeHandler {
54
55
  path,
55
56
  bundle,
56
57
  previous,
57
- step: previous ? diffSet(previous.bundle, bundle, { renamed }) : undefined,
58
+ step: previous
59
+ ? Bundle.fromDescriptor(previous.bundle).diff(Bundle.fromDescriptor(bundle), { renamed })
60
+ : undefined,
58
61
  }));
59
62
 
60
63
  const ambiguous: FreezeInspection['ambiguous'] = {};
@@ -95,14 +98,16 @@ export default class FreezeHandler {
95
98
  * `Fronds.schemas()` is deliberately flat — a fact heard in one frond is declared in
96
99
  * another — so the per-frond map is built here, where the question IS per frond.
97
100
  */
98
- private async read(input: Freeze) {
101
+ private async read(input: Omit<Freeze, 'json'>) {
99
102
  const scan = await this.projectScan.at(input.root ?? undefined);
100
103
  return Promise.all(
101
104
  scan.fronds
102
105
  .filter((frond) => frond.entities.length > 0)
103
106
  .map(async (frond) => ({
104
107
  path: frond.source.path,
105
- bundle: describeSet(Object.fromEntries(frond.entities.map((e) => [e.name, e.entityClass]))),
108
+ bundle: Bundle.fromSchemas(
109
+ Object.fromEntries(frond.entities.map((e) => [e.name, e.entityClass])),
110
+ ).descriptor,
106
111
  declared: declaredRenames(frond.entities),
107
112
  previous: await previousOf(frond.source.path, input.version),
108
113
  })),
@@ -114,17 +119,17 @@ type Inspected = { previous?: { name: string }; step?: SetDiff };
114
119
 
115
120
  /**
116
121
  * What the entities state about themselves — `previous` says what a field WAS, while
117
- * `diff` reads old new, so the pair is turned around here and nowhere else.
122
+ * `Bundle.diff` reads old to new, so the pair is turned around here and nowhere else.
118
123
  */
119
124
  function declaredRenames(
120
- entities: ReadonlyArray<{ name: string; entityClass: unknown }>,
125
+ entities: readonly { name: string; entityClass: unknown }[],
121
126
  ): Record<string, Record<string, string>> {
122
127
  const out: Record<string, Record<string, string>> = {};
123
128
  for (const { name, entityClass } of entities) {
124
- const previous = (entityClass as { previous?: Record<string, string> }).previous;
125
- // Keyed as `describeSet` keys `$defs`, which is what `diffSet` reads. Spelling the
129
+ const previous = (entityClass as SchemaView).previous;
130
+ // Keyed as `Bundle.fromSchemas` keys `$defs`, which is what `Bundle.diff` reads. Spelling the
126
131
  // convention a second way here is the defect this repo has already recorded twice.
127
- const key = registrationKeyOf(name);
132
+ const key = lowerFirst(name);
128
133
  if (previous) out[key] = Object.fromEntries(Object.entries(previous).map(([now, was]) => [was, now]));
129
134
  }
130
135
  return out;
@@ -132,12 +137,12 @@ function declaredRenames(
132
137
 
133
138
  /** Every source of an answer, folded per entity — later sources win field by field. */
134
139
  function settled(
135
- sources: ReadonlyArray<Record<string, Record<string, string>>>,
140
+ sources: readonly Record<string, Record<string, string>>[],
136
141
  answers: Record<string, Record<string, string>>,
137
142
  ): Record<string, Record<string, string>> {
138
143
  const out: Record<string, Record<string, string>> = {};
139
144
  for (const source of [...sources, answers]) {
140
- for (const [entity, pairs] of Object.entries(source)) out[entity] = { ...(out[entity] ?? {}), ...pairs };
145
+ for (const [entity, pairs] of Object.entries(source)) out[entity] = { ...out[entity], ...pairs };
141
146
  }
142
147
  return out;
143
148
  }
@@ -31,7 +31,8 @@ export default class MigrateHandler {
31
31
  constructor(private projectScan: ProjectScan) {}
32
32
 
33
33
  /** Realise the frozen steps this database has not caught up with. */
34
- async execute(input: Migrate): Promise<MigrationPlan> {
34
+ /** `json` is the presentation's, so it is not part of what this operation receives. */
35
+ async execute(input: Omit<Migrate, 'json'>): Promise<MigrationPlan> {
35
36
  const scan = await this.projectScan.at(input.root ?? undefined);
36
37
  const perFrond = await Promise.all(scan.fronds.map((frond) => stepsOf(frond.source.path)));
37
38
  const steps = perFrond.flat();
@@ -77,7 +78,7 @@ export default class MigrateHandler {
77
78
  }
78
79
 
79
80
  /** The versions read, oldest first and each named once however many fronds cut it. */
80
- function versionsOf(steps: ReadonlyArray<{ version: string }>): string[] {
81
+ function versionsOf(steps: readonly { version: string }[]): string[] {
81
82
  return [...new Set(steps.map(({ version }) => version))].sort((a, b) => a.localeCompare(b, 'en', { numeric: true }));
82
83
  }
83
84
 
@@ -90,7 +91,7 @@ function onSource(step: SetDiff, source: string, sourceOf: (entity: string) => s
90
91
  }
91
92
 
92
93
  /** Every recorded step, oldest first — the chain composes, so it is replayed whole. */
93
- async function stepsOf(frondPath: string): Promise<Array<{ version: string; step: SetDiff }>> {
94
+ async function stepsOf(frondPath: string): Promise<{ version: string; step: SetDiff }[]> {
94
95
  const chain = await chainOf(frondPath);
95
96
  // The first version has a shape and no step — there was nothing before it to move from.
96
97
  return chain.flatMap(({ name, step }) => (step ? [{ version: name, step }] : []));
@@ -1,5 +1,5 @@
1
1
  import { entity, text } from '@fougere/schema';
2
2
 
3
3
  export default class BuildFrond extends entity({
4
- name: text({ description: 'Frond name to build (e.g. blog)' }),
4
+ frond: text({ description: 'Frond name to build (e.g. blog)' }),
5
5
  }) {}
@@ -2,5 +2,5 @@ import { entity, text } from "@fougere/schema";
2
2
 
3
3
  /** `fougere call <entity>.<op> [--field value …]` — invoke one operation, print the result. */
4
4
  export default class Call extends entity({
5
- target: text({ min: 1, description: "entity.op to invoke (e.g. post.create)" }),
5
+ operation: text({ min: 1, description: "entity.op to invoke (e.g. post.create)" }),
6
6
  }) {}
@@ -1,6 +1,6 @@
1
1
  import { entity, text } from '@fougere/schema';
2
2
 
3
3
  export default class Sync extends entity({
4
- name: text({ description: 'Frond name to sync (e.g. blog)' }),
4
+ frond: text({ description: 'Frond name to sync (e.g. blog)' }),
5
5
  from: text({ description: 'Remote URL (e.g. http://blog-service:3000)' }),
6
6
  }) {}