@shortlink-org/portolan 0.2.4 → 0.4.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/README.md +174 -6
- package/catalog/enum_test.go +46 -0
- package/catalog/evidence_test.go +35 -0
- package/catalog/model.go +1213 -0
- package/catalog/roundtrip_test.go +210 -0
- package/catalog/via_test.go +38 -0
- package/cli/init.test.mjs +6 -1
- package/cli/portolan.mjs +14 -1
- package/cli/portolan.test.mjs +49 -0
- package/go.mod +21 -0
- package/go.sum +34 -0
- package/internal/gocall/README.md +19 -0
- package/internal/gocall/analyze.go +189 -0
- package/internal/gocall/analyze_test.go +107 -0
- package/internal/gohttp/analyze.go +2562 -0
- package/internal/gohttp/destination.go +373 -0
- package/internal/gohttp/endpoints.go +1067 -0
- package/internal/gohttp/roots.go +320 -0
- package/internal/gohttp/typed.go +96 -0
- package/internal/goscan/constants.go +85 -0
- package/internal/goscan/goscan_test.go +227 -0
- package/internal/goscan/index.go +629 -0
- package/internal/goscan/index_test.go +66 -0
- package/internal/goscan/names.go +52 -0
- package/internal/goscan/parse_test.go +11 -0
- package/internal/goscan/source.go +37 -0
- package/internal/goscan/tree.go +284 -0
- package/internal/goscan/types.go +99 -0
- package/internal/wsdl/ids.go +127 -0
- package/internal/wsdl/ids_test.go +21 -0
- package/internal/wsdl/model.go +70 -0
- package/internal/wsdl/parse.go +949 -0
- package/internal/wsdl/parse_test.go +170 -0
- package/package.json +28 -15
- package/plugin/describe.go +156 -0
- package/plugin/describe_test.go +114 -0
- package/plugin/protocol.go +141 -0
- package/plugin/schematest/schematest.go +126 -0
- package/plugins/README.md +404 -50
- package/plugins/cmd/portolan-http-clients/main.go +19 -0
- package/plugins/extract-argocd/options.schema.json +44 -0
- package/plugins/extract-celery/extract.py +0 -2
- package/plugins/extract-celery/extract_test.py +1 -1
- package/plugins/extract-celery/main.py +1 -0
- package/plugins/extract-csharp-ddd/README.md +213 -0
- package/plugins/extract-csharp-ddd/options.schema.json +55 -0
- package/plugins/extract-django/README.md +52 -19
- package/plugins/extract-django/choices.py +47 -0
- package/plugins/extract-django/domain.py +30 -18
- package/plugins/extract-django/extract.py +21 -7
- package/plugins/extract-django/extract_test.py +68 -2
- package/plugins/extract-django/lifecycle.py +4 -28
- package/plugins/extract-django/main.py +1 -0
- package/plugins/extract-django/operations.py +1 -1
- package/plugins/extract-django/routing_test.py +109 -1
- package/plugins/extract-django/rules.py +154 -0
- package/plugins/extract-django/rules_test.py +158 -0
- package/plugins/extract-django/store.py +1 -1
- package/plugins/extract-django/transport.py +101 -55
- package/plugins/extract-django/verbs.py +241 -0
- package/plugins/extract-go/README.md +47 -0
- package/plugins/extract-go-sqs/options.schema.json +21 -0
- package/plugins/extract-http-clients/describe.go +20 -0
- package/plugins/extract-http-clients/describe_test.go +11 -0
- package/plugins/extract-http-clients/extract.go +740 -0
- package/plugins/extract-http-clients/extract_test.go +1561 -0
- package/plugins/extract-http-clients/main.go +41 -0
- package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
- package/plugins/extract-k8s/options.schema.json +32 -0
- package/plugins/extract-laravel/Cargo.lock +962 -0
- package/plugins/extract-laravel/Cargo.toml +20 -0
- package/plugins/extract-laravel/README.md +200 -0
- package/plugins/extract-laravel/options.schema.json +68 -0
- package/plugins/extract-laravel/rustfmt.toml +1 -0
- package/plugins/extract-php-ddd/Cargo.lock +972 -0
- package/plugins/extract-php-ddd/Cargo.toml +22 -0
- package/plugins/extract-php-ddd/README.md +141 -0
- package/plugins/extract-php-ddd/options.schema.json +50 -0
- package/plugins/extract-php-ddd/rustfmt.toml +1 -0
- package/plugins/extract-python-kafka/extract.py +0 -2
- package/plugins/extract-python-kafka/extract_test.py +1 -1
- package/plugins/extract-python-kafka/main.py +1 -0
- package/plugins/extract-terraform/options.schema.json +26 -0
- package/plugins/extract-ts/extract.test.ts +2 -2
- package/plugins/extract-ts/extract.ts +4 -5
- package/plugins/extract-ts/graphql.test.ts +1 -1
- package/plugins/extract-ts/main.ts +1 -0
- package/plugins/openapi/ids.go +261 -0
- package/plugins/openapi/ids_test.go +98 -0
- package/plugins/phpscan/Cargo.lock +953 -0
- package/plugins/phpscan/Cargo.toml +19 -0
- package/plugins/phpscan/rustfmt.toml +1 -0
- package/plugins/portolan-go.wasm +0 -0
- package/plugins/pyplugin/catalog.py +24 -1
- package/plugins/pyplugin/protocol.py +1 -5
- package/plugins/verify-otel/options.schema.json +12 -0
- package/portolan.json +132 -4
- package/rules/builtin.json +218 -0
- package/schema/portolan.schema.json +905 -4
- package/scripts/README.md +21 -13
- package/scripts/catalog-sources.mjs +6 -0
- package/scripts/delivery-presets.mjs +57 -14
- package/scripts/diff.mjs +5 -1
- package/scripts/django-aggregates.test.mjs +58 -0
- package/scripts/gen-likec4.mjs +150 -17
- package/scripts/gen-likec4.test.mjs +96 -0
- package/scripts/gen.mjs +148 -118
- package/scripts/gitops-example.test.mjs +108 -0
- package/scripts/go-discovery.test.mjs +30 -0
- package/scripts/history.mjs +186 -3
- package/scripts/history.test.mjs +1 -1
- package/scripts/host-plugins/fetch-argocd.mjs +338 -0
- package/scripts/host-plugins/fetch-argocd.options.json +49 -0
- package/scripts/host-plugins/fetch-argocd.test.mjs +274 -0
- package/scripts/host-plugins/fetch-bsr.mjs +1 -0
- package/scripts/host-plugins/fetch-csr.mjs +1 -0
- package/scripts/host-plugins/fetch-git.mjs +78 -21
- package/scripts/host-plugins/fetch-git.test.mjs +62 -8
- package/scripts/host-plugins/fetch-k8s.mjs +263 -0
- package/scripts/host-plugins/fetch-k8s.options.json +50 -0
- package/scripts/host-plugins/fetch-k8s.test.mjs +259 -0
- package/scripts/host-plugins/k8s-topology.mjs +183 -0
- package/scripts/json-format.mjs +192 -0
- package/scripts/json-format.test.mjs +97 -0
- package/scripts/local-api.mjs +287 -12
- package/scripts/local-api.test.mjs +150 -5
- package/scripts/local-discovery.mjs +94 -9
- package/scripts/manifest.mjs +14 -3
- package/scripts/manifest.test.mjs +24 -0
- package/scripts/output-diff.mjs +94 -0
- package/scripts/output-diff.test.mjs +36 -0
- package/scripts/package-smoke.mjs +62 -4
- package/scripts/plugin-host.mjs +45 -3
- package/scripts/plugin-host.test.mjs +9 -0
- package/scripts/plugin-wasm-worker.mjs +4 -1
- package/scripts/plugins-fresh.mjs +97 -0
- package/scripts/plugins-fresh.test.mjs +64 -0
- package/scripts/provenance.mjs +72 -0
- package/scripts/provenance.test.mjs +149 -0
- package/scripts/run-builtin.mjs +62 -7
- package/scripts/schema.mjs +167 -19
- package/scripts/trace-trials.mjs +176 -0
- package/scripts/trace-trials.test.mjs +142 -0
- package/scripts/warning-policy.mjs +167 -0
- package/scripts/warning-policy.test.mjs +93 -0
- package/src/app/Breadcrumbs.test.ts +3 -0
- package/src/app/Breadcrumbs.tsx +3 -0
- package/src/app/CatalogApp.tsx +2 -0
- package/src/app/Sidebar.tsx +3 -3
- package/src/app/SidebarFooter.tsx +20 -4
- package/src/catalog-docs.test.ts +64 -0
- package/src/catalog-docs.ts +35 -0
- package/src/catalog-error.test.ts +15 -0
- package/src/catalog-index.ts +25 -0
- package/src/catalog-model.ts +293 -5
- package/src/catalog-validation.ts +113 -2
- package/src/catalog.test.ts +40 -1
- package/src/chat/Starter.tsx +5 -11
- package/src/chat/tools.test.ts +27 -0
- package/src/chat/tools.ts +5 -9
- package/src/components/CatalogStamp.tsx +10 -8
- package/src/components/ChannelRows.test.tsx +76 -1
- package/src/components/ChannelRows.tsx +223 -60
- package/src/components/DeploymentRows.tsx +131 -0
- package/src/components/DocsLinks.test.tsx +27 -0
- package/src/components/DocsLinks.tsx +56 -0
- package/src/components/FieldTree.tsx +5 -2
- package/src/components/HTTPDestinationEvidence.test.tsx +23 -0
- package/src/components/HTTPDestinationEvidence.tsx +31 -0
- package/src/components/Integrations.tsx +1 -1
- package/src/components/MachineDocs.tsx +6 -5
- package/src/components/MethodRows.tsx +9 -2
- package/src/components/PluginIcon.tsx +77 -0
- package/src/components/ProblemRow.tsx +173 -153
- package/src/components/RelationEvidence.test.tsx +14 -0
- package/src/components/RelationEvidence.tsx +53 -0
- package/src/components/RuleMarks.tsx +22 -0
- package/src/components/ShapeRows.tsx +24 -20
- package/src/data.ts +46 -7
- package/src/enrich.test.ts +459 -4
- package/src/enrich.ts +308 -7
- package/src/er/ErCanvas.tsx +218 -12
- package/src/er/GroupNode.tsx +57 -0
- package/src/er/StoreHeader.tsx +1 -0
- package/src/er/layout.test.ts +85 -2
- package/src/er/layout.ts +140 -5
- package/src/er/spec.test.ts +17 -0
- package/src/er/spec.ts +23 -10
- package/src/flow/Recordings.test.tsx +52 -0
- package/src/flow/Recordings.tsx +236 -0
- package/src/flow/StepDetail.tsx +59 -0
- package/src/flow/TraceTrial.tsx +419 -0
- package/src/flow/evidence.test.ts +16 -0
- package/src/flow/evidence.ts +34 -0
- package/src/flow/examples.test.ts +33 -0
- package/src/flow/examples.ts +37 -0
- package/src/flow/outline.test.ts +28 -0
- package/src/flow/outline.ts +7 -2
- package/src/flow/trace-trial-resume.test.ts +42 -0
- package/src/flow/trace-trial-resume.ts +74 -0
- package/src/graph/elk.ts +78 -0
- package/src/index.css +68 -0
- package/src/landing/DraggableReveal.tsx +3 -2
- package/src/landing/EvidencePipeline.tsx +105 -0
- package/src/landing/LandingPage.tsx +17 -70
- package/src/landing/ProductTour.tsx +6 -6
- package/src/lib/all-problems.ts +27 -17
- package/src/lib/catalog-diff.ts +1 -1
- package/src/lib/centrality.test.ts +251 -0
- package/src/lib/centrality.ts +232 -0
- package/src/lib/confluence.test.ts +37 -0
- package/src/lib/confluence.ts +41 -0
- package/src/lib/context-color.ts +1 -1
- package/src/lib/deployment-drift.ts +22 -0
- package/src/lib/derive.ts +43 -69
- package/src/lib/django-aggregates.d.mts +9 -0
- package/src/lib/django-aggregates.mjs +36 -0
- package/src/lib/django-aggregates.test.ts +29 -0
- package/src/lib/django-aggregates.ts +5 -0
- package/src/lib/environments.test.ts +83 -0
- package/src/lib/environments.ts +57 -0
- package/src/lib/integration-url.test.ts +30 -0
- package/src/lib/integration-url.ts +63 -0
- package/src/lib/kafka-ui.ts +3 -45
- package/src/lib/local-api.ts +116 -4
- package/src/lib/notion.ts +13 -0
- package/src/lib/plugin-index.json +2765 -0
- package/src/lib/plugins.test.ts +68 -0
- package/src/lib/plugins.ts +264 -0
- package/src/lib/problem-flows.test.ts +61 -0
- package/src/lib/problem-flows.ts +78 -0
- package/src/lib/problem-rules-cel.d.mts +43 -0
- package/src/lib/problem-rules-cel.mjs +407 -0
- package/src/lib/problem-rules.test.ts +287 -0
- package/src/lib/problem-rules.ts +271 -0
- package/src/lib/problem-subjects.ts +737 -0
- package/src/lib/rule-entries.ts +39 -0
- package/src/lib/{data-problems.test.ts → rules-data.test.ts} +18 -16
- package/src/lib/rules-deploy.test.ts +133 -0
- package/src/lib/{problems.test.ts → rules-edges.test.ts} +8 -3
- package/src/lib/{proto-problems.test.ts → rules-proto.test.ts} +9 -25
- package/src/lib/{wire-problems.test.ts → rules-wire.test.ts} +13 -11
- package/src/lib/rules.test.ts +51 -0
- package/src/lib/rules.ts +86 -0
- package/src/lib/setup-info.test.ts +17 -0
- package/src/lib/setup-info.ts +58 -0
- package/src/lib/shape.test.ts +32 -0
- package/src/lib/shape.ts +30 -6
- package/src/lib/tech.ts +16 -0
- package/src/lib/trace-project.test.ts +34 -0
- package/src/lib/trace-project.ts +50 -0
- package/src/lib/use-problems.ts +23 -0
- package/src/lib/warnings.test.ts +63 -0
- package/src/lib/warnings.ts +260 -0
- package/src/likec4/ids.test.ts +6 -2
- package/src/likec4/ids.ts +43 -0
- package/src/main.tsx +23 -0
- package/src/map/ContextMapGraph.tsx +76 -32
- package/src/merge-deployments.test.ts +127 -0
- package/src/merge.test.ts +82 -0
- package/src/merge.ts +189 -18
- package/src/pages/AggregatePage.tsx +65 -14
- package/src/pages/ContextMap.tsx +45 -3
- package/src/pages/ContextPage.tsx +8 -5
- package/src/pages/EventPage.tsx +15 -5
- package/src/pages/FlowDetail.tsx +23 -2
- package/src/pages/GraphPage.tsx +40 -3
- package/src/pages/Overview.tsx +152 -12
- package/src/pages/PluginIndex.tsx +190 -0
- package/src/pages/Problems.tsx +396 -128
- package/src/pages/ServicePage.tsx +62 -5
- package/src/pages/Settings.tsx +217 -43
- package/src/pages/settings/AboutSettings.tsx +8 -1
- package/src/pages/settings/DjangoAggregateChoices.tsx +79 -0
- package/src/pages/settings/IntegrationsSettings.tsx +63 -17
- package/src/pages/settings/RecordingSettings.tsx +138 -0
- package/src/pages/settings/RulesSettings.tsx +825 -0
- package/src/routes.test.ts +9 -0
- package/src/routes.ts +22 -1
- package/src/selection/DetailPanel.tsx +15 -0
- package/src/virtual-provenance.d.ts +11 -0
- package/vite.config.ts +5 -0
- package/scripts/vendor-lock.mjs +0 -58
- package/scripts/vendor-lock.test.mjs +0 -69
- package/src/lib/data-problems.ts +0 -314
- package/src/lib/proto-problems.ts +0 -237
- package/src/lib/wire-problems.ts +0 -342
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// A recording's trial, remembered across a reload.
|
|
2
|
+
//
|
|
3
|
+
// Keeping a recording writes portolan.json and then regenerates the catalog,
|
|
4
|
+
// and each of those reloads the page in development - the manifest and the
|
|
5
|
+
// sources are imported, not fetched. The runs go on in the server, which
|
|
6
|
+
// keeps every run's events and replays them to whoever asks; what is lost is
|
|
7
|
+
// the dialog watching them. So the page writes down which run it was
|
|
8
|
+
// watching, and the dialog picks the run back up when the page comes back.
|
|
9
|
+
|
|
10
|
+
export interface TraceTrialMemory {
|
|
11
|
+
/** The trial run the dialog was opened on. */
|
|
12
|
+
runId: string;
|
|
13
|
+
/** The write run started by keeping the recording, once there is one. */
|
|
14
|
+
writeRunId: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The storage this reads and writes: sessionStorage in the browser, anything shaped like it in a test. */
|
|
18
|
+
export interface TrialStorage {
|
|
19
|
+
getItem(key: string): string | null;
|
|
20
|
+
setItem(key: string, value: string): void;
|
|
21
|
+
removeItem(key: string): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const KEY = "portolan.trace-trial.v1";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `scope` says which page was watching: a flow's slug, or "settings". A
|
|
28
|
+
* memory left by one page is not picked up by another - a dialog that pops
|
|
29
|
+
* up on a page the reader did not open it on is a surprise, not a resume.
|
|
30
|
+
*/
|
|
31
|
+
export function rememberTraceTrial(
|
|
32
|
+
storage: TrialStorage | null,
|
|
33
|
+
scope: string,
|
|
34
|
+
memory: TraceTrialMemory,
|
|
35
|
+
): void {
|
|
36
|
+
try {
|
|
37
|
+
storage?.setItem(KEY, JSON.stringify({ scope, ...memory }));
|
|
38
|
+
} catch {
|
|
39
|
+
// Storage full or forbidden: the dialog simply does not survive a reload.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function recallTraceTrial(
|
|
44
|
+
storage: TrialStorage | null,
|
|
45
|
+
scope: string,
|
|
46
|
+
): TraceTrialMemory | null {
|
|
47
|
+
try {
|
|
48
|
+
const raw = storage?.getItem(KEY);
|
|
49
|
+
if (!raw) return null;
|
|
50
|
+
const value = JSON.parse(raw) as Partial<TraceTrialMemory> & { scope?: unknown };
|
|
51
|
+
if (value.scope !== scope || typeof value.runId !== "string" || !value.runId) return null;
|
|
52
|
+
|
|
53
|
+
return { runId: value.runId, writeRunId: typeof value.writeRunId === "string" ? value.writeRunId : null };
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function forgetTraceTrial(storage: TrialStorage | null): void {
|
|
60
|
+
try {
|
|
61
|
+
storage?.removeItem(KEY);
|
|
62
|
+
} catch {
|
|
63
|
+
// Nothing to forget, or nowhere to forget it from.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The browser's session storage, or null where there is none. */
|
|
68
|
+
export function sessionStore(): TrialStorage | null {
|
|
69
|
+
try {
|
|
70
|
+
return typeof sessionStorage === "undefined" ? null : sessionStorage;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/graph/elk.ts
CHANGED
|
@@ -190,6 +190,84 @@ export async function layoutWithElk(input: LayoutInput): Promise<LayoutResult> {
|
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Groups of nodes, packed
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
export interface GroupLayoutInput {
|
|
198
|
+
groups: {
|
|
199
|
+
id: string;
|
|
200
|
+
nodes: { id: string; width: number; height: number }[];
|
|
201
|
+
/** Edges between this group's own nodes; one across groups is not laid out. */
|
|
202
|
+
edges: { id: string; source: string; target: string }[];
|
|
203
|
+
}[];
|
|
204
|
+
/** Width over height the packed whole should come out near: the canvas's. */
|
|
205
|
+
aspectRatio: number;
|
|
206
|
+
layerSpacing?: number;
|
|
207
|
+
nodeSpacing?: number;
|
|
208
|
+
/** Room between two groups' frames. */
|
|
209
|
+
groupSpacing?: number;
|
|
210
|
+
/** Room a frame keeps above its nodes, for its label. */
|
|
211
|
+
labelHeight?: number;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export interface GroupLayoutResult {
|
|
215
|
+
/** Every node, in the coordinates of the whole picture. */
|
|
216
|
+
positions: Record<string, Point>;
|
|
217
|
+
/** Each group's frame, in the same coordinates. */
|
|
218
|
+
frames: Record<string, { x: number; y: number; width: number; height: number }>;
|
|
219
|
+
width: number;
|
|
220
|
+
height: number;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* A layered layout inside each group and the groups packed as rectangles
|
|
225
|
+
* outside, to an aspect ratio. This is for a graph that a single layered pass
|
|
226
|
+
* cannot draw: one with a layer of fifty nodes, which comes out as a column
|
|
227
|
+
* whatever direction it is read in. Packing ignores the edges between
|
|
228
|
+
* groups, deliberately - the caller draws those itself, and a packing that
|
|
229
|
+
* tried to shorten them would trade the readable shape for it.
|
|
230
|
+
*/
|
|
231
|
+
export async function layoutGroupsWithElk(input: GroupLayoutInput): Promise<GroupLayoutResult> {
|
|
232
|
+
const labelHeight = input.labelHeight ?? 28;
|
|
233
|
+
const graph: ElkNode = {
|
|
234
|
+
id: "root",
|
|
235
|
+
layoutOptions: {
|
|
236
|
+
"elk.algorithm": "rectpacking",
|
|
237
|
+
"elk.aspectRatio": String(input.aspectRatio),
|
|
238
|
+
"elk.spacing.nodeNode": String(input.groupSpacing ?? 40),
|
|
239
|
+
"elk.padding": "[top=16,left=16,bottom=16,right=16]",
|
|
240
|
+
},
|
|
241
|
+
children: input.groups.map((group) => ({
|
|
242
|
+
id: group.id,
|
|
243
|
+
layoutOptions: {
|
|
244
|
+
"elk.algorithm": "layered",
|
|
245
|
+
"elk.direction": "RIGHT",
|
|
246
|
+
"elk.layered.spacing.nodeNodeBetweenLayers": String(input.layerSpacing ?? 120),
|
|
247
|
+
"elk.spacing.nodeNode": String(input.nodeSpacing ?? 24),
|
|
248
|
+
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
|
249
|
+
"elk.padding": `[top=${labelHeight},left=12,bottom=12,right=12]`,
|
|
250
|
+
},
|
|
251
|
+
children: group.nodes.map((n) => ({ id: n.id, width: n.width, height: n.height })),
|
|
252
|
+
edges: group.edges.map((e) => ({ id: e.id, sources: [e.source], targets: [e.target] })),
|
|
253
|
+
})),
|
|
254
|
+
edges: [],
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const laid = await elk.layout(graph);
|
|
258
|
+
const positions: Record<string, Point> = {};
|
|
259
|
+
const frames: GroupLayoutResult["frames"] = {};
|
|
260
|
+
for (const group of laid.children ?? []) {
|
|
261
|
+
const gx = group.x ?? 0;
|
|
262
|
+
const gy = group.y ?? 0;
|
|
263
|
+
frames[group.id] = { x: gx, y: gy, width: group.width ?? 0, height: group.height ?? 0 };
|
|
264
|
+
for (const child of group.children ?? []) {
|
|
265
|
+
positions[child.id] = { x: gx + (child.x ?? 0), y: gy + (child.y ?? 0) };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return { positions, frames, width: laid.width ?? 0, height: laid.height ?? 0 };
|
|
269
|
+
}
|
|
270
|
+
|
|
193
271
|
// ---------------------------------------------------------------------------
|
|
194
272
|
// Drawing what elk answered
|
|
195
273
|
// ---------------------------------------------------------------------------
|
package/src/index.css
CHANGED
|
@@ -51,6 +51,12 @@
|
|
|
51
51
|
--status-declared: #8a6d1f;
|
|
52
52
|
--status-unresolved: #c02a29;
|
|
53
53
|
|
|
54
|
+
/* Which way a message travels on a channel: out is the accent's blue, in
|
|
55
|
+
is teal, far enough apart that a row reads at a glance and neither is a
|
|
56
|
+
status colour. */
|
|
57
|
+
--dir-send: #2563a8;
|
|
58
|
+
--dir-receive: #0f7a6c;
|
|
59
|
+
|
|
54
60
|
/* Domain events are the loudest leaf in the tree. Amber, but a step darker
|
|
55
61
|
than amber-600 so 12px mono still clears contrast on white. */
|
|
56
62
|
--kind-event: #b45309;
|
|
@@ -173,6 +179,9 @@
|
|
|
173
179
|
--status-declared: #d6a844;
|
|
174
180
|
--status-unresolved: #ff6b63;
|
|
175
181
|
|
|
182
|
+
--dir-send: #82b4f4;
|
|
183
|
+
--dir-receive: #5fcbb3;
|
|
184
|
+
|
|
176
185
|
--kind-event: #e8a33d;
|
|
177
186
|
--response-error: #d98288;
|
|
178
187
|
--response-error-bg: #2a1c20;
|
|
@@ -428,6 +437,16 @@
|
|
|
428
437
|
border-color: var(--status-unresolved);
|
|
429
438
|
}
|
|
430
439
|
|
|
440
|
+
/* A message's direction on a channel, worn by the chip that names it. */
|
|
441
|
+
.dir-send {
|
|
442
|
+
color: var(--dir-send);
|
|
443
|
+
border-color: var(--dir-send);
|
|
444
|
+
}
|
|
445
|
+
.dir-receive {
|
|
446
|
+
color: var(--dir-receive);
|
|
447
|
+
border-color: var(--dir-receive);
|
|
448
|
+
}
|
|
449
|
+
|
|
431
450
|
/* Section label above a block of content. */
|
|
432
451
|
.label {
|
|
433
452
|
@apply mono uppercase text-muted;
|
|
@@ -681,6 +700,50 @@
|
|
|
681
700
|
outline: 2px solid color-mix(in srgb, var(--accent) 55%, white);
|
|
682
701
|
outline-offset: 3px;
|
|
683
702
|
}
|
|
703
|
+
.product-danger {
|
|
704
|
+
color: white;
|
|
705
|
+
border-color: #a33a4b;
|
|
706
|
+
background:
|
|
707
|
+
radial-gradient(
|
|
708
|
+
circle at 18% 0%,
|
|
709
|
+
rgb(255 255 255 / 0.16),
|
|
710
|
+
transparent 42%
|
|
711
|
+
),
|
|
712
|
+
linear-gradient(
|
|
713
|
+
to bottom,
|
|
714
|
+
#8e2b3d,
|
|
715
|
+
#651827
|
|
716
|
+
);
|
|
717
|
+
box-shadow:
|
|
718
|
+
inset 0 1px 0 rgb(255 255 255 / 0.2),
|
|
719
|
+
inset 0 -1px 0 rgb(0 0 0 / 0.28),
|
|
720
|
+
0 1px 2px rgb(0 0 0 / 0.24),
|
|
721
|
+
0 12px 28px -18px color-mix(in srgb, var(--status-unresolved) 84%, transparent);
|
|
722
|
+
text-shadow: 0 1px 0 rgb(0 0 0 / 0.32);
|
|
723
|
+
}
|
|
724
|
+
.product-danger:hover {
|
|
725
|
+
color: white;
|
|
726
|
+
border-color: #c45a6b;
|
|
727
|
+
background:
|
|
728
|
+
radial-gradient(
|
|
729
|
+
circle at 18% 0%,
|
|
730
|
+
rgb(255 255 255 / 0.2),
|
|
731
|
+
transparent 42%
|
|
732
|
+
),
|
|
733
|
+
linear-gradient(
|
|
734
|
+
to bottom,
|
|
735
|
+
#a13246,
|
|
736
|
+
#741b2c
|
|
737
|
+
);
|
|
738
|
+
box-shadow:
|
|
739
|
+
inset 0 1px 0 rgb(255 255 255 / 0.26),
|
|
740
|
+
inset 0 -1px 0 rgb(0 0 0 / 0.3),
|
|
741
|
+
0 2px 4px rgb(0 0 0 / 0.25),
|
|
742
|
+
0 16px 34px -17px color-mix(in srgb, var(--status-unresolved) 96%, transparent);
|
|
743
|
+
}
|
|
744
|
+
.product-danger:focus-visible {
|
|
745
|
+
outline-color: color-mix(in srgb, var(--status-unresolved) 62%, white);
|
|
746
|
+
}
|
|
684
747
|
.product-primary:disabled,
|
|
685
748
|
.tbtn:disabled {
|
|
686
749
|
cursor: not-allowed;
|
|
@@ -1441,6 +1504,11 @@
|
|
|
1441
1504
|
color: var(--fg-faint);
|
|
1442
1505
|
background: var(--surface-2);
|
|
1443
1506
|
}
|
|
1507
|
+
/* A rule on the value - "≥ 1 chars", "matches ^[A-Z]{3}$" - in the same
|
|
1508
|
+
tone. A pattern can run long; the tooltip carries the whole of it. */
|
|
1509
|
+
.rule-mark {
|
|
1510
|
+
@apply max-w-[18ch] truncate;
|
|
1511
|
+
}
|
|
1444
1512
|
}
|
|
1445
1513
|
|
|
1446
1514
|
/* ---------------------------------------------------------------------------
|
|
@@ -105,7 +105,9 @@ export function DraggableReveal({
|
|
|
105
105
|
className={`pointer-events-none absolute inset-0 z-0 flex ${catPlacement[revealEdge]}`}
|
|
106
106
|
>
|
|
107
107
|
<m.img
|
|
108
|
-
layout
|
|
108
|
+
// Deliberately not a layout animation: the preview's children can
|
|
109
|
+
// reflow independently, and their tab changes must not send this
|
|
110
|
+
// hidden illustration across the background.
|
|
109
111
|
src={cat}
|
|
110
112
|
alt=""
|
|
111
113
|
width={800}
|
|
@@ -118,7 +120,6 @@ export function DraggableReveal({
|
|
|
118
120
|
y: dragging ? -3 : 0,
|
|
119
121
|
}}
|
|
120
122
|
transition={{
|
|
121
|
-
layout: { type: "spring", stiffness: 260, damping: 28 },
|
|
122
123
|
opacity: { duration: 0.22 },
|
|
123
124
|
scale: { duration: 0.28 },
|
|
124
125
|
y: { duration: 0.28 },
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ArrowRight, Braces, CircleDotDashed, Code2, Network } from "lucide-react";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { useInView } from "motion/react";
|
|
4
|
+
import { m, useReducedMotion } from "../lib/motion";
|
|
5
|
+
|
|
6
|
+
const steps = [
|
|
7
|
+
{
|
|
8
|
+
icon: Code2,
|
|
9
|
+
label: "01 / read",
|
|
10
|
+
title: "Code and specifications",
|
|
11
|
+
copy: "Existing source, schemas, contracts, ADRs and traces.",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
icon: Braces,
|
|
15
|
+
label: "02 / extract",
|
|
16
|
+
title: "Local fragments",
|
|
17
|
+
copy: "Each service publishes the architectural facts it owns.",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
icon: CircleDotDashed,
|
|
21
|
+
label: "03 / validate",
|
|
22
|
+
title: "One estate model",
|
|
23
|
+
copy: "Portolan merges the union and checks every relationship.",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
icon: Network,
|
|
27
|
+
label: "04 / publish",
|
|
28
|
+
title: "A static catalog",
|
|
29
|
+
copy: "Browse the site or export Markdown, Mermaid and Backstage entities.",
|
|
30
|
+
},
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
// One shared cycle keeps the arrows in sync, including the quiet pause.
|
|
34
|
+
const cycle = 6.8;
|
|
35
|
+
const pulse = (index: number) => {
|
|
36
|
+
const start = 0.4 + index * 0.85;
|
|
37
|
+
return {
|
|
38
|
+
duration: cycle,
|
|
39
|
+
repeat: Infinity,
|
|
40
|
+
ease: "easeInOut" as const,
|
|
41
|
+
times: [0, start, start + 0.2, start + 0.65, start + 1.15, cycle].map(
|
|
42
|
+
(time) => time / cycle,
|
|
43
|
+
),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function EvidencePipeline() {
|
|
48
|
+
const ref = useRef<HTMLOListElement>(null);
|
|
49
|
+
const inView = useInView(ref, { amount: 0.2 });
|
|
50
|
+
const reduced = useReducedMotion();
|
|
51
|
+
const playing = inView && !reduced;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<ol
|
|
55
|
+
ref={ref}
|
|
56
|
+
aria-label="How Portolan turns evidence into architecture documentation"
|
|
57
|
+
className="mt-12 grid gap-4 md:grid-cols-2 xl:grid-cols-4"
|
|
58
|
+
>
|
|
59
|
+
{steps.map((item, index) => {
|
|
60
|
+
const Icon = item.icon;
|
|
61
|
+
const continues = index < steps.length - 1;
|
|
62
|
+
const connectsWithinRow = index === 0 || index === 2;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<li
|
|
66
|
+
key={item.title}
|
|
67
|
+
className="relative rounded-card border border-line bg-canvas p-5 shadow-xs"
|
|
68
|
+
>
|
|
69
|
+
<div className="flex items-center justify-between">
|
|
70
|
+
<span className="flow-tile text-accent">
|
|
71
|
+
<Icon size={14} aria-hidden />
|
|
72
|
+
</span>
|
|
73
|
+
<span className="mono text-faint">{item.label}</span>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<h3 className="mt-8 text-md font-semibold">{item.title}</h3>
|
|
77
|
+
<p className="mt-2 text-sm text-muted">{item.copy}</p>
|
|
78
|
+
|
|
79
|
+
{continues ? (
|
|
80
|
+
<span
|
|
81
|
+
aria-hidden
|
|
82
|
+
className={`absolute top-[22px] -right-[17px] z-10 h-[18px] w-4 items-center justify-center bg-canvas text-faint ${
|
|
83
|
+
connectsWithinRow ? "hidden md:flex" : "hidden xl:flex"
|
|
84
|
+
}`}
|
|
85
|
+
>
|
|
86
|
+
<ArrowRight size={11} />
|
|
87
|
+
<m.span
|
|
88
|
+
className="absolute inset-0 flex items-center justify-center text-accent drop-shadow-[0_0_4px_var(--color-accent)]"
|
|
89
|
+
initial={{ opacity: 0, x: -4 }}
|
|
90
|
+
animate={playing ? {
|
|
91
|
+
opacity: [0, 0, 1, 1, 0, 0],
|
|
92
|
+
x: [-4, -4, -2, 2, 4, 4],
|
|
93
|
+
} : { opacity: 0, x: -4 }}
|
|
94
|
+
transition={playing ? pulse(index + 0.65) : { duration: 0.2 }}
|
|
95
|
+
>
|
|
96
|
+
<ArrowRight size={11} />
|
|
97
|
+
</m.span>
|
|
98
|
+
</span>
|
|
99
|
+
) : null}
|
|
100
|
+
</li>
|
|
101
|
+
);
|
|
102
|
+
})}
|
|
103
|
+
</ol>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ArrowRight,
|
|
3
3
|
Bot,
|
|
4
|
-
Braces,
|
|
5
4
|
Check,
|
|
6
|
-
CircleDotDashed,
|
|
7
|
-
Code2,
|
|
8
5
|
Copy,
|
|
9
6
|
FileSearch,
|
|
10
7
|
Files,
|
|
11
8
|
FileText,
|
|
12
9
|
GitBranch,
|
|
13
10
|
Moon,
|
|
14
|
-
Network,
|
|
15
11
|
ShieldCheck,
|
|
16
12
|
Sun,
|
|
17
13
|
Terminal,
|
|
@@ -24,9 +20,11 @@ import { useTheme } from "../app/theme";
|
|
|
24
20
|
import { useDocumentTitle } from "../app/title";
|
|
25
21
|
import { catalog } from "../data";
|
|
26
22
|
import { m } from "../lib/motion";
|
|
23
|
+
import { landingInputs } from "../lib/plugins";
|
|
27
24
|
import { paths } from "../routes";
|
|
28
25
|
import { catalogTo } from "./catalog";
|
|
29
26
|
import { DraggableReveal } from "./DraggableReveal";
|
|
27
|
+
import { EvidencePipeline } from "./EvidencePipeline";
|
|
30
28
|
import { heroColumn, heroLine, Reveal } from "./motion";
|
|
31
29
|
import { ProductFrame } from "./ProductFrame";
|
|
32
30
|
import { ProductTour } from "./ProductTour";
|
|
@@ -79,6 +77,9 @@ function Header() {
|
|
|
79
77
|
<a className="hover:text-ink" href="#get-started">
|
|
80
78
|
Get started
|
|
81
79
|
</a>
|
|
80
|
+
<Link className="hover:text-ink" to={pluginsTo}>
|
|
81
|
+
Plugins
|
|
82
|
+
</Link>
|
|
82
83
|
</nav>
|
|
83
84
|
<div className="ml-auto flex items-center gap-2">
|
|
84
85
|
<button
|
|
@@ -149,11 +150,10 @@ function HeroDemo() {
|
|
|
149
150
|
);
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
];
|
|
153
|
+
// What the plugins say they read, from the index `npm run schema` writes: a
|
|
154
|
+
// new extractor lands here without anyone remembering to add it.
|
|
155
|
+
const inputGroups = landingInputs();
|
|
156
|
+
const pluginsTo = catalogTo(paths.plugins());
|
|
157
157
|
|
|
158
158
|
const features = [
|
|
159
159
|
{
|
|
@@ -249,33 +249,6 @@ function CopyCommand({
|
|
|
249
249
|
);
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
const pipeline = [
|
|
253
|
-
{
|
|
254
|
-
icon: Code2,
|
|
255
|
-
label: "01 / read",
|
|
256
|
-
title: "Code and specifications",
|
|
257
|
-
copy: "Existing source, schemas, contracts, ADRs and traces.",
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
icon: Braces,
|
|
261
|
-
label: "02 / extract",
|
|
262
|
-
title: "Local fragments",
|
|
263
|
-
copy: "Each service publishes the architectural facts it owns.",
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
icon: CircleDotDashed,
|
|
267
|
-
label: "03 / validate",
|
|
268
|
-
title: "One estate model",
|
|
269
|
-
copy: "Portolan merges the union and checks every relationship.",
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
icon: Network,
|
|
273
|
-
label: "04 / publish",
|
|
274
|
-
title: "A static catalog",
|
|
275
|
-
copy: "Browse the site or export Markdown, Mermaid and Backstage entities.",
|
|
276
|
-
},
|
|
277
|
-
];
|
|
278
|
-
|
|
279
252
|
export function LandingPage() {
|
|
280
253
|
useDocumentTitle("Architecture from code");
|
|
281
254
|
|
|
@@ -365,14 +338,12 @@ export function LandingPage() {
|
|
|
365
338
|
<div className="mx-auto flex max-w-[1200px] flex-col gap-5 px-5 py-7 sm:px-8 lg:flex-row lg:items-center">
|
|
366
339
|
<div className="shrink-0">
|
|
367
340
|
<div className="mono text-faint">BUILT-IN INPUTS</div>
|
|
368
|
-
<
|
|
369
|
-
|
|
370
|
-
target="_blank"
|
|
371
|
-
rel="noreferrer"
|
|
341
|
+
<Link
|
|
342
|
+
to={pluginsTo}
|
|
372
343
|
className="mt-1 inline-flex items-center gap-1 text-sm text-accent hover:underline"
|
|
373
344
|
>
|
|
374
|
-
See every
|
|
375
|
-
</
|
|
345
|
+
See every plugin <ArrowRight size={12} />
|
|
346
|
+
</Link>
|
|
376
347
|
</div>
|
|
377
348
|
<div className="flex flex-wrap gap-x-7 gap-y-3 lg:ml-auto lg:justify-end">
|
|
378
349
|
{inputGroups.map((group) => (
|
|
@@ -422,34 +393,7 @@ export function LandingPage() {
|
|
|
422
393
|
Architecture documentation that begins with evidence.
|
|
423
394
|
</h2>
|
|
424
395
|
</Reveal>
|
|
425
|
-
<
|
|
426
|
-
{pipeline.map((item, index) => {
|
|
427
|
-
const Icon = item.icon;
|
|
428
|
-
return (
|
|
429
|
-
<Reveal
|
|
430
|
-
key={item.title}
|
|
431
|
-
delay={index * 0.07}
|
|
432
|
-
className="relative rounded-card border border-line bg-canvas p-5 shadow-xs"
|
|
433
|
-
>
|
|
434
|
-
<div className="flex items-center justify-between">
|
|
435
|
-
<span className="flow-tile text-accent">
|
|
436
|
-
<Icon size={14} />
|
|
437
|
-
</span>
|
|
438
|
-
<span className="mono text-faint">{item.label}</span>
|
|
439
|
-
</div>
|
|
440
|
-
<h3 className="mt-8 text-md font-semibold">{item.title}</h3>
|
|
441
|
-
<p className="mt-2 text-sm text-muted">{item.copy}</p>
|
|
442
|
-
{index < pipeline.length - 1 ? (
|
|
443
|
-
<ArrowRight
|
|
444
|
-
size={16}
|
|
445
|
-
className="absolute top-1/2 -right-[11px] z-10 hidden -translate-y-1/2 rounded-full bg-canvas text-faint xl:block"
|
|
446
|
-
aria-hidden
|
|
447
|
-
/>
|
|
448
|
-
) : null}
|
|
449
|
-
</Reveal>
|
|
450
|
-
);
|
|
451
|
-
})}
|
|
452
|
-
</div>
|
|
396
|
+
<EvidencePipeline />
|
|
453
397
|
</div>
|
|
454
398
|
</section>
|
|
455
399
|
|
|
@@ -744,6 +688,9 @@ export function LandingPage() {
|
|
|
744
688
|
<a className="hover:text-ink" href={PRODUCT_README}>
|
|
745
689
|
Docs
|
|
746
690
|
</a>
|
|
691
|
+
<Link className="hover:text-ink" to={pluginsTo}>
|
|
692
|
+
Plugins
|
|
693
|
+
</Link>
|
|
747
694
|
<a
|
|
748
695
|
className="hover:text-ink"
|
|
749
696
|
href={PRODUCT_LICENSE}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// product's own canvases in a smaller box; the other two are read straight
|
|
3
3
|
// out of the same catalog. Nothing on this page is drawn from a mock-up.
|
|
4
4
|
|
|
5
|
-
import { lazy, Suspense,
|
|
5
|
+
import { lazy, Suspense, useState } from "react";
|
|
6
6
|
import { Link } from "react-router";
|
|
7
7
|
import {
|
|
8
8
|
AlertTriangle,
|
|
@@ -14,10 +14,10 @@ import {
|
|
|
14
14
|
Network,
|
|
15
15
|
Radio,
|
|
16
16
|
} from "lucide-react";
|
|
17
|
-
import {
|
|
17
|
+
import { index } from "../data";
|
|
18
18
|
import { DiagramSkeleton } from "../components/DiagramSkeleton";
|
|
19
19
|
import { contextVar } from "../lib/context-color";
|
|
20
|
-
import {
|
|
20
|
+
import { useProblems } from "../lib/use-problems";
|
|
21
21
|
import {
|
|
22
22
|
AnimatePresence,
|
|
23
23
|
LayoutGroup,
|
|
@@ -194,7 +194,7 @@ function ContractDemo() {
|
|
|
194
194
|
const SHOWN_PROBLEMS = 4;
|
|
195
195
|
|
|
196
196
|
function ProblemsDemo() {
|
|
197
|
-
const all =
|
|
197
|
+
const all = useProblems();
|
|
198
198
|
const errors = all.filter((p) => p.severity === "error").length;
|
|
199
199
|
const shown = all.slice(0, SHOWN_PROBLEMS);
|
|
200
200
|
|
|
@@ -228,7 +228,7 @@ function ProblemsDemo() {
|
|
|
228
228
|
<div className="space-y-2">
|
|
229
229
|
{shown.map((problem, i) => (
|
|
230
230
|
<div
|
|
231
|
-
key={`${problem.
|
|
231
|
+
key={`${problem.rule}:${problem.id}:${problem.peer}`}
|
|
232
232
|
style={staggerStyle(i)}
|
|
233
233
|
className="stagger-in grid grid-cols-[20px_minmax(0,1fr)_auto] items-start gap-3 rounded-card border border-line bg-canvas p-3"
|
|
234
234
|
>
|
|
@@ -255,7 +255,7 @@ function ProblemsDemo() {
|
|
|
255
255
|
</div>
|
|
256
256
|
</div>
|
|
257
257
|
<span className="chip border-line text-muted">
|
|
258
|
-
{problem.
|
|
258
|
+
{problem.rule}
|
|
259
259
|
</span>
|
|
260
260
|
</div>
|
|
261
261
|
))}
|
package/src/lib/all-problems.ts
CHANGED
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
// Every reading the catalog makes of itself, in one list.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
3
|
+
// The rules in force - the shipped ones, switched and re-graded as the
|
|
4
|
+
// manifest says, and the estate's own - run over the merged catalog, and
|
|
5
|
+
// what they find comes out as problems. The problems page, the overview and
|
|
6
|
+
// the landing all want the union, errors first - a boundary leak is not the
|
|
7
|
+
// same kind of news as a column whose type has drifted, and mixing them
|
|
8
|
+
// buries the first.
|
|
9
9
|
|
|
10
10
|
import type { Catalog, CatalogIndex } from "../catalog";
|
|
11
|
-
import { dataProblems } from "./data-problems";
|
|
12
|
-
import { problems } from "./derive";
|
|
13
11
|
import type { Problem } from "./derive";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
12
|
+
import { currentRuleEntries, evaluateRules, resolveRules } from "./problem-rules";
|
|
13
|
+
import type { ProblemRule, RuleEvaluation } from "./problem-rules";
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
/** The rules over the catalog, with what they had to say about it. */
|
|
16
|
+
export function evaluateProblems(
|
|
17
|
+
catalog: Catalog,
|
|
18
|
+
index: CatalogIndex,
|
|
19
|
+
rules: readonly ProblemRule[] = resolveRules(currentRuleEntries()),
|
|
20
|
+
): RuleEvaluation {
|
|
21
|
+
const evaluation = evaluateRules(catalog, index, rules);
|
|
22
|
+
return { ...evaluation, problems: bySeverity(evaluation.problems) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function allProblems(
|
|
26
|
+
catalog: Catalog,
|
|
27
|
+
index: CatalogIndex,
|
|
28
|
+
rules: readonly ProblemRule[] = resolveRules(currentRuleEntries()),
|
|
29
|
+
): Problem[] {
|
|
30
|
+
return evaluateProblems(catalog, index, rules).problems;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function bySeverity(found: Problem[]): Problem[] {
|
|
24
34
|
return [
|
|
25
35
|
...found.filter((p) => p.severity === "error"),
|
|
26
36
|
...found.filter((p) => p.severity === "warning"),
|
package/src/lib/catalog-diff.ts
CHANGED
|
@@ -245,7 +245,7 @@ function diffOwners(before: Service, after: Service, add: Add): void {
|
|
|
245
245
|
function signature(method: RpcMethod): string {
|
|
246
246
|
const parts = [method.request ?? "?", method.response ?? "?"];
|
|
247
247
|
if (method.streaming) parts.push(method.streaming);
|
|
248
|
-
if (method.http) parts.push(`${method.http.method} ${method.http.path}`);
|
|
248
|
+
if (method.http) parts.push(`${method.http.method || "?"} ${method.http.path}`);
|
|
249
249
|
if (method.soap) {
|
|
250
250
|
parts.push(
|
|
251
251
|
[
|