@fougere/cli 0.11.0-alpha.0 → 0.12.1-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.
- package/app/commands/GraphCommand.ts +5 -5
- package/app/commands/ServeCommand.ts +2 -2
- package/fronds/analysis/handlers/CheckHandler.ts +2 -1
- package/fronds/analysis/handlers/DevtoolsHandler.ts +2 -1
- package/fronds/analysis/handlers/ExplainHandler.ts +3 -2
- package/fronds/analysis/handlers/GraphHandler.ts +9 -6
- package/fronds/analysis/handlers/LoadHandler.ts +2 -1
- package/fronds/analysis/services/ProjectScan.ts +6 -2
- package/package.json +15 -15
|
@@ -19,12 +19,12 @@ export default class GraphCommand {
|
|
|
19
19
|
|
|
20
20
|
if (machineWanted(raw)) return printMachine(result);
|
|
21
21
|
|
|
22
|
-
if (result.
|
|
22
|
+
if (result.totalFronds === 0) {
|
|
23
23
|
this.ui.warn('No fronds found. Run this from a Fougere project root.');
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
this.ui.step(`${pc.bold(String(result.totalEntities))} entities, ${pc.bold(String(result.totalRefs))} refs, ${pc.bold(String(result.
|
|
27
|
+
this.ui.step(`${pc.bold(String(result.totalEntities))} entities, ${pc.bold(String(result.totalRefs))} refs, ${pc.bold(String(result.totalFronds))} frond(s)`);
|
|
28
28
|
this.ui.note(renderFronds(result.declared), 'Fronds');
|
|
29
29
|
this.ui.note(renderGraph(result.nodes), 'Entity Graph');
|
|
30
30
|
|
|
@@ -33,7 +33,7 @@ export default class GraphCommand {
|
|
|
33
33
|
this.ui.note(renderClusters(result.clusters), 'Suggested Domains');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
if (result.
|
|
36
|
+
if (result.totalFronds === 1 && result.clusters.length > 1 && result.totalEntities >= min) {
|
|
37
37
|
this.ui.info(`${pc.dim('Tip:')} ${result.clusters.length} natural domains detected. Consider splitting into separate fronds.`);
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -63,8 +63,8 @@ function renderFronds(declared: DeclaredTopology): string {
|
|
|
63
63
|
}).join('\n');
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function renderGraph(nodes:
|
|
67
|
-
return
|
|
66
|
+
function renderGraph(nodes: Record<string, EntityNode>): string {
|
|
67
|
+
return Object.entries(nodes).map(([name, node]) => {
|
|
68
68
|
const refs = node.refs.length > 0
|
|
69
69
|
? ` ${pc.dim('→')} ${node.refs.map((r) => pc.cyan(r)).join(', ')}`
|
|
70
70
|
: '';
|
|
@@ -36,7 +36,7 @@ export default class ServeCommand {
|
|
|
36
36
|
// the same way rather than the first one being special.
|
|
37
37
|
const conventions = await installLoader(root, watching);
|
|
38
38
|
|
|
39
|
-
let hosted = await bootApp(root, {
|
|
39
|
+
let hosted = await bootApp(root, { only: [frond], topology: false });
|
|
40
40
|
if (!hosted.fronds.some((f) => f.name === frond)) {
|
|
41
41
|
this.ui.error(`Frond '${frond}' introuvable dans ce projet.`);
|
|
42
42
|
return;
|
|
@@ -64,7 +64,7 @@ export default class ServeCommand {
|
|
|
64
64
|
const started = Date.now();
|
|
65
65
|
let next: App;
|
|
66
66
|
try {
|
|
67
|
-
next = await bootApp(root, {
|
|
67
|
+
next = await bootApp(root, { only: [frond], topology: false });
|
|
68
68
|
} catch (error) {
|
|
69
69
|
// The previous app keeps serving: a dev loop that dies on a typo is worse than
|
|
70
70
|
// one that holds the last state which booted.
|
|
@@ -12,6 +12,7 @@ import ProjectScan from '../services/ProjectScan.js';
|
|
|
12
12
|
import { statSync } from 'node:fs';
|
|
13
13
|
import { dirname, join } from 'node:path';
|
|
14
14
|
import { FACADE_OUT, DEFAULT_OUT } from './BuildHandler.js';
|
|
15
|
+
import { remotesOf } from '@fougere/core/node';
|
|
15
16
|
|
|
16
17
|
export interface CheckResult {
|
|
17
18
|
fronds: number;
|
|
@@ -46,7 +47,7 @@ export default class CheckHandler {
|
|
|
46
47
|
// migration, seed or adapter mount is needed for a global semantic check.
|
|
47
48
|
const model = resolveEffectiveOperations(fronds, {
|
|
48
49
|
diagnostics,
|
|
49
|
-
remotes: config
|
|
50
|
+
remotes: remotesOf(config),
|
|
50
51
|
adapters: config.adapters,
|
|
51
52
|
});
|
|
52
53
|
const findings: Diagnostic[] = [...model.diagnostics];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHttpTransport } from '@fougere/transport-http/client';
|
|
2
2
|
import type { CallPage, CallRecord } from '@fougere/core';
|
|
3
3
|
import ProjectScan from '../services/ProjectScan.js';
|
|
4
|
+
import { remotesOf } from '@fougere/core/node';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Where an app answers in development — Nuxt, Next and the site all sit there, so it is a
|
|
@@ -80,7 +81,7 @@ export default class DevtoolsHandler {
|
|
|
80
81
|
private async addresses(root?: string): Promise<{ url: string; frond?: string }[]> {
|
|
81
82
|
const { config } = await this.projectScan.at(root);
|
|
82
83
|
const here = local();
|
|
83
|
-
const remotes = Object.entries(config
|
|
84
|
+
const remotes = Object.entries(remotesOf(config))
|
|
84
85
|
.map(([frond, url]) => ({ url: trimmed(url), frond }));
|
|
85
86
|
|
|
86
87
|
return [{ url: here }, ...remotes.filter((one) => one.url !== here)];
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import { relative } from 'node:path';
|
|
7
7
|
import ProjectScan from '../services/ProjectScan.js';
|
|
8
8
|
import { ANONYMOUS_SCHEMA_NAME, lowerFirst, type SchemaView } from '@fougere/schema';
|
|
9
|
+
import { remotesOf } from '@fougere/core/node';
|
|
9
10
|
|
|
10
11
|
type Cardinality = NonNullable<OperationContract['cardinality']>;
|
|
11
12
|
type Binding = EffectiveOperation['binding'][number];
|
|
@@ -91,7 +92,7 @@ export default class ExplainHandler {
|
|
|
91
92
|
/** The names this project serves — the list `explain` used to spell in a refusal only. */
|
|
92
93
|
async list(input: { root?: string }): Promise<ExplainListing> {
|
|
93
94
|
const { scan, model } = await this.modelOf(input.root);
|
|
94
|
-
const remotes = scan.config
|
|
95
|
+
const remotes = remotesOf(scan.config);
|
|
95
96
|
const counted = new Map<string, number>();
|
|
96
97
|
for (const operation of model.operations) {
|
|
97
98
|
const frond = operation.placement.frond;
|
|
@@ -115,7 +116,7 @@ export default class ExplainHandler {
|
|
|
115
116
|
scan,
|
|
116
117
|
model: resolveEffectiveOperations(scan.fronds, {
|
|
117
118
|
diagnostics: scan.diagnostics,
|
|
118
|
-
remotes: scan.config
|
|
119
|
+
remotes: remotesOf(scan.config),
|
|
119
120
|
adapters: scan.config.adapters,
|
|
120
121
|
}),
|
|
121
122
|
};
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildGraph, declaredTopologyOf, suggestSplit,
|
|
3
|
-
type DeclaredTopology, type EntityNode, type DomainCluster,
|
|
3
|
+
type DeclaredTopology, type EntityNode, type DomainCluster,
|
|
4
4
|
} from '@fougere/core';
|
|
5
5
|
import ProjectScan from '../services/ProjectScan.js';
|
|
6
|
+
import { remotesOf } from '@fougere/core/node';
|
|
6
7
|
|
|
7
8
|
export interface GraphResult {
|
|
8
|
-
fronds
|
|
9
|
+
/** How many fronds the scan found — the descriptors themselves hold classes, which no
|
|
10
|
+
* operation answers: a reader outside this process would receive the husk JSON makes of them. */
|
|
11
|
+
totalFronds: number;
|
|
9
12
|
/** Where the fronds run and which reaches which — the same picture, one altitude up. */
|
|
10
13
|
declared: DeclaredTopology;
|
|
11
|
-
nodes:
|
|
14
|
+
nodes: Record<string, EntityNode>;
|
|
12
15
|
clusters: DomainCluster[];
|
|
13
16
|
totalEntities: number;
|
|
14
17
|
totalRefs: number;
|
|
@@ -24,9 +27,9 @@ export default class GraphHandler {
|
|
|
24
27
|
const clusters = suggestSplit(nodes);
|
|
25
28
|
|
|
26
29
|
return {
|
|
27
|
-
fronds,
|
|
28
|
-
declared: declaredTopologyOf({ fronds, remotes: config
|
|
29
|
-
nodes,
|
|
30
|
+
totalFronds: fronds.length,
|
|
31
|
+
declared: declaredTopologyOf({ fronds, remotes: remotesOf(config) }),
|
|
32
|
+
nodes: Object.fromEntries(nodes),
|
|
30
33
|
clusters,
|
|
31
34
|
totalEntities: nodes.size,
|
|
32
35
|
totalRefs: [...nodes.values()].reduce((sum, n) => sum + n.refs.length, 0),
|
|
@@ -2,6 +2,7 @@ import { loadScript, reachableOps } from '@fougere/testing';
|
|
|
2
2
|
import { writeFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import ProjectScan from '../services/ProjectScan.js';
|
|
5
|
+
import { remotesOf } from '@fougere/core/node';
|
|
5
6
|
|
|
6
7
|
export interface LoadScenario {
|
|
7
8
|
/** Where it was written, or `null` when it was only printed. */
|
|
@@ -27,7 +28,7 @@ export default class LoadHandler {
|
|
|
27
28
|
const facade = input.facade?.trim() || undefined;
|
|
28
29
|
// The topology statement travels with it: an op that crosses a process is not held to the
|
|
29
30
|
// same figure as one that never leaves, and `remotes:` is what says which is which.
|
|
30
|
-
const remotes = config
|
|
31
|
+
const remotes = remotesOf(config);
|
|
31
32
|
const script = loadScript({ fronds }, { ...(facade ? { facade } : {}), remotes });
|
|
32
33
|
|
|
33
34
|
const file = input.out === undefined ? join(root, 'load.js') : input.out || null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FougereConfig, type ScanResult } from '@fougere/core';
|
|
2
2
|
import { scanProject, frondAliases } from '@fougere/compiler';
|
|
3
|
-
import { resolveConventions } from '@fougere/core';
|
|
3
|
+
import { nested, resolveConventions } from '@fougere/core';
|
|
4
4
|
import { setModuleLoader, loadConfig } from '@fougere/core/node';
|
|
5
5
|
import { resolve } from 'node:path';
|
|
6
6
|
|
|
@@ -46,6 +46,10 @@ export default class ProjectScan {
|
|
|
46
46
|
// naming its neighbour is unreadable to the very tool that checks it.
|
|
47
47
|
install(await frondAliases(target, conventions));
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
const scanned = await scanProject(target, undefined, conventions);
|
|
50
|
+
|
|
51
|
+
// A scan reads the disk, and the disk is flat: who inherits from whom is in the config.
|
|
52
|
+
// Stamped here so every reader of a project gets what a boot would have given it.
|
|
53
|
+
return { root: target, config, ...scanned, fronds: nested(scanned.fronds, config.fronds) };
|
|
50
54
|
}
|
|
51
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fougere/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1-alpha.0",
|
|
4
4
|
"description": "The Fougere CLI — compose a workspace, serve a frond, call an operation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fougere",
|
|
@@ -42,20 +42,20 @@
|
|
|
42
42
|
"jiti": "^2.4.2",
|
|
43
43
|
"picocolors": "^1.1.1",
|
|
44
44
|
"safe-regex": "^2.1.1",
|
|
45
|
-
"@fougere/
|
|
46
|
-
"@fougere/
|
|
47
|
-
"@fougere/
|
|
48
|
-
"@fougere/
|
|
49
|
-
"@fougere/
|
|
50
|
-
"@fougere/
|
|
51
|
-
"@fougere/schema": "0.
|
|
52
|
-
"@fougere/
|
|
53
|
-
"@fougere/
|
|
54
|
-
"@fougere/
|
|
55
|
-
"@fougere/
|
|
56
|
-
"@fougere/
|
|
57
|
-
"@fougere/admin": "0.
|
|
58
|
-
"@fougere/
|
|
45
|
+
"@fougere/compiler": "0.12.1-alpha.0",
|
|
46
|
+
"@fougere/defaults": "0.12.1-alpha.0",
|
|
47
|
+
"@fougere/adapter-sql": "0.12.1-alpha.0",
|
|
48
|
+
"@fougere/next": "0.12.1-alpha.0",
|
|
49
|
+
"@fougere/react": "0.12.1-alpha.0",
|
|
50
|
+
"@fougere/core": "0.12.1-alpha.0",
|
|
51
|
+
"@fougere/schema": "0.12.1-alpha.0",
|
|
52
|
+
"@fougere/container": "0.12.1-alpha.0",
|
|
53
|
+
"@fougere/testing": "0.12.1-alpha.0",
|
|
54
|
+
"@fougere/nuxt": "0.12.1-alpha.0",
|
|
55
|
+
"@fougere/svelte": "0.12.1-alpha.0",
|
|
56
|
+
"@fougere/transport-http": "0.12.1-alpha.0",
|
|
57
|
+
"@fougere/admin": "0.12.1-alpha.0",
|
|
58
|
+
"@fougere/oclif": "0.12.1-alpha.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"vitest": "^4.1.0"
|