@fougere/cli 0.2.0-alpha.2 → 0.4.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 (81) hide show
  1. package/README.md +11 -2
  2. package/app/commands/BuildCommand.ts +39 -0
  3. package/app/commands/CallCommand.ts +3 -3
  4. package/app/commands/CheckCommand.ts +2 -1
  5. package/app/commands/ExplainCommand.ts +124 -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 +74 -7
  12. package/app/commands/grant-material.ts +5 -0
  13. package/dist/bin.js +55 -10
  14. package/dist/bin.js.map +1 -1
  15. package/dist/bridge.d.ts.map +1 -1
  16. package/dist/bridge.js +15 -8
  17. package/dist/bridge.js.map +1 -1
  18. package/dist/completion.d.ts +4 -1
  19. package/dist/completion.d.ts.map +1 -1
  20. package/dist/completion.js +54 -20
  21. package/dist/completion.js.map +1 -1
  22. package/dist/loader.d.ts +11 -0
  23. package/dist/loader.d.ts.map +1 -0
  24. package/dist/loader.js +23 -0
  25. package/dist/loader.js.map +1 -0
  26. package/dist/runner.d.ts.map +1 -1
  27. package/dist/runner.js +7 -6
  28. package/dist/runner.js.map +1 -1
  29. package/fronds/analysis/entities/Build.ts +7 -0
  30. package/fronds/analysis/entities/Explain.ts +9 -0
  31. package/fronds/analysis/entities/Freeze.ts +7 -0
  32. package/fronds/analysis/entities/Migrate.ts +7 -0
  33. package/fronds/analysis/handlers/BuildHandler.ts +60 -0
  34. package/fronds/analysis/handlers/CheckHandler.ts +40 -41
  35. package/fronds/analysis/handlers/ExplainHandler.ts +254 -0
  36. package/fronds/analysis/handlers/FreezeHandler.ts +176 -0
  37. package/fronds/analysis/handlers/MigrateHandler.ts +97 -0
  38. package/fronds/analysis/services/ProjectScan.ts +23 -7
  39. package/fronds/analysis/versions.ts +58 -0
  40. package/fronds/scaffold/entities/BuildFrond.ts +1 -1
  41. package/fronds/scaffold/entities/Call.ts +1 -1
  42. package/fronds/scaffold/entities/Grant.ts +6 -0
  43. package/fronds/scaffold/entities/Keys.ts +4 -0
  44. package/fronds/scaffold/entities/Serve.ts +2 -1
  45. package/fronds/scaffold/entities/Sync.ts +1 -1
  46. package/fronds/scaffold/handlers/BuildFrondHandler.ts +13 -15
  47. package/fronds/scaffold/handlers/GrantHandler.ts +8 -0
  48. package/fronds/scaffold/handlers/KeysHandler.ts +8 -0
  49. package/fronds/scaffold/handlers/SyncHandler.ts +40 -37
  50. package/fronds/scaffold/services/ProjectWriter.ts +9 -8
  51. package/package.json +10 -8
  52. package/src/bin.ts +83 -0
  53. package/src/bridge.ts +70 -0
  54. package/src/completion.ts +152 -0
  55. package/src/index.ts +3 -0
  56. package/src/loader.ts +28 -0
  57. package/src/runner.ts +139 -0
  58. package/src/theme.ts +19 -0
  59. package/src/ui.ts +131 -0
  60. package/templates/admin/fronds/admin/handlers/UserHandler.ts +3 -5
  61. package/templates/admin/fronds/admin/package.json +1 -1
  62. package/templates/api/fronds/api/handlers/TaskHandler.ts +3 -5
  63. package/templates/api/fronds/api/package.json +1 -1
  64. package/templates/apps/nuxt/app/pages/index.vue +1 -1
  65. package/templates/blog/app/pages/posts/index.vue +1 -1
  66. package/templates/blog/app/pages/posts/manage.vue +1 -1
  67. package/templates/blog/app/pages/posts/new.vue +1 -1
  68. package/templates/blog/fronds/blog/handlers/PostHandler.ts +3 -5
  69. package/templates/blog/fronds/blog/package.json +1 -1
  70. package/templates/flat/AGENTS.md +14 -0
  71. package/templates/flat/CLAUDE.md +25 -3
  72. package/templates/frond/AGENTS.md +14 -0
  73. package/templates/frond/CLAUDE.md +25 -3
  74. package/templates/frond/fronds/__name__/handlers/PostHandler.ts +3 -5
  75. package/templates/frond/fronds/__name__/package.json +1 -1
  76. package/templates/frond/serve.mjs +3 -2
  77. package/templates/fronds/blank/package.json +1 -1
  78. package/templates/fronds/blog/handlers/PostHandler.ts +2 -4
  79. package/templates/fronds/blog/package.json +1 -1
  80. package/templates/workspace/AGENTS.md +14 -0
  81. package/templates/workspace/CLAUDE.md +25 -3
package/dist/bin.js CHANGED
@@ -6,23 +6,68 @@
6
6
  * fronds/ → loaded at runtime by jiti (domain)
7
7
  * app/ → loaded at runtime by jiti (presentation)
8
8
  */
9
- import { createApp, setModuleLoader } from '@fougere/core';
9
+ import { createApp, setLogLevel, envLevel } from '@fougere/core';
10
+ import { scanProject, getModuleLoader, frondDirsOf, DEFAULT_CONVENTIONS } from '@fougere/core/node';
11
+ import { readdir, stat } from 'node:fs/promises';
12
+ import { join } from 'node:path';
10
13
  import { createContainer } from '@fougere/container';
11
14
  import { ui } from './ui.js';
12
15
  import { run } from './runner.js';
13
- const { createJiti } = await import('jiti');
14
- const jiti = createJiti(import.meta.url, { interopDefault: true });
15
- setModuleLoader((filePath) => jiti.import(filePath));
16
+ import { installLoader } from './loader.js';
17
+ await installLoader(process.cwd());
16
18
  const cliRoot = new URL('..', import.meta.url).pathname;
17
19
  const container = createContainer();
18
- container.registerValue('ui', ui());
20
+ const terminal = ui();
21
+ container.registerValue('ui', terminal);
19
22
  container.registerValue('cwd', process.cwd());
20
- // The CLI is a Fougere app — silence its boot chatter unless explicitly asked.
23
+ // The CLI is a Fougere app — silence its boot chatter unless explicitly asked. The
24
+ // threshold is SET, not only announced: a static import evaluates the logger module,
25
+ // its env read included, before this line runs.
21
26
  process.env.FOUGERE_LOG_LEVEL ??= 'warn';
22
- const app = await createApp({
23
- root: cliRoot,
24
- createContainer: () => container,
25
- });
27
+ setLogLevel(envLevel() ?? 'warn');
28
+ /** The newest declaration under `fronds/`, or 0 when there is none to compare against. */
29
+ async function newestDeclaration(root) {
30
+ const frondsDir = join(root, DEFAULT_CONVENTIONS.fronds);
31
+ const names = await readdir(frondsDir, { withFileTypes: true }).catch(() => []);
32
+ const dirs = names.filter((entry) => entry.isDirectory())
33
+ .flatMap((entry) => [
34
+ join(frondsDir, entry.name),
35
+ ...frondDirsOf(DEFAULT_CONVENTIONS).map((dir) => join(frondsDir, entry.name, dir)),
36
+ ]);
37
+ const times = await Promise.all(dirs.map(async (dir) => {
38
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
39
+ const stats = await Promise.all(entries
40
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.ts'))
41
+ .map((entry) => stat(join(dir, entry.name)).then((s) => s.mtimeMs).catch(() => 0)));
42
+ return Math.max(0, ...stats);
43
+ }));
44
+ return Math.max(0, ...times);
45
+ }
46
+ /**
47
+ * The CLI is a Fougere app, so it reads its own written-down scan like any deployment —
48
+ * producing the description reads the project, consuming it does not.
49
+ *
50
+ * Reading its own 44 declarations through the compiler cost 617 ms on every invocation,
51
+ * for a domain fixed at publish time. Staleness is decided by mtime rather than by a flag:
52
+ * editing a frond must not need a command, and a published package has nothing newer than
53
+ * its artefact.
54
+ */
55
+ async function scanOf(root) {
56
+ const written = join(root, '.fougere/scan.generated.ts');
57
+ const writtenAt = await stat(written).then((s) => s.mtimeMs).catch(() => 0);
58
+ if (writtenAt > 0 && writtenAt >= await newestDeclaration(root)) {
59
+ return (await getModuleLoader()(written)).scan;
60
+ }
61
+ // The only slow phase, and it announced nothing: the boot states it at `info`, which the
62
+ // threshold above lowers to `warn`. The terminal says it instead, and a pipe keeps its
63
+ // output parsable.
64
+ const spin = process.stdout.isTTY ? terminal.spinner('reading fronds') : undefined;
65
+ const scan = await scanProject(root);
66
+ spin?.stop(`${scan.fronds.length} frond(s)`);
67
+ return scan;
68
+ }
69
+ const scan = await scanOf(cliRoot);
70
+ const app = await createApp({ scan, createContainer: () => container });
26
71
  container.registerValue('app', app);
27
72
  await run(app);
28
73
  //# sourceMappingURL=bin.js.map
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,CAqDpD"}
package/dist/bridge.js CHANGED
@@ -1,4 +1,5 @@
1
- import { Anatomy, inputFields } from '@fougere/schema';
1
+ import { Lifecycle, Role } from '@fougere/schema';
2
+ import { Anatomy, Visibility } from '@fougere/schema';
2
3
  function toKebab(name) {
3
4
  return name.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
4
5
  }
@@ -8,17 +9,16 @@ export function entityToArgs(fields) {
8
9
  let positionalIndex = 0;
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
- for (const [key, field] of Object.entries(inputFields(fields))) {
12
- if (field.role?.relation)
12
+ for (const [key, field] of Object.entries(Visibility.of(fields).input)) {
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':
@@ -29,8 +29,15 @@ export function entityToArgs(fields) {
29
29
  case 'number':
30
30
  case 'integer':
31
31
  case 'string':
32
- // A date-time string stays a named string never a positional arg.
33
- if (shape.type === 'string' && shape.format === 'date-time') {
32
+ // A closed set is citty's `enum`: the shape already names the legal values, so the
33
+ // refusal and the `--help` listing come from the declaration rather than a check
34
+ // written beside it.
35
+ if (shape.type === 'string' && shape.enum?.length) {
36
+ def.type = 'enum';
37
+ def.options = shape.enum.filter((v) => v !== null);
38
+ // A date-time string stays a named string — never a positional arg.
39
+ }
40
+ else if (shape.type === 'string' && shape.format === 'date-time') {
34
41
  def.type = 'string';
35
42
  }
36
43
  else if (positionalIndex === 0 && def.required && key !== 'force') {
@@ -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":"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"}
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * Shell completion generator — outputs a script for bash/zsh.
3
3
  *
4
- * Sources: scanned frond handlers (commands) + entity fields (flags).
4
+ * Commands and flags are the CLI's own entities, read once at generation. The VALUES a
5
+ * positional accepts belong to the project the shell sits in, so they are not written
6
+ * down here: the script asks `fougere explain --names` at the moment of the TAB. A list
7
+ * frozen into a script is stale the first time a handler is added.
5
8
  */
6
9
  import type { App } from '@fougere/core';
7
10
  export declare function generateZshCompletion(app: App, binName?: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"completion.d.ts","sourceRoot":"","sources":["../src/completion.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAsCzC,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,SAAY,GAAG,MAAM,CAmC3E;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,SAAY,GAAG,MAAM,CA8B5E"}
1
+ {"version":3,"file":"completion.d.ts","sourceRoot":"","sources":["../src/completion.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAkDzC,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,SAAY,GAAG,MAAM,CAgD3E;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,SAAY,GAAG,MAAM,CA2C5E"}
@@ -1,7 +1,12 @@
1
- import { inputFields } from '@fougere/schema';
1
+ import { entityToArgs } from './bridge.js';
2
2
  function toKebab(name) {
3
3
  return name.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase()).replace(/^-/, '');
4
4
  }
5
+ /** What a positional names, said by the field's own name — no table to keep in step. */
6
+ const NAMES = {
7
+ operation: 'operations',
8
+ frond: 'fronds',
9
+ };
5
10
  function extractCommandMeta(app) {
6
11
  const commands = [];
7
12
  for (const frond of app.fronds) {
@@ -19,25 +24,37 @@ function extractCommandMeta(app) {
19
24
  }
20
25
  if (typeof facade.execute !== 'function')
21
26
  continue;
22
- const fields = entity.entityClass.getFields();
23
- const flags = Object.keys(inputFields(fields))
24
- .map((key) => `--${toKebab(key)}`);
25
- commands.push({ name: toKebab(entity.name), flags });
27
+ // The same function the runner builds citty's args from, so the positional and the
28
+ // flags are the ones the command actually has.
29
+ const args = Object.entries(entityToArgs(entity.entityClass.getFields()));
30
+ const positional = args.find(([, def]) => def.type === 'positional')?.[0];
31
+ commands.push({
32
+ name: toKebab(entity.name),
33
+ flags: args.filter(([, def]) => def.type !== 'positional').map(([key]) => `--${key}`),
34
+ ...(positional && NAMES[positional] ? { names: NAMES[positional] } : {}),
35
+ });
26
36
  }
27
37
  }
28
38
  return commands;
29
39
  }
30
40
  export function generateZshCompletion(app, binName = 'fougere') {
31
41
  const commands = extractCommandMeta(app);
32
- const cmdNames = commands.map((c) => c.name).join(' ');
42
+ const valueCases = commands
43
+ .filter((cmd) => cmd.names)
44
+ .map((cmd) => ` ${cmd.name}) _${binName}_names ${cmd.names} ;;`)
45
+ .join('\n');
33
46
  const flagCases = commands
34
- .map((cmd) => {
35
- const flags = cmd.flags.join(' ');
36
- return ` ${cmd.name}) flags="${flags}" ;;`;
37
- })
47
+ .map((cmd) => ` ${cmd.name}) flags="${cmd.flags.join(' ')}" ;;`)
38
48
  .join('\n');
39
49
  return `# Auto-generated by @fougere/cli
40
50
 
51
+ # The project answers; a stale list in this file would not.
52
+ _${binName}_names() {
53
+ local -a values
54
+ values=(\${(f)"$(${binName} explain --names $1 2>/dev/null)"})
55
+ (( \${#values} )) && compadd -- \${values}
56
+ }
57
+
41
58
  _${binName}() {
42
59
  local -a commands flags
43
60
  commands=(${commands.map((c) => `'${c.name}'`).join(' ')})
@@ -48,14 +65,19 @@ _${binName}() {
48
65
  fi
49
66
 
50
67
  local cmd=\${words[2]}
68
+
69
+ if [[ "\${words[CURRENT]}" != --* ]]; then
70
+ case "$cmd" in
71
+ ${valueCases}
72
+ esac
73
+ return
74
+ fi
75
+
51
76
  case "$cmd" in
52
77
  ${flagCases}
53
78
  *) flags="" ;;
54
79
  esac
55
-
56
- if [[ "\${words[CURRENT]}" == --* ]]; then
57
- _values 'flags' \${(s: :)flags}
58
- fi
80
+ _values 'flags' \${(s: :)flags}
59
81
  }
60
82
 
61
83
  compdef _${binName} ${binName}
@@ -64,17 +86,17 @@ compdef _${binName} ${binName}
64
86
  export function generateBashCompletion(app, binName = 'fougere') {
65
87
  const commands = extractCommandMeta(app);
66
88
  const cmdNames = commands.map((c) => c.name).join(' ');
89
+ const valueCases = commands
90
+ .filter((cmd) => cmd.names)
91
+ .map((cmd) => ` ${cmd.name}) names="${cmd.names}" ;;`)
92
+ .join('\n');
67
93
  const flagCases = commands
68
- .map((cmd) => {
69
- const flags = cmd.flags.join(' ');
70
- return ` ${cmd.name}) COMPREPLY=( $(compgen -W "${flags}" -- "\${cur}") ) ;;`;
71
- })
94
+ .map((cmd) => ` ${cmd.name}) COMPREPLY=( $(compgen -W "${cmd.flags.join(' ')}" -- "\${cur}") ) ;;`)
72
95
  .join('\n');
73
96
  return `# Auto-generated by @fougere/cli
74
97
  _${binName}_completions() {
75
- local cur prev cmd
98
+ local cur cmd names
76
99
  cur="\${COMP_WORDS[COMP_CWORD]}"
77
- prev="\${COMP_WORDS[COMP_CWORD-1]}"
78
100
  cmd="\${COMP_WORDS[1]}"
79
101
 
80
102
  if [[ \${COMP_CWORD} -eq 1 ]]; then
@@ -82,6 +104,18 @@ _${binName}_completions() {
82
104
  return
83
105
  fi
84
106
 
107
+ if [[ "\${cur}" != --* ]]; then
108
+ names=""
109
+ case "$cmd" in
110
+ ${valueCases}
111
+ esac
112
+ if [[ -n "\${names}" ]]; then
113
+ # The project answers; a stale list in this file would not.
114
+ COMPREPLY=( $(compgen -W "$(${binName} explain --names "\${names}" 2>/dev/null | tr '\\n' ' ')" -- "\${cur}") )
115
+ return
116
+ fi
117
+ fi
118
+
85
119
  case "$cmd" in
86
120
  ${flagCases}
87
121
  esac
@@ -1 +1 @@
1
- {"version":3,"file":"completion.js","sourceRoot":"","sources":["../src/completion.ts"],"names":[],"mappings":"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"}
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"}
@@ -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,CA0FjD"}
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 { lowerFirst } 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,11 @@ 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'
82
+ && (parsed.json === true || typeof parsed.names === 'string');
83
+ if (cmdName !== 'completion' && !machineOutput)
83
84
  terminal.intro();
84
85
  // citty adds `_` (raw positionals) and `--` (passthrough); strip them
85
86
  // so only the entity's own fields reach the handler.
@@ -93,7 +94,7 @@ export async function run(app) {
93
94
  }
94
95
  else {
95
96
  // 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: {} });
97
+ await createAppRunner(app)({ entity: lowerFirst(entity.name), op: 'execute' }, { params: {}, query: {}, body: input, state: {} });
97
98
  }
98
99
  }
99
100
  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,9 @@
1
+ import { bool, entity, oneOf, 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
+ names: optional(oneOf('operations', 'fronds', { description: 'Print one name per line — what shell completion reads' })),
9
+ }) {}
@@ -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
+ }