@fougere/cli 0.12.5-alpha.0 → 0.12.6-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/dist/.fougere/scan.generated.d.ts +3 -0
- package/dist/.fougere/scan.generated.d.ts.map +1 -0
- package/dist/.fougere/scan.generated.js +140 -0
- package/dist/.fougere/scan.generated.js.map +1 -0
- package/dist/app/commands/BuildCommand.d.ts +18 -0
- package/dist/app/commands/BuildCommand.d.ts.map +1 -0
- package/dist/app/commands/BuildCommand.js +35 -0
- package/dist/app/commands/BuildCommand.js.map +1 -0
- package/dist/app/commands/CallCommand.d.ts +16 -0
- package/dist/app/commands/CallCommand.d.ts.map +1 -0
- package/dist/app/commands/CallCommand.js +73 -0
- package/dist/app/commands/CallCommand.js.map +1 -0
- package/dist/app/commands/CheckCommand.d.ts +11 -0
- package/dist/app/commands/CheckCommand.d.ts.map +1 -0
- package/dist/app/commands/CheckCommand.js +59 -0
- package/dist/app/commands/CheckCommand.js.map +1 -0
- package/dist/app/commands/CompletionCommand.d.ts +7 -0
- package/dist/app/commands/CompletionCommand.d.ts.map +1 -0
- package/dist/app/commands/CompletionCommand.js +23 -0
- package/dist/app/commands/CompletionCommand.js.map +1 -0
- package/dist/app/commands/DevtoolsCommand.d.ts +20 -0
- package/dist/app/commands/DevtoolsCommand.d.ts.map +1 -0
- package/dist/app/commands/DevtoolsCommand.js +79 -0
- package/dist/app/commands/DevtoolsCommand.js.map +1 -0
- package/dist/app/commands/ExplainCommand.d.ts +18 -0
- package/dist/app/commands/ExplainCommand.d.ts.map +1 -0
- package/dist/app/commands/ExplainCommand.js +111 -0
- package/dist/app/commands/ExplainCommand.js.map +1 -0
- package/dist/app/commands/FreezeCommand.d.ts +21 -0
- package/dist/app/commands/FreezeCommand.d.ts.map +1 -0
- package/dist/app/commands/FreezeCommand.js +102 -0
- package/dist/app/commands/FreezeCommand.js.map +1 -0
- package/dist/app/commands/GrantCommand.d.ts +18 -0
- package/dist/app/commands/GrantCommand.d.ts.map +1 -0
- package/dist/app/commands/GrantCommand.js +39 -0
- package/dist/app/commands/GrantCommand.js.map +1 -0
- package/dist/app/commands/GraphCommand.d.ts +11 -0
- package/dist/app/commands/GraphCommand.d.ts.map +1 -0
- package/dist/app/commands/GraphCommand.js +72 -0
- package/dist/app/commands/GraphCommand.js.map +1 -0
- package/dist/app/commands/KeysCommand.d.ts +22 -0
- package/dist/app/commands/KeysCommand.d.ts.map +1 -0
- package/dist/app/commands/KeysCommand.js +50 -0
- package/dist/app/commands/KeysCommand.js.map +1 -0
- package/dist/app/commands/LoadCommand.d.ts +19 -0
- package/dist/app/commands/LoadCommand.d.ts.map +1 -0
- package/dist/app/commands/LoadCommand.js +37 -0
- package/dist/app/commands/LoadCommand.js.map +1 -0
- package/dist/app/commands/MigrateCommand.d.ts +17 -0
- package/dist/app/commands/MigrateCommand.d.ts.map +1 -0
- package/dist/app/commands/MigrateCommand.js +47 -0
- package/dist/app/commands/MigrateCommand.js.map +1 -0
- package/dist/app/commands/NewCommand.d.ts +24 -0
- package/dist/app/commands/NewCommand.d.ts.map +1 -0
- package/dist/app/commands/NewCommand.js +131 -0
- package/dist/app/commands/NewCommand.js.map +1 -0
- package/dist/app/commands/ServeCommand.d.ts +17 -0
- package/dist/app/commands/ServeCommand.d.ts.map +1 -0
- package/dist/app/commands/ServeCommand.js +93 -0
- package/dist/app/commands/ServeCommand.js.map +1 -0
- package/dist/app/commands/grant-material.d.ts +5 -0
- package/dist/app/commands/grant-material.d.ts.map +1 -0
- package/{app/commands/grant-material.ts → dist/app/commands/grant-material.js} +2 -2
- package/dist/app/commands/grant-material.js.map +1 -0
- package/dist/fronds/analysis/entities/Build.d.ts +10 -0
- package/dist/fronds/analysis/entities/Build.d.ts.map +1 -0
- package/dist/fronds/analysis/entities/Build.js +9 -0
- package/dist/fronds/analysis/entities/Build.js.map +1 -0
- package/dist/fronds/analysis/entities/Check.d.ts +8 -0
- package/dist/fronds/analysis/entities/Check.d.ts.map +1 -0
- package/dist/fronds/analysis/entities/Check.js +7 -0
- package/dist/fronds/analysis/entities/Check.js.map +1 -0
- package/dist/fronds/analysis/entities/Devtools.d.ts +10 -0
- package/dist/fronds/analysis/entities/Devtools.d.ts.map +1 -0
- package/dist/fronds/analysis/entities/Devtools.js +9 -0
- package/dist/fronds/analysis/entities/Devtools.js.map +1 -0
- package/dist/fronds/analysis/entities/Explain.d.ts +11 -0
- package/dist/fronds/analysis/entities/Explain.d.ts.map +1 -0
- package/dist/fronds/analysis/entities/Explain.js +10 -0
- package/dist/fronds/analysis/entities/Explain.js.map +1 -0
- package/dist/fronds/analysis/entities/Freeze.d.ts +10 -0
- package/dist/fronds/analysis/entities/Freeze.d.ts.map +1 -0
- package/dist/fronds/analysis/entities/Freeze.js +9 -0
- package/dist/fronds/analysis/entities/Freeze.js.map +1 -0
- package/dist/fronds/analysis/entities/Graph.d.ts +9 -0
- package/dist/fronds/analysis/entities/Graph.d.ts.map +1 -0
- package/dist/fronds/analysis/entities/Graph.js +8 -0
- package/dist/fronds/analysis/entities/Graph.js.map +1 -0
- package/dist/fronds/analysis/entities/Load.d.ts +10 -0
- package/dist/fronds/analysis/entities/Load.d.ts.map +1 -0
- package/dist/fronds/analysis/entities/Load.js +9 -0
- package/dist/fronds/analysis/entities/Load.js.map +1 -0
- package/dist/fronds/analysis/entities/Migrate.d.ts +10 -0
- package/dist/fronds/analysis/entities/Migrate.d.ts.map +1 -0
- package/dist/fronds/analysis/entities/Migrate.js +9 -0
- package/dist/fronds/analysis/entities/Migrate.js.map +1 -0
- package/dist/fronds/analysis/handlers/BuildHandler.d.ts +50 -0
- package/dist/fronds/analysis/handlers/BuildHandler.d.ts.map +1 -0
- package/dist/fronds/analysis/handlers/BuildHandler.js +63 -0
- package/dist/fronds/analysis/handlers/BuildHandler.js.map +1 -0
- package/dist/fronds/analysis/handlers/CheckHandler.d.ts +30 -0
- package/dist/fronds/analysis/handlers/CheckHandler.d.ts.map +1 -0
- package/dist/fronds/analysis/handlers/CheckHandler.js +196 -0
- package/dist/fronds/analysis/handlers/CheckHandler.js.map +1 -0
- package/dist/fronds/analysis/handlers/DevtoolsHandler.d.ts +45 -0
- package/dist/fronds/analysis/handlers/DevtoolsHandler.d.ts.map +1 -0
- package/dist/fronds/analysis/handlers/DevtoolsHandler.js +61 -0
- package/dist/fronds/analysis/handlers/DevtoolsHandler.js.map +1 -0
- package/dist/fronds/analysis/handlers/ExplainHandler.d.ts +99 -0
- package/dist/fronds/analysis/handlers/ExplainHandler.d.ts.map +1 -0
- package/dist/fronds/analysis/handlers/ExplainHandler.js +157 -0
- package/dist/fronds/analysis/handlers/ExplainHandler.js.map +1 -0
- package/dist/fronds/analysis/handlers/FreezeHandler.d.ts +55 -0
- package/dist/fronds/analysis/handlers/FreezeHandler.d.ts.map +1 -0
- package/dist/fronds/analysis/handlers/FreezeHandler.js +145 -0
- package/dist/fronds/analysis/handlers/FreezeHandler.js.map +1 -0
- package/dist/fronds/analysis/handlers/GraphHandler.d.ts +23 -0
- package/dist/fronds/analysis/handlers/GraphHandler.d.ts.map +1 -0
- package/dist/fronds/analysis/handlers/GraphHandler.js +23 -0
- package/dist/fronds/analysis/handlers/GraphHandler.js.map +1 -0
- package/dist/fronds/analysis/handlers/LoadHandler.d.ts +26 -0
- package/dist/fronds/analysis/handlers/LoadHandler.d.ts.map +1 -0
- package/dist/fronds/analysis/handlers/LoadHandler.js +35 -0
- package/dist/fronds/analysis/handlers/LoadHandler.js.map +1 -0
- package/dist/fronds/analysis/handlers/MigrateHandler.d.ts +34 -0
- package/dist/fronds/analysis/handlers/MigrateHandler.d.ts.map +1 -0
- package/dist/fronds/analysis/handlers/MigrateHandler.js +92 -0
- package/dist/fronds/analysis/handlers/MigrateHandler.js.map +1 -0
- package/dist/fronds/analysis/services/ProjectScan.d.ts +22 -0
- package/dist/fronds/analysis/services/ProjectScan.d.ts.map +1 -0
- package/dist/fronds/analysis/services/ProjectScan.js +48 -0
- package/dist/fronds/analysis/services/ProjectScan.js.map +1 -0
- package/dist/fronds/analysis/versions.d.ts +21 -0
- package/dist/fronds/analysis/versions.d.ts.map +1 -0
- package/dist/fronds/analysis/versions.js +48 -0
- package/dist/fronds/analysis/versions.js.map +1 -0
- package/dist/fronds/scaffold/entities/BuildFrond.d.ts +7 -0
- package/dist/fronds/scaffold/entities/BuildFrond.d.ts.map +1 -0
- package/dist/fronds/scaffold/entities/BuildFrond.js +6 -0
- package/dist/fronds/scaffold/entities/BuildFrond.js.map +1 -0
- package/dist/fronds/scaffold/entities/Call.d.ts +8 -0
- package/dist/fronds/scaffold/entities/Call.d.ts.map +1 -0
- package/{fronds/scaffold/entities/Call.ts → dist/fronds/scaffold/entities/Call.js} +4 -3
- package/dist/fronds/scaffold/entities/Call.js.map +1 -0
- package/dist/fronds/scaffold/entities/Completion.d.ts +7 -0
- package/dist/fronds/scaffold/entities/Completion.d.ts.map +1 -0
- package/dist/fronds/scaffold/entities/Completion.js +6 -0
- package/dist/fronds/scaffold/entities/Completion.js.map +1 -0
- package/dist/fronds/scaffold/entities/Grant.d.ts +8 -0
- package/dist/fronds/scaffold/entities/Grant.d.ts.map +1 -0
- package/{fronds/scaffold/entities/Grant.ts → dist/fronds/scaffold/entities/Grant.js} +4 -3
- package/dist/fronds/scaffold/entities/Grant.js.map +1 -0
- package/dist/fronds/scaffold/entities/Keys.d.ts +6 -0
- package/dist/fronds/scaffold/entities/Keys.d.ts.map +1 -0
- package/{fronds/scaffold/entities/Keys.ts → dist/fronds/scaffold/entities/Keys.js} +3 -2
- package/dist/fronds/scaffold/entities/Keys.js.map +1 -0
- package/dist/fronds/scaffold/entities/New.d.ts +21 -0
- package/dist/fronds/scaffold/entities/New.d.ts.map +1 -0
- package/dist/fronds/scaffold/entities/New.js +20 -0
- package/dist/fronds/scaffold/entities/New.js.map +1 -0
- package/dist/fronds/scaffold/entities/Serve.d.ts +10 -0
- package/dist/fronds/scaffold/entities/Serve.d.ts.map +1 -0
- package/dist/fronds/scaffold/entities/Serve.js +9 -0
- package/dist/fronds/scaffold/entities/Serve.js.map +1 -0
- package/dist/fronds/scaffold/entities/Sync.d.ts +8 -0
- package/dist/fronds/scaffold/entities/Sync.d.ts.map +1 -0
- package/dist/fronds/scaffold/entities/Sync.js +7 -0
- package/dist/fronds/scaffold/entities/Sync.js.map +1 -0
- package/dist/fronds/scaffold/handlers/BuildFrondHandler.d.ts +11 -0
- package/dist/fronds/scaffold/handlers/BuildFrondHandler.d.ts.map +1 -0
- package/dist/fronds/scaffold/handlers/BuildFrondHandler.js +82 -0
- package/dist/fronds/scaffold/handlers/BuildFrondHandler.js.map +1 -0
- package/dist/fronds/scaffold/handlers/CallHandler.d.ts +10 -0
- package/dist/fronds/scaffold/handlers/CallHandler.d.ts.map +1 -0
- package/{fronds/scaffold/handlers/CallHandler.ts → dist/fronds/scaffold/handlers/CallHandler.js} +3 -2
- package/dist/fronds/scaffold/handlers/CallHandler.js.map +1 -0
- package/dist/fronds/scaffold/handlers/CompletionHandler.d.ts +9 -0
- package/dist/fronds/scaffold/handlers/CompletionHandler.d.ts.map +1 -0
- package/{fronds/scaffold/handlers/CompletionHandler.ts → dist/fronds/scaffold/handlers/CompletionHandler.js} +3 -2
- package/dist/fronds/scaffold/handlers/CompletionHandler.js.map +1 -0
- package/dist/fronds/scaffold/handlers/GrantHandler.d.ts +9 -0
- package/dist/fronds/scaffold/handlers/GrantHandler.d.ts.map +1 -0
- package/{fronds/scaffold/handlers/GrantHandler.ts → dist/fronds/scaffold/handlers/GrantHandler.js} +3 -2
- package/dist/fronds/scaffold/handlers/GrantHandler.js.map +1 -0
- package/dist/fronds/scaffold/handlers/KeysHandler.d.ts +9 -0
- package/dist/fronds/scaffold/handlers/KeysHandler.d.ts.map +1 -0
- package/{fronds/scaffold/handlers/KeysHandler.ts → dist/fronds/scaffold/handlers/KeysHandler.js} +3 -2
- package/dist/fronds/scaffold/handlers/KeysHandler.js.map +1 -0
- package/dist/fronds/scaffold/handlers/NewHandler.d.ts +9 -0
- package/dist/fronds/scaffold/handlers/NewHandler.d.ts.map +1 -0
- package/{fronds/scaffold/handlers/NewHandler.ts → dist/fronds/scaffold/handlers/NewHandler.js} +3 -2
- package/dist/fronds/scaffold/handlers/NewHandler.js.map +1 -0
- package/dist/fronds/scaffold/handlers/ServeHandler.d.ts +10 -0
- package/dist/fronds/scaffold/handlers/ServeHandler.d.ts.map +1 -0
- package/{fronds/scaffold/handlers/ServeHandler.ts → dist/fronds/scaffold/handlers/ServeHandler.js} +3 -2
- package/dist/fronds/scaffold/handlers/ServeHandler.js.map +1 -0
- package/dist/fronds/scaffold/handlers/SyncHandler.d.ts +20 -0
- package/dist/fronds/scaffold/handlers/SyncHandler.d.ts.map +1 -0
- package/dist/fronds/scaffold/handlers/SyncHandler.js +349 -0
- package/dist/fronds/scaffold/handlers/SyncHandler.js.map +1 -0
- package/dist/fronds/scaffold/services/ProjectWriter.d.ts +71 -0
- package/dist/fronds/scaffold/services/ProjectWriter.d.ts.map +1 -0
- package/dist/fronds/scaffold/services/ProjectWriter.js +341 -0
- package/dist/fronds/scaffold/services/ProjectWriter.js.map +1 -0
- package/dist/src/bin.d.ts.map +1 -0
- package/dist/src/bin.js +11 -0
- package/dist/src/bin.js.map +1 -0
- package/dist/src/bridge.d.ts.map +1 -0
- package/dist/src/bridge.js.map +1 -0
- package/dist/src/completion.d.ts.map +1 -0
- package/dist/src/completion.js.map +1 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/loader.d.ts.map +1 -0
- package/dist/src/loader.js.map +1 -0
- package/dist/src/machine.d.ts.map +1 -0
- package/dist/src/machine.js.map +1 -0
- package/dist/src/main.d.ts +2 -0
- package/dist/src/main.d.ts.map +1 -0
- package/dist/src/main.js +19 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/runner.d.ts.map +1 -0
- package/dist/{runner.js → src/runner.js} +22 -22
- package/dist/src/runner.js.map +1 -0
- package/dist/src/theme.d.ts.map +1 -0
- package/dist/src/theme.js.map +1 -0
- package/dist/src/typescript/EntityTypes.d.ts.map +1 -0
- package/dist/src/typescript/EntityTypes.js.map +1 -0
- package/dist/src/typescript/FacadeTypes.d.ts.map +1 -0
- package/dist/src/typescript/FacadeTypes.js.map +1 -0
- package/dist/src/typescript/syntax.d.ts.map +1 -0
- package/dist/src/typescript/syntax.js.map +1 -0
- package/dist/{ui.d.ts → src/ui.d.ts} +0 -1
- package/dist/src/ui.d.ts.map +1 -0
- package/dist/{ui.js → src/ui.js} +24 -22
- package/dist/src/ui.js.map +1 -0
- package/package.json +23 -27
- package/app/commands/BuildCommand.ts +0 -43
- package/app/commands/CallCommand.ts +0 -70
- package/app/commands/CheckCommand.ts +0 -70
- package/app/commands/CompletionCommand.ts +0 -24
- package/app/commands/DevtoolsCommand.ts +0 -92
- package/app/commands/ExplainCommand.ts +0 -136
- package/app/commands/FreezeCommand.ts +0 -119
- package/app/commands/GrantCommand.ts +0 -46
- package/app/commands/GraphCommand.ts +0 -91
- package/app/commands/KeysCommand.ts +0 -57
- package/app/commands/LoadCommand.ts +0 -46
- package/app/commands/MigrateCommand.ts +0 -61
- package/app/commands/NewCommand.ts +0 -137
- package/app/commands/ServeCommand.ts +0 -108
- package/dist/bin.d.ts.map +0 -1
- package/dist/bin.js +0 -64
- package/dist/bin.js.map +0 -1
- package/dist/bridge.d.ts.map +0 -1
- package/dist/bridge.js.map +0 -1
- package/dist/completion.d.ts.map +0 -1
- package/dist/completion.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js.map +0 -1
- package/dist/machine.d.ts.map +0 -1
- package/dist/machine.js.map +0 -1
- package/dist/runner.d.ts.map +0 -1
- package/dist/runner.js.map +0 -1
- package/dist/theme.d.ts.map +0 -1
- package/dist/theme.js.map +0 -1
- package/dist/typescript/EntityTypes.d.ts.map +0 -1
- package/dist/typescript/EntityTypes.js.map +0 -1
- package/dist/typescript/FacadeTypes.d.ts.map +0 -1
- package/dist/typescript/FacadeTypes.js.map +0 -1
- package/dist/typescript/syntax.d.ts.map +0 -1
- package/dist/typescript/syntax.js.map +0 -1
- package/dist/ui.d.ts.map +0 -1
- package/dist/ui.js.map +0 -1
- package/fronds/analysis/entities/Build.ts +0 -8
- package/fronds/analysis/entities/Check.ts +0 -6
- package/fronds/analysis/entities/Devtools.ts +0 -8
- package/fronds/analysis/entities/Explain.ts +0 -9
- package/fronds/analysis/entities/Freeze.ts +0 -8
- package/fronds/analysis/entities/Graph.ts +0 -7
- package/fronds/analysis/entities/Load.ts +0 -8
- package/fronds/analysis/entities/Migrate.ts +0 -8
- package/fronds/analysis/handlers/BuildHandler.ts +0 -87
- package/fronds/analysis/handlers/CheckHandler.ts +0 -217
- package/fronds/analysis/handlers/DevtoolsHandler.ts +0 -97
- package/fronds/analysis/handlers/ExplainHandler.ts +0 -263
- package/fronds/analysis/handlers/FreezeHandler.ts +0 -183
- package/fronds/analysis/handlers/GraphHandler.ts +0 -38
- package/fronds/analysis/handlers/LoadHandler.ts +0 -44
- package/fronds/analysis/handlers/MigrateHandler.ts +0 -110
- package/fronds/analysis/services/ProjectScan.ts +0 -55
- package/fronds/analysis/versions.ts +0 -59
- package/fronds/scaffold/entities/BuildFrond.ts +0 -5
- package/fronds/scaffold/entities/Completion.ts +0 -5
- package/fronds/scaffold/entities/New.ts +0 -19
- package/fronds/scaffold/entities/Serve.ts +0 -8
- package/fronds/scaffold/entities/Sync.ts +0 -6
- package/fronds/scaffold/handlers/BuildFrondHandler.ts +0 -96
- package/fronds/scaffold/handlers/SyncHandler.ts +0 -376
- package/fronds/scaffold/services/ProjectWriter.ts +0 -339
- package/fronds/shims.d.ts +0 -9
- package/src/bin.ts +0 -77
- package/src/bridge.ts +0 -118
- package/src/completion.ts +0 -145
- package/src/index.ts +0 -1
- package/src/loader.ts +0 -28
- package/src/machine.ts +0 -17
- package/src/runner.ts +0 -152
- package/src/theme.ts +0 -19
- package/src/typescript/EntityTypes.ts +0 -92
- package/src/typescript/FacadeTypes.ts +0 -50
- package/src/typescript/syntax.ts +0 -11
- package/src/ui.ts +0 -142
- /package/dist/{bin.d.ts → src/bin.d.ts} +0 -0
- /package/dist/{bridge.d.ts → src/bridge.d.ts} +0 -0
- /package/dist/{bridge.js → src/bridge.js} +0 -0
- /package/dist/{completion.d.ts → src/completion.d.ts} +0 -0
- /package/dist/{completion.js → src/completion.js} +0 -0
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/{index.js → src/index.js} +0 -0
- /package/dist/{loader.d.ts → src/loader.d.ts} +0 -0
- /package/dist/{loader.js → src/loader.js} +0 -0
- /package/dist/{machine.d.ts → src/machine.d.ts} +0 -0
- /package/dist/{machine.js → src/machine.js} +0 -0
- /package/dist/{runner.d.ts → src/runner.d.ts} +0 -0
- /package/dist/{theme.d.ts → src/theme.d.ts} +0 -0
- /package/dist/{theme.js → src/theme.js} +0 -0
- /package/dist/{typescript → src/typescript}/EntityTypes.d.ts +0 -0
- /package/dist/{typescript → src/typescript}/EntityTypes.js +0 -0
- /package/dist/{typescript → src/typescript}/FacadeTypes.d.ts +0 -0
- /package/dist/{typescript → src/typescript}/FacadeTypes.js +0 -0
- /package/dist/{typescript → src/typescript}/syntax.d.ts +0 -0
- /package/dist/{typescript → src/typescript}/syntax.js +0 -0
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { Shapes } from '@fougere/schema';
|
|
2
|
-
import safeRegex from 'safe-regex';
|
|
3
|
-
import {
|
|
4
|
-
buildGraph,
|
|
5
|
-
clusterEntities,
|
|
6
|
-
resolveEffectiveOperations,
|
|
7
|
-
type Diagnostic,
|
|
8
|
-
} from '@fougere/core';
|
|
9
|
-
import { adaptersOf, crossFrondImports, handlerDeclarations, outsideConventions } from '@fougere/compiler';
|
|
10
|
-
import { resolveConventions } from '@fougere/core';
|
|
11
|
-
import ProjectScan from '../services/ProjectScan.js';
|
|
12
|
-
import { statSync } from 'node:fs';
|
|
13
|
-
import { dirname, join } from 'node:path';
|
|
14
|
-
import { FACADE_OUT, DEFAULT_OUT } from './BuildHandler.js';
|
|
15
|
-
import { remotesOf } from '@fougere/core/node';
|
|
16
|
-
|
|
17
|
-
export interface CheckResult {
|
|
18
|
-
fronds: number;
|
|
19
|
-
handlers: number;
|
|
20
|
-
findings: Diagnostic[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Shared with `fougere graph` — one threshold, so the two never disagree. */
|
|
24
|
-
const DOMAIN_SPLIT_MIN_ENTITIES = 6;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* What does not hold in this app — derived from its declarations, not from tests.
|
|
28
|
-
*
|
|
29
|
-
* It scans rather than boots — see `ProjectScan`, which states what that costs.
|
|
30
|
-
*
|
|
31
|
-
* It raises no operation-resolution rule of its own: those findings come from the same
|
|
32
|
-
* EffectiveOperation resolver boot consumes. The remaining findings come from their
|
|
33
|
-
* existing owners — scan diagnostics, the import reader and domain clustering. A second
|
|
34
|
-
* opinion here would report what the runtime does not serve, precisely what a checker
|
|
35
|
-
* must avoid.
|
|
36
|
-
*
|
|
37
|
-
* A rule about an ABSENCE is only sound if the analysis attests it looked, which is
|
|
38
|
-
* why the scan's diagnostics had to exist before this command could.
|
|
39
|
-
*/
|
|
40
|
-
export default class CheckHandler {
|
|
41
|
-
constructor(private projectScan: ProjectScan) {}
|
|
42
|
-
|
|
43
|
-
/** Report what does not hold in a Fougere app, without booting it. */
|
|
44
|
-
async execute(input: { root?: string }): Promise<CheckResult> {
|
|
45
|
-
const { fronds, diagnostics, config, root } = await this.projectScan.at(input.root);
|
|
46
|
-
// This is the same pure resolution the boot consumes. No app lifecycle, database,
|
|
47
|
-
// migration, seed or adapter mount is needed for a global semantic check.
|
|
48
|
-
const model = resolveEffectiveOperations(fronds, {
|
|
49
|
-
diagnostics,
|
|
50
|
-
remotes: remotesOf(config),
|
|
51
|
-
adapters: config.adapters,
|
|
52
|
-
});
|
|
53
|
-
const findings: Diagnostic[] = [...model.diagnostics];
|
|
54
|
-
const handlers = fronds.reduce((count, frond) => count + frond.handlers.length, 0);
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Entity groups with no `ref()` between them, in one frond. Reported as a FACT, not a
|
|
58
|
-
* verdict: it means either a missing relation or a frond that has not been named, and
|
|
59
|
-
* nothing here can tell which. `fougere graph` shows the groups.
|
|
60
|
-
*/
|
|
61
|
-
if (fronds.length === 1) {
|
|
62
|
-
// `clusterEntities`, not `suggestSplit`: the latter invents a cut on a connected
|
|
63
|
-
// graph, which is its job and not this question. Here only real components count.
|
|
64
|
-
const nodes = buildGraph(fronds);
|
|
65
|
-
const clusters = clusterEntities(nodes);
|
|
66
|
-
// Same threshold as `fougere graph`'s own tip: below it, two components are a small
|
|
67
|
-
// app that has not grown its links yet, not a frond holding two domains.
|
|
68
|
-
if (clusters.length > 1 && nodes.size >= DOMAIN_SPLIT_MIN_ENTITIES) {
|
|
69
|
-
findings.push({
|
|
70
|
-
severity: 'warning',
|
|
71
|
-
code: 'frond-holds-several-domains',
|
|
72
|
-
filePath: fronds[0].source.path,
|
|
73
|
-
message: `'${fronds[0].name}': ${clusters.length} entity groups with no relation between `
|
|
74
|
-
+ `them (${clusters.map((c) => c.name).join(', ')}). Either a relation is missing, or `
|
|
75
|
-
+ `they are separate fronds — see \`fougere graph\`.`,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* An entity addressing an adapter this project does not depend on. Judged HERE and
|
|
82
|
-
* not at boot: a process only knows the adapters it loaded, so an entity stating a
|
|
83
|
-
* Postgres column type in an app running on `adapter/memory` is indistinguishable
|
|
84
|
-
* from a typo. A project's dependencies name every adapter it could ever load.
|
|
85
|
-
*
|
|
86
|
-
* Silent when the project depends on none — there would be nothing to compare against,
|
|
87
|
-
* and reporting every key as unknown is worse than saying nothing.
|
|
88
|
-
*/
|
|
89
|
-
const known = await adaptersOf(root);
|
|
90
|
-
if (known.length > 0) {
|
|
91
|
-
for (const frond of fronds) {
|
|
92
|
-
for (const entity of frond.entities) {
|
|
93
|
-
for (const name of Object.keys(entity.entityClass.getAdapters() ?? {})) {
|
|
94
|
-
if (known.includes(name)) continue;
|
|
95
|
-
findings.push({
|
|
96
|
-
severity: 'warning',
|
|
97
|
-
code: 'unknown-adapter',
|
|
98
|
-
filePath: entity.filePath,
|
|
99
|
-
subject: entity.name,
|
|
100
|
-
message: `states \`adapters: { ${name} }\`, and this project depends on no adapter `
|
|
101
|
-
+ `that answers to it. It depends on ${known.join(', ')}. Nothing reads the entry, `
|
|
102
|
-
+ 'so it is inert — a typo, or a dependency that was never added.',
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* A `pattern` that backtracks super-linearly. The pattern is fixed at declaration and
|
|
111
|
-
* the string tested against it is not: whoever calls the facade chooses it, so the cost
|
|
112
|
-
* of one match is theirs to set. Judged HERE because a regex says the same thing on
|
|
113
|
-
* every run — checking it per row would pay for a verdict that never changes.
|
|
114
|
-
*
|
|
115
|
-
* `safe-regex` reads the star height, which is the common shape and not every one:
|
|
116
|
-
* `(a|a)*b` backtracks too and passes. Reported as a suspicion, never as a refusal.
|
|
117
|
-
*/
|
|
118
|
-
for (const frond of fronds) {
|
|
119
|
-
for (const entity of frond.entities) {
|
|
120
|
-
for (const [key, field] of Object.entries(entity.entityClass.getFields())) {
|
|
121
|
-
for (const pattern of Shapes.patterns(field.shape)) {
|
|
122
|
-
if (safeRegex(pattern)) continue;
|
|
123
|
-
findings.push({
|
|
124
|
-
severity: 'warning',
|
|
125
|
-
code: 'super-linear-pattern',
|
|
126
|
-
filePath: entity.filePath,
|
|
127
|
-
subject: `${entity.name}.${key}`,
|
|
128
|
-
message: `states \`pattern: ${JSON.stringify(pattern)}\`, whose nested repetition `
|
|
129
|
-
+ 'backtracks super-linearly. A value a caller chooses can hold the event loop '
|
|
130
|
-
+ 'for seconds — rewrite the repeated group, or bound the field with `max`.',
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* A warning, not a refusal: it resolves today and the app runs. What it costs is
|
|
139
|
-
* paid once, late — the day the frond it reaches into is deployed on its own.
|
|
140
|
-
*/
|
|
141
|
-
for (const reach of await crossFrondImports(fronds)) {
|
|
142
|
-
findings.push({
|
|
143
|
-
severity: 'warning',
|
|
144
|
-
code: reach.rule,
|
|
145
|
-
filePath: reach.filePath,
|
|
146
|
-
message: reach.message,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const conventions = resolveConventions(config.conventions);
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* What no convention names. Reported before the declarations below, because a frond
|
|
154
|
-
* with nowhere to put a word is why the word ends up in a handler.
|
|
155
|
-
*/
|
|
156
|
-
for (const unplaced of await outsideConventions(fronds, conventions)) {
|
|
157
|
-
findings.push({
|
|
158
|
-
severity: 'warning',
|
|
159
|
-
code: unplaced.rule,
|
|
160
|
-
filePath: unplaced.filePath,
|
|
161
|
-
subject: unplaced.frond,
|
|
162
|
-
message: unplaced.message,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* The same shape of warning, one file lower: what the handler holds still runs, and
|
|
168
|
-
* what it costs is the next caller — a test, a second operation, another handler —
|
|
169
|
-
* that cannot reach it without moving it first.
|
|
170
|
-
*/
|
|
171
|
-
for (const held of await handlerDeclarations(fronds, conventions)) {
|
|
172
|
-
findings.push({
|
|
173
|
-
severity: 'warning',
|
|
174
|
-
code: held.rule,
|
|
175
|
-
filePath: held.filePath,
|
|
176
|
-
subject: `${held.handler}.${held.subject}`,
|
|
177
|
-
message: held.message,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
findings.push(...facadeFindings(root, fronds));
|
|
182
|
-
|
|
183
|
-
return { fronds: fronds.length, handlers, findings };
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* A facade module older than the handlers it was read off.
|
|
189
|
-
*
|
|
190
|
-
* An ABSENT module needs no finding: a page importing one fails to resolve, loudly, and a
|
|
191
|
-
* backend project never imports one at all — warning there would fire on every project that
|
|
192
|
-
* does not want a facade, which is most of them.
|
|
193
|
-
*
|
|
194
|
-
* A STALE one is the silent case. The import resolves, the operations are the old ones, and a
|
|
195
|
-
* renamed op compiles against a name nothing serves any more. An mtime is the whole check.
|
|
196
|
-
*/
|
|
197
|
-
function facadeFindings(root: string, fronds: readonly { handlers: readonly { filePath: string }[] }[]): Diagnostic[] {
|
|
198
|
-
const facade = join(root, dirname(DEFAULT_OUT), FACADE_OUT);
|
|
199
|
-
const written = statSync(facade, { throwIfNoEntry: false })?.mtimeMs;
|
|
200
|
-
if (written === undefined) return [];
|
|
201
|
-
|
|
202
|
-
const newer = fronds
|
|
203
|
-
.flatMap((frond) => frond.handlers)
|
|
204
|
-
.filter((handler) => (statSync(handler.filePath, { throwIfNoEntry: false })?.mtimeMs ?? 0) > written);
|
|
205
|
-
|
|
206
|
-
if (newer.length === 0) return [];
|
|
207
|
-
|
|
208
|
-
return [{
|
|
209
|
-
severity: 'warning',
|
|
210
|
-
code: 'facade-stale',
|
|
211
|
-
filePath: facade,
|
|
212
|
-
subject: `${newer.length} handler${newer.length > 1 ? 's' : ''}`,
|
|
213
|
-
message: 'The facade module is older than the handlers it was read off, so a page checks its '
|
|
214
|
-
+ 'calls against operations that may no longer exist. Run `fougere build`.',
|
|
215
|
-
}];
|
|
216
|
-
}
|
|
217
|
-
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { createHttpTransport } from '@fougere/transport-http/client';
|
|
2
|
-
import type { CallPage, CallRecord } from '@fougere/core';
|
|
3
|
-
import ProjectScan from '../services/ProjectScan.js';
|
|
4
|
-
import { remotesOf } from '@fougere/core/node';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Where an app answers in development — Nuxt, Next and the site all sit there, so it is a
|
|
8
|
-
* convention and not a setting. A port belongs to the HOST, though, and this project states
|
|
9
|
-
* none: an app served anywhere else is named the way `FOUGERE_LOG_LEVEL` names a level the
|
|
10
|
-
* file did not, and `--url` still wins over both.
|
|
11
|
-
*/
|
|
12
|
-
const LOCAL = 'http://127.0.0.1:3000';
|
|
13
|
-
|
|
14
|
-
function local(): string {
|
|
15
|
-
return trimmed(process.env.FOUGERE_URL || LOCAL);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** One address that was asked, and what came back from it. */
|
|
19
|
-
export interface CallSource {
|
|
20
|
-
url: string;
|
|
21
|
-
/** The frond `remotes:` names at this address, when that is where it came from. */
|
|
22
|
-
frond?: string;
|
|
23
|
-
cursor: number;
|
|
24
|
-
inFlight: number;
|
|
25
|
-
dropped: number;
|
|
26
|
-
/** Present instead of the numbers when the address did not answer, or serves no log. */
|
|
27
|
-
refusal?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Every app of this project, read at once.
|
|
32
|
-
*
|
|
33
|
-
* A reader pulls: no app registers anywhere, and an app started alone depends on nothing.
|
|
34
|
-
* The price is knowing the addresses, which the project already states — `remotes:` says
|
|
35
|
-
* where a call goes, so it also says where the other half of a call can be watched.
|
|
36
|
-
*/
|
|
37
|
-
export interface CallsView {
|
|
38
|
-
sources: CallSource[];
|
|
39
|
-
/** Every call, oldest first, each carrying the address it was read from. */
|
|
40
|
-
calls: (CallRecord & { source: string })[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default class DevtoolsHandler {
|
|
44
|
-
constructor(private projectScan: ProjectScan) {}
|
|
45
|
-
|
|
46
|
-
/** Read what the running apps have dispatched since a cursor per address. */
|
|
47
|
-
async execute(input: { url?: string; root?: string; since?: Record<string, number> }): Promise<CallsView> {
|
|
48
|
-
const asked = input.url ? [{ url: trimmed(input.url) }] : await this.addresses(input.root);
|
|
49
|
-
const since = input.since ?? {};
|
|
50
|
-
|
|
51
|
-
const read = await Promise.all(asked.map(async (one): Promise<CallSource & { calls: CallRecord[] }> => {
|
|
52
|
-
try {
|
|
53
|
-
const page = await createHttpTransport(one.url)(
|
|
54
|
-
{ entity: 'rpc', op: 'calls' },
|
|
55
|
-
{ params: {}, query: {}, input: { since: since[one.url] ?? 0 }, state: {} },
|
|
56
|
-
) as CallPage;
|
|
57
|
-
|
|
58
|
-
return { ...one, cursor: page.cursor, inFlight: page.inFlight, dropped: page.dropped, calls: page.calls };
|
|
59
|
-
} catch (err) {
|
|
60
|
-
// An address that does not answer is a source with a reason, not a missing source:
|
|
61
|
-
// an app not started and an app without the package are different facts, and both
|
|
62
|
-
// are worth seeing beside the ones that answered.
|
|
63
|
-
return { ...one, cursor: since[one.url] ?? 0, inFlight: 0, dropped: 0, calls: [], refusal: said(err) };
|
|
64
|
-
}
|
|
65
|
-
}));
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
sources: read.map(({ calls: _calls, ...source }) => source),
|
|
69
|
-
calls: read
|
|
70
|
-
.flatMap(({ url, calls }) => calls.map((call) => ({ ...call, source: url })))
|
|
71
|
-
.sort((a, b) => a.startedAt - b.startedAt || a.seq - b.seq),
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* The addresses this project declares, plus the local one.
|
|
77
|
-
*
|
|
78
|
-
* `remotes:` is the operator's statement of where a call goes; reading it here is not a
|
|
79
|
-
* second source of truth, it is the same one read for the other direction.
|
|
80
|
-
*/
|
|
81
|
-
private async addresses(root?: string): Promise<{ url: string; frond?: string }[]> {
|
|
82
|
-
const { config } = await this.projectScan.at(root);
|
|
83
|
-
const here = local();
|
|
84
|
-
const remotes = Object.entries(remotesOf(config))
|
|
85
|
-
.map(([frond, url]) => ({ url: trimmed(url), frond }));
|
|
86
|
-
|
|
87
|
-
return [{ url: here }, ...remotes.filter((one) => one.url !== here)];
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function trimmed(url: string): string {
|
|
92
|
-
return url.replace(/\/$/, '');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function said(err: unknown): string {
|
|
96
|
-
return err instanceof Error ? err.message : String(err);
|
|
97
|
-
}
|
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
addressOf,
|
|
3
|
-
resolveEffectiveOperations,
|
|
4
|
-
type EffectiveOperation,
|
|
5
|
-
type OperationContract,
|
|
6
|
-
} from '@fougere/core';
|
|
7
|
-
import { relative } from 'node:path';
|
|
8
|
-
import ProjectScan from '../services/ProjectScan.js';
|
|
9
|
-
import { ANONYMOUS_SCHEMA_NAME, type SchemaView } from '@fougere/schema';
|
|
10
|
-
import { remotesOf } from '@fougere/core/node';
|
|
11
|
-
|
|
12
|
-
type Cardinality = NonNullable<OperationContract['cardinality']>;
|
|
13
|
-
type Binding = EffectiveOperation['binding'][number];
|
|
14
|
-
|
|
15
|
-
export type ExplainedBinding =
|
|
16
|
-
| { kind: 'collector'; typeName: string }
|
|
17
|
-
| { kind: 'fact'; factName: string }
|
|
18
|
-
| { kind: 'param'; name: string; coerce?: 'number' | 'boolean' }
|
|
19
|
-
| { kind: 'input' }
|
|
20
|
-
| { kind: 'context' }
|
|
21
|
-
| { kind: 'query' };
|
|
22
|
-
|
|
23
|
-
export interface ExplainedParameter {
|
|
24
|
-
name: string;
|
|
25
|
-
type: string | null;
|
|
26
|
-
optional: boolean;
|
|
27
|
-
nullable: boolean;
|
|
28
|
-
undefinable: boolean;
|
|
29
|
-
binding: ExplainedBinding | null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface ExplainedCollector {
|
|
33
|
-
typeName: string;
|
|
34
|
-
class: string;
|
|
35
|
-
file: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Stable, JSON-shaped projection of core's canonical EffectiveOperation. */
|
|
39
|
-
export interface ExplainResult {
|
|
40
|
-
operation: string;
|
|
41
|
-
handler: {
|
|
42
|
-
class: string;
|
|
43
|
-
address: string;
|
|
44
|
-
method: string;
|
|
45
|
-
file: string | null;
|
|
46
|
-
};
|
|
47
|
-
kind: 'query' | 'command';
|
|
48
|
-
description: string | null;
|
|
49
|
-
input: string | null;
|
|
50
|
-
output: { type: string; cardinality: Cardinality | null } | null;
|
|
51
|
-
parameters: ExplainedParameter[];
|
|
52
|
-
collectors: ExplainedCollector[];
|
|
53
|
-
contexts: string[];
|
|
54
|
-
semantics: EffectiveOperation['semantics'];
|
|
55
|
-
exposure: {
|
|
56
|
-
surfaces: string[];
|
|
57
|
-
adapters: string[];
|
|
58
|
-
};
|
|
59
|
-
placement: {
|
|
60
|
-
frond: string;
|
|
61
|
-
runtime: 'local' | 'remote';
|
|
62
|
-
remote: string | null;
|
|
63
|
-
};
|
|
64
|
-
/** Where the work GOES, next to where it answers — and how much of it leaves the process. */
|
|
65
|
-
reach: {
|
|
66
|
-
fronds: { frond: string; runtime: 'local' | 'remote' }[];
|
|
67
|
-
hops: number;
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** What this project serves, when no single operation was named. */
|
|
72
|
-
export interface ExplainListing {
|
|
73
|
-
fronds: {
|
|
74
|
-
name: string;
|
|
75
|
-
runtime: 'local' | 'remote';
|
|
76
|
-
remote: string | null;
|
|
77
|
-
operations: number;
|
|
78
|
-
}[];
|
|
79
|
-
operations: string[];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
interface Selector {
|
|
83
|
-
frond?: string;
|
|
84
|
-
surface?: string;
|
|
85
|
-
address: string;
|
|
86
|
-
op: string;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** Read one operation directly from the model boot and check also consume. */
|
|
90
|
-
export default class ExplainHandler {
|
|
91
|
-
constructor(private projectScan: ProjectScan) {}
|
|
92
|
-
|
|
93
|
-
/** The names this project serves — the list `explain` used to spell in a refusal only. */
|
|
94
|
-
async list(input: { root?: string }): Promise<ExplainListing> {
|
|
95
|
-
const { scan, model } = await this.modelOf(input.root);
|
|
96
|
-
const remotes = remotesOf(scan.config);
|
|
97
|
-
const counted = new Map<string, number>();
|
|
98
|
-
for (const operation of model.operations) {
|
|
99
|
-
const frond = operation.placement.frond;
|
|
100
|
-
counted.set(frond, (counted.get(frond) ?? 0) + 1);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
fronds: scan.fronds.map((frond) => ({
|
|
105
|
-
name: frond.name,
|
|
106
|
-
runtime: remotes[frond.name] ? 'remote' as const : 'local' as const,
|
|
107
|
-
remote: remotes[frond.name] ?? null,
|
|
108
|
-
operations: counted.get(frond.name) ?? 0,
|
|
109
|
-
})).sort((a, b) => a.name.localeCompare(b.name)),
|
|
110
|
-
operations: model.operations.map((operation) => operation.id).sort(),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private async modelOf(root?: string) {
|
|
115
|
-
const scan = await this.projectScan.at(root);
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
scan,
|
|
119
|
-
model: resolveEffectiveOperations(scan.fronds, {
|
|
120
|
-
diagnostics: scan.diagnostics,
|
|
121
|
-
remotes: remotesOf(scan.config),
|
|
122
|
-
adapters: scan.config.adapters,
|
|
123
|
-
}),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** Print the contract one operation will be served under. */
|
|
128
|
-
async execute(input: { operation?: string; root?: string; json?: boolean }): Promise<ExplainResult> {
|
|
129
|
-
const requested = input.operation?.trim();
|
|
130
|
-
if (!requested) throw new Error('Usage: fougere explain <Operation> [--json] [--root <directory>]');
|
|
131
|
-
|
|
132
|
-
const selector = parseSelector(requested);
|
|
133
|
-
const { scan, model } = await this.modelOf(input.root);
|
|
134
|
-
const candidates = model.operations.filter((operation) => matches(operation, selector));
|
|
135
|
-
|
|
136
|
-
if (candidates.length === 0) {
|
|
137
|
-
const failed = model.resolutionDiagnostics.find((diagnostic) =>
|
|
138
|
-
diagnostic.subject?.toLowerCase().includes(`.${selector.op.toLowerCase()}`)
|
|
139
|
-
&& diagnostic.subject.toLowerCase().includes(selector.address.toLowerCase()));
|
|
140
|
-
if (failed) throw new Error(`[${failed.code}] ${failed.message}`);
|
|
141
|
-
|
|
142
|
-
const available = model.operations.map((operation) => operation.id).sort();
|
|
143
|
-
throw new Error(
|
|
144
|
-
`Unknown operation '${requested}'. `
|
|
145
|
-
+ (available.length > 0
|
|
146
|
-
? `Available operations: ${available.join(', ')}.`
|
|
147
|
-
: 'This project exposes no resolved operation.'),
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (candidates.length > 1) {
|
|
152
|
-
throw new Error(
|
|
153
|
-
`Ambiguous operation '${requested}'. Matches: `
|
|
154
|
-
+ `${candidates.map((operation) => operation.id).sort().join(', ')}. `
|
|
155
|
-
+ 'Use one of these qualified names.',
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const selected = candidates[0]!;
|
|
160
|
-
const invalid = model.resolutionDiagnostics.find((diagnostic) =>
|
|
161
|
-
diagnostic.subject?.startsWith(`${selected.handler.className}.${selected.name}`));
|
|
162
|
-
if (invalid) throw new Error(`[${invalid.code}] ${invalid.message}`);
|
|
163
|
-
|
|
164
|
-
return project(selected, scan.root);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function project(operation: EffectiveOperation, root: string): ExplainResult {
|
|
169
|
-
return {
|
|
170
|
-
operation: operation.operation,
|
|
171
|
-
handler: {
|
|
172
|
-
class: operation.implementation.className,
|
|
173
|
-
address: operation.handler.address,
|
|
174
|
-
method: operation.implementation.method,
|
|
175
|
-
file: operation.implementation.filePath
|
|
176
|
-
? relative(root, operation.implementation.filePath)
|
|
177
|
-
: null,
|
|
178
|
-
},
|
|
179
|
-
kind: operation.kind,
|
|
180
|
-
description: operation.description ?? null,
|
|
181
|
-
input: inputTypeOf(operation),
|
|
182
|
-
output: outputOf(operation),
|
|
183
|
-
parameters: operation.parameters.map((parameter) => ({
|
|
184
|
-
name: parameter.name,
|
|
185
|
-
type: parameter.type,
|
|
186
|
-
optional: parameter.optional,
|
|
187
|
-
nullable: parameter.nullable,
|
|
188
|
-
undefinable: parameter.undefinable,
|
|
189
|
-
binding: bindingOf(parameter.binding),
|
|
190
|
-
})),
|
|
191
|
-
collectors: operation.collectors.map((collector) => ({
|
|
192
|
-
typeName: collector.typeName,
|
|
193
|
-
class: collector.className,
|
|
194
|
-
file: relative(root, collector.filePath),
|
|
195
|
-
})),
|
|
196
|
-
contexts: operation.contexts,
|
|
197
|
-
semantics: operation.semantics,
|
|
198
|
-
exposure: operation.exposure,
|
|
199
|
-
placement: {
|
|
200
|
-
frond: operation.placement.frond,
|
|
201
|
-
runtime: operation.placement.runtime,
|
|
202
|
-
remote: operation.placement.remote ?? null,
|
|
203
|
-
},
|
|
204
|
-
reach: operation.reach,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function parseSelector(value: string): Selector {
|
|
209
|
-
const dot = value.lastIndexOf('.');
|
|
210
|
-
if (dot <= 0 || dot === value.length - 1) {
|
|
211
|
-
throw new Error(`Invalid operation '${value}'. Expected '<Handler>.<method>', for example 'Post.publish'.`);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const path = value.slice(0, dot).split('/').filter(Boolean);
|
|
215
|
-
if (path.length === 0 || path.length > 3) {
|
|
216
|
-
throw new Error(`Invalid operation '${value}'. Expected 'Post.publish', 'blog/Post.publish', or 'blog/public/Post.publish'.`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const qualifiers = path.slice(0, -1);
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
...(qualifiers[0] ? { frond: qualifiers[0] } : {}),
|
|
223
|
-
...(qualifiers[1] ? { surface: qualifiers[1] } : {}),
|
|
224
|
-
address: addressOf(path.at(-1)!),
|
|
225
|
-
op: value.slice(dot + 1),
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function matches(operation: EffectiveOperation, selector: Selector): boolean {
|
|
230
|
-
return operation.handler.address.toLowerCase() === selector.address.toLowerCase()
|
|
231
|
-
&& operation.name === selector.op
|
|
232
|
-
&& (!selector.frond || operation.placement.frond === selector.frond)
|
|
233
|
-
&& (!selector.surface || operation.exposure.surfaces.includes(selector.surface));
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function inputTypeOf(operation: EffectiveOperation): string | null {
|
|
237
|
-
const body = operation.parameters.find((parameter) => parameter.binding.source.kind === 'input');
|
|
238
|
-
|
|
239
|
-
return body?.type ?? schemaName(operation.input) ?? null;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function outputOf(operation: EffectiveOperation): ExplainResult['output'] {
|
|
243
|
-
const type = schemaName(operation.output) ?? parsedOutput(operation.signature?.returnType?.raw);
|
|
244
|
-
|
|
245
|
-
return type ? { type, cardinality: operation.cardinality ?? null } : null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function schemaName(schema: SchemaView | undefined): string | undefined {
|
|
249
|
-
if (!schema) return undefined;
|
|
250
|
-
if (schema.name && schema.name !== ANONYMOUS_SCHEMA_NAME) return schema.name;
|
|
251
|
-
|
|
252
|
-
return schema.derivation?.sourceName;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function parsedOutput(raw: string | undefined): string | undefined {
|
|
256
|
-
if (!raw) return undefined;
|
|
257
|
-
|
|
258
|
-
return raw.startsWith('Promise<') && raw.endsWith('>') ? raw.slice(8, -1) : raw;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function bindingOf(binding: Binding): ExplainedBinding {
|
|
262
|
-
return { ...binding.source } as ExplainedBinding;
|
|
263
|
-
}
|