@shortlink-org/portolan 0.1.0 → 0.2.1
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 +69 -205
- package/cli/init.mjs +285 -0
- package/cli/init.test.mjs +158 -0
- package/cli/portolan.mjs +37 -73
- package/index.html +19 -0
- package/package.json +5 -10
- package/plugins/README.md +66 -27
- package/plugins/portolan-go.wasm +0 -0
- package/portolan.json +52 -73
- package/public/landing/cats/bug-hunter.webp +0 -0
- package/public/landing/cats/compass-nap.webp +0 -0
- package/public/landing/cats/diagrammer.webp +0 -0
- package/public/landing/cats/docs-reader.webp +0 -0
- package/public/landing/cats/laptop.webp +0 -0
- package/public/landing/cats/map-inspector.webp +0 -0
- package/public/landing/cats/server-break.webp +0 -0
- package/public/landing/cats/star-mapper.webp +0 -0
- package/public/landing/cats/system-builder.webp +0 -0
- package/public/landing/cats/thread-tangle.webp +0 -0
- package/public/og.png +0 -0
- package/public/readme-header.webp +0 -0
- package/schema/portolan.schema.json +180 -0
- package/scripts/builtin-plugins.mjs +14 -0
- package/scripts/delivery-presets.mjs +353 -0
- package/scripts/gen.mjs +37 -4
- package/scripts/history.mjs +126 -0
- package/scripts/history.test.mjs +110 -0
- package/scripts/host-plugins/fetch-bsr.mjs +351 -0
- package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
- package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
- package/scripts/host-plugins/fetch-csr.mjs +386 -0
- package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
- package/scripts/host-plugins/fetch-git.mjs +379 -0
- package/scripts/host-plugins/fetch-git.test.mjs +210 -0
- package/scripts/local-api.mjs +27 -10
- package/scripts/local-api.test.mjs +85 -2
- package/scripts/manifest.mjs +3 -2
- package/scripts/package-smoke.mjs +24 -3
- package/scripts/plugin-host.mjs +77 -18
- package/scripts/plugin-host.test.mjs +50 -1
- package/scripts/plugin-wasm-worker.mjs +6 -3
- package/scripts/run-builtin.mjs +6 -26
- package/scripts/schema.mjs +6 -1
- package/src/app/App.tsx +24 -429
- package/src/app/Breadcrumbs.test.ts +7 -0
- package/src/app/Breadcrumbs.tsx +15 -2
- package/src/app/CatalogApp.tsx +436 -0
- package/src/app/Sidebar.tsx +0 -1
- package/src/chat/ChatPanel.tsx +98 -41
- package/src/chat/Composer.tsx +4 -2
- package/src/chat/Conversation.tsx +79 -15
- package/src/chat/Header.tsx +43 -9
- package/src/chat/Starter.tsx +64 -3
- package/src/chat/Steps.tsx +1 -1
- package/src/chat/flags.test.ts +51 -14
- package/src/chat/flags.ts +8 -2
- package/src/chat/page-context.test.ts +51 -0
- package/src/chat/page-context.ts +128 -0
- package/src/chat/prompt.test.ts +49 -1
- package/src/chat/prompt.ts +42 -1
- package/src/chat/tools.ts +7 -2
- package/src/chat/transport.ts +16 -4
- package/src/components/ProblemRow.tsx +182 -0
- package/src/data.ts +20 -5
- package/src/er/ErCanvas.tsx +1 -1
- package/src/graph/DependencyGraph.tsx +10 -1
- package/src/graph/FocusedEventGraph.tsx +1 -1
- package/src/graph/GraphToolbar.tsx +2 -4
- package/src/index.css +220 -2
- package/src/landing/DraggableReveal.tsx +168 -0
- package/src/landing/EstateGraph.tsx +28 -0
- package/src/landing/FlowPlayback.tsx +282 -0
- package/src/landing/HeroMap.tsx +128 -0
- package/src/landing/LandingPage.tsx +757 -0
- package/src/landing/ProductFrame.tsx +68 -0
- package/src/landing/ProductTour.tsx +371 -0
- package/src/landing/catalog.ts +25 -0
- package/src/landing/motion.tsx +52 -0
- package/src/lib/all-problems.ts +28 -0
- package/src/lib/local-api.ts +35 -1
- package/src/lib/setup-info.test.ts +8 -0
- package/src/lib/setup-info.ts +3 -2
- package/src/lib/source-code.ts +2 -1
- package/src/likec4/C4View.tsx +5 -0
- package/src/likec4/InteractiveView.tsx +5 -1
- package/src/map/ContextMapGraph.tsx +47 -20
- package/src/pages/ContextMap.tsx +5 -1
- package/src/pages/FlowDetail.tsx +24 -7
- package/src/pages/Overview.tsx +68 -12
- package/src/pages/Problems.tsx +6 -193
- package/src/pages/Settings.tsx +133 -108
- package/src/pages/settings/DeliverySettings.tsx +229 -0
- package/src/pages/settings/PreferencesSettings.tsx +101 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +13 -1
- package/vite.config.ts +17 -9
- package/catalog/enum_test.go +0 -46
- package/catalog/model.go +0 -932
- package/catalog/roundtrip_test.go +0 -185
- package/catalog/via_test.go +0 -38
- package/go.mod +0 -14
- package/go.sum +0 -14
- package/internal/commands/cargo.go +0 -154
- package/internal/commands/commands.go +0 -143
- package/internal/commands/commands_test.go +0 -323
- package/internal/commands/gradle.go +0 -77
- package/internal/commands/justfile.go +0 -82
- package/internal/commands/makefile.go +0 -106
- package/internal/commands/maven.go +0 -93
- package/internal/commands/packagejson.go +0 -159
- package/internal/commands/pyproject.go +0 -169
- package/internal/commands/taskfile.go +0 -101
- package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
- package/internal/commands/testdata/estate/Makefile +0 -33
- package/internal/commands/testdata/estate/Taskfile.yml +0 -21
- package/internal/commands/testdata/estate/build.gradle.kts +0 -21
- package/internal/commands/testdata/estate/justfile +0 -20
- package/internal/commands/testdata/estate/package.json +0 -13
- package/internal/commands/testdata/estate/pom.xml +0 -29
- package/internal/commands/testdata/estate/pyproject.toml +0 -27
- package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
- package/internal/commands/testdata/golden/commands.json +0 -305
- package/internal/gohttp/analyze.go +0 -2500
- package/internal/gohttp/endpoints.go +0 -1067
- package/internal/gohttp/roots.go +0 -320
- package/internal/gohttp/typed.go +0 -143
- package/internal/goscan/constants.go +0 -85
- package/internal/goscan/goscan_test.go +0 -227
- package/internal/goscan/names.go +0 -52
- package/internal/goscan/parse_test.go +0 -11
- package/internal/goscan/source.go +0 -36
- package/internal/goscan/tree.go +0 -155
- package/internal/goscan/types.go +0 -99
- package/internal/wsdl/ids.go +0 -127
- package/internal/wsdl/ids_test.go +0 -21
- package/internal/wsdl/model.go +0 -62
- package/internal/wsdl/parse.go +0 -920
- package/internal/wsdl/parse_test.go +0 -133
- package/plugin/describe.go +0 -107
- package/plugin/describe_test.go +0 -114
- package/plugin/protocol.go +0 -117
- package/plugin/schematest/schematest.go +0 -126
- package/plugins/extract-adr/describe.go +0 -19
- package/plugins/extract-adr/describe_test.go +0 -11
- package/plugins/extract-adr/extract.go +0 -153
- package/plugins/extract-adr/extract_test.go +0 -350
- package/plugins/extract-adr/history.go +0 -99
- package/plugins/extract-adr/main.go +0 -65
- package/plugins/extract-adr/parse.go +0 -642
- package/plugins/extract-adr/parse_test.go +0 -405
- package/plugins/extract-asyncapi/describe.go +0 -19
- package/plugins/extract-asyncapi/describe_test.go +0 -11
- package/plugins/extract-asyncapi/extract.go +0 -315
- package/plugins/extract-asyncapi/extract_test.go +0 -197
- package/plugins/extract-asyncapi/main.go +0 -48
- package/plugins/extract-asyncapi/spec.go +0 -151
- package/plugins/extract-commands/describe.go +0 -19
- package/plugins/extract-commands/describe_test.go +0 -11
- package/plugins/extract-commands/extract.go +0 -79
- package/plugins/extract-commands/extract_test.go +0 -92
- package/plugins/extract-commands/main.go +0 -55
- package/plugins/extract-csr/avro.go +0 -258
- package/plugins/extract-csr/describe.go +0 -19
- package/plugins/extract-csr/describe_test.go +0 -11
- package/plugins/extract-csr/extract.go +0 -338
- package/plugins/extract-csr/extract_test.go +0 -374
- package/plugins/extract-csr/jsonschema.go +0 -343
- package/plugins/extract-csr/lock.go +0 -25
- package/plugins/extract-csr/main.go +0 -84
- package/plugins/extract-csr/subject.go +0 -102
- package/plugins/extract-flows/describe.go +0 -19
- package/plugins/extract-flows/describe_test.go +0 -11
- package/plugins/extract-flows/extract.go +0 -91
- package/plugins/extract-flows/main.go +0 -45
- package/plugins/extract-flows/parse.go +0 -593
- package/plugins/extract-flows/parse_test.go +0 -204
- package/plugins/extract-glossary/describe.go +0 -19
- package/plugins/extract-glossary/describe_test.go +0 -11
- package/plugins/extract-glossary/extract.go +0 -115
- package/plugins/extract-glossary/extract_test.go +0 -220
- package/plugins/extract-glossary/main.go +0 -59
- package/plugins/extract-glossary/parse.go +0 -214
- package/plugins/extract-glossary/parse_test.go +0 -203
- package/plugins/extract-go/aggregate.go +0 -214
- package/plugins/extract-go/client.go +0 -409
- package/plugins/extract-go/client_test.go +0 -266
- package/plugins/extract-go/describe.go +0 -19
- package/plugins/extract-go/describe_test.go +0 -11
- package/plugins/extract-go/enum.go +0 -195
- package/plugins/extract-go/enum_test.go +0 -82
- package/plugins/extract-go/event.go +0 -99
- package/plugins/extract-go/extract.go +0 -191
- package/plugins/extract-go/extract_test.go +0 -261
- package/plugins/extract-go/flow.go +0 -1441
- package/plugins/extract-go/flow_test.go +0 -609
- package/plugins/extract-go/httpclient.go +0 -174
- package/plugins/extract-go/httpclient_test.go +0 -296
- package/plugins/extract-go/ids.go +0 -92
- package/plugins/extract-go/layout.go +0 -225
- package/plugins/extract-go/layout_test.go +0 -125
- package/plugins/extract-go/lifecycle.go +0 -324
- package/plugins/extract-go/lifecycle_test.go +0 -108
- package/plugins/extract-go/main.go +0 -79
- package/plugins/extract-go/operation.go +0 -157
- package/plugins/extract-go/source.go +0 -316
- package/plugins/extract-go/transport.go +0 -321
- package/plugins/extract-go/transport_test.go +0 -145
- package/plugins/extract-go/wiring.go +0 -434
- package/plugins/extract-go-nats/describe.go +0 -19
- package/plugins/extract-go-nats/describe_test.go +0 -11
- package/plugins/extract-go-nats/extract.go +0 -177
- package/plugins/extract-go-nats/extract_test.go +0 -389
- package/plugins/extract-go-nats/index.go +0 -379
- package/plugins/extract-go-nats/main.go +0 -42
- package/plugins/extract-go-nats/resolve.go +0 -161
- package/plugins/extract-go-nats/sites.go +0 -224
- package/plugins/extract-graphql/describe.go +0 -19
- package/plugins/extract-graphql/describe_test.go +0 -11
- package/plugins/extract-graphql/extract.go +0 -433
- package/plugins/extract-graphql/extract_test.go +0 -256
- package/plugins/extract-graphql/ids.go +0 -49
- package/plugins/extract-graphql/lex.go +0 -237
- package/plugins/extract-graphql/main.go +0 -51
- package/plugins/extract-graphql/parse.go +0 -621
- package/plugins/extract-graphql/parse_test.go +0 -122
- package/plugins/extract-http-clients/describe.go +0 -19
- package/plugins/extract-http-clients/describe_test.go +0 -11
- package/plugins/extract-http-clients/extract.go +0 -705
- package/plugins/extract-http-clients/extract_test.go +0 -1263
- package/plugins/extract-http-clients/main.go +0 -42
- package/plugins/extract-openapi/describe.go +0 -19
- package/plugins/extract-openapi/describe_test.go +0 -11
- package/plugins/extract-openapi/discover.go +0 -243
- package/plugins/extract-openapi/extract.go +0 -526
- package/plugins/extract-openapi/extract_test.go +0 -545
- package/plugins/extract-openapi/main.go +0 -75
- package/plugins/extract-openapi/spec.go +0 -350
- package/plugins/extract-project/describe.go +0 -19
- package/plugins/extract-project/describe_test.go +0 -11
- package/plugins/extract-project/extract.go +0 -221
- package/plugins/extract-project/extract_test.go +0 -109
- package/plugins/extract-project/main.go +0 -41
- package/plugins/extract-proto/ast.go +0 -125
- package/plugins/extract-proto/consumes.go +0 -77
- package/plugins/extract-proto/describe.go +0 -19
- package/plugins/extract-proto/extract.go +0 -293
- package/plugins/extract-proto/extract_test.go +0 -459
- package/plugins/extract-proto/ids.go +0 -89
- package/plugins/extract-proto/ids_test.go +0 -57
- package/plugins/extract-proto/lex.go +0 -285
- package/plugins/extract-proto/main.go +0 -100
- package/plugins/extract-proto/module.go +0 -120
- package/plugins/extract-proto/parse.go +0 -720
- package/plugins/extract-proto/parse_test.go +0 -307
- package/plugins/extract-proto/provides.go +0 -236
- package/plugins/extract-proto/resolve.go +0 -222
- package/plugins/extract-proto/resolve_test.go +0 -119
- package/plugins/extract-redis/describe.go +0 -19
- package/plugins/extract-redis/describe_test.go +0 -11
- package/plugins/extract-redis/extract.go +0 -183
- package/plugins/extract-redis/extract_test.go +0 -168
- package/plugins/extract-redis/keyspaces.go +0 -469
- package/plugins/extract-redis/main.go +0 -44
- package/plugins/extract-river/describe.go +0 -19
- package/plugins/extract-river/describe_test.go +0 -11
- package/plugins/extract-river/extract.go +0 -507
- package/plugins/extract-river/extract_test.go +0 -132
- package/plugins/extract-river/main.go +0 -42
- package/plugins/extract-sql/ddl.go +0 -893
- package/plugins/extract-sql/ddl_test.go +0 -401
- package/plugins/extract-sql/describe.go +0 -19
- package/plugins/extract-sql/describe_test.go +0 -11
- package/plugins/extract-sql/layout.go +0 -221
- package/plugins/extract-sql/layout_test.go +0 -55
- package/plugins/extract-sql/lineage.go +0 -107
- package/plugins/extract-sql/main.go +0 -141
- package/plugins/extract-sql/maps.go +0 -564
- package/plugins/extract-sql/maps_java.go +0 -117
- package/plugins/extract-sql/maps_rust.go +0 -333
- package/plugins/extract-sql/maps_rust_test.go +0 -70
- package/plugins/extract-sql/maps_test.go +0 -204
- package/plugins/extract-sql/maps_ts.go +0 -398
- package/plugins/extract-sql/maps_ts_test.go +0 -136
- package/plugins/extract-sql/projection.go +0 -70
- package/plugins/extract-sql/projection_test.go +0 -50
- package/plugins/extract-sql/store.go +0 -420
- package/plugins/extract-sql/store_test.go +0 -233
- package/plugins/extract-sql/view.go +0 -295
- package/plugins/extract-watermill/describe.go +0 -19
- package/plugins/extract-watermill/describe_test.go +0 -11
- package/plugins/extract-watermill/extract.go +0 -1228
- package/plugins/extract-watermill/extract_test.go +0 -234
- package/plugins/extract-watermill/main.go +0 -41
- package/plugins/extract-wsdl/describe.go +0 -19
- package/plugins/extract-wsdl/describe_test.go +0 -11
- package/plugins/extract-wsdl/extract.go +0 -148
- package/plugins/extract-wsdl/extract_test.go +0 -45
- package/plugins/extract-wsdl/main.go +0 -53
- package/plugins/fetch-bsr/auth.go +0 -114
- package/plugins/fetch-bsr/bsr.go +0 -240
- package/plugins/fetch-bsr/cache.go +0 -66
- package/plugins/fetch-bsr/describe.go +0 -19
- package/plugins/fetch-bsr/fetch.go +0 -192
- package/plugins/fetch-bsr/fetch_test.go +0 -465
- package/plugins/fetch-bsr/lock.go +0 -71
- package/plugins/fetch-bsr/main.go +0 -117
- package/plugins/fetch-csr/auth.go +0 -95
- package/plugins/fetch-csr/cache.go +0 -69
- package/plugins/fetch-csr/describe.go +0 -19
- package/plugins/fetch-csr/describe_test.go +0 -11
- package/plugins/fetch-csr/fetch.go +0 -254
- package/plugins/fetch-csr/fetch_test.go +0 -494
- package/plugins/fetch-csr/lock.go +0 -95
- package/plugins/fetch-csr/main.go +0 -122
- package/plugins/fetch-csr/registry.go +0 -209
- package/plugins/fetch-git/cache.go +0 -65
- package/plugins/fetch-git/describe.go +0 -19
- package/plugins/fetch-git/describe_test.go +0 -11
- package/plugins/fetch-git/fetch.go +0 -181
- package/plugins/fetch-git/fetch_test.go +0 -332
- package/plugins/fetch-git/git.go +0 -169
- package/plugins/fetch-git/lock.go +0 -72
- package/plugins/fetch-git/main.go +0 -127
- package/plugins/fetch-git/offline.go +0 -39
- package/plugins/fetch-git/pin.go +0 -90
- package/plugins/fetch-git/pin_test.go +0 -74
- package/plugins/gen-backstage/describe.go +0 -17
- package/plugins/gen-backstage/main.go +0 -22
- package/plugins/gen-backstage/plugin.go +0 -473
- package/plugins/gen-backstage/plugin_test.go +0 -145
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +0 -162
- package/plugins/gen-markdown/aggregate.go +0 -362
- package/plugins/gen-markdown/canonical.go +0 -93
- package/plugins/gen-markdown/context.go +0 -90
- package/plugins/gen-markdown/coverage_test.go +0 -89
- package/plugins/gen-markdown/describe.go +0 -19
- package/plugins/gen-markdown/describe_test.go +0 -11
- package/plugins/gen-markdown/external.go +0 -71
- package/plugins/gen-markdown/flow.go +0 -277
- package/plugins/gen-markdown/glossary.go +0 -76
- package/plugins/gen-markdown/glossary_test.go +0 -148
- package/plugins/gen-markdown/llms.go +0 -302
- package/plugins/gen-markdown/main.go +0 -22
- package/plugins/gen-markdown/markdown.go +0 -254
- package/plugins/gen-markdown/markdown_test.go +0 -100
- package/plugins/gen-markdown/module.go +0 -107
- package/plugins/gen-markdown/plugin.go +0 -39
- package/plugins/gen-markdown/quality_test.go +0 -176
- package/plugins/gen-markdown/redis_test.go +0 -38
- package/plugins/gen-markdown/render.go +0 -344
- package/plugins/gen-markdown/render_test.go +0 -186
- package/plugins/gen-markdown/service.go +0 -456
- package/plugins/gen-markdown/source.go +0 -177
- package/plugins/gen-markdown/store.go +0 -201
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +0 -17
- package/plugins/gen-mermaid/main.go +0 -22
- package/plugins/gen-mermaid/plugin.go +0 -104
- package/plugins/gen-mermaid/plugin_test.go +0 -33
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +0 -261
- package/plugins/openapi/ids_test.go +0 -98
- package/plugins/verify-codeowners/describe.go +0 -19
- package/plugins/verify-codeowners/describe_test.go +0 -11
- package/plugins/verify-codeowners/main.go +0 -75
- package/plugins/verify-codeowners/match.go +0 -85
- package/plugins/verify-codeowners/match_test.go +0 -47
- package/plugins/verify-codeowners/owners.go +0 -164
- package/plugins/verify-codeowners/owners_test.go +0 -225
- package/plugins/verify-codeowners/parse.go +0 -90
- package/plugins/verify-codeowners/parse_test.go +0 -62
- package/plugins/verify-otel/describe.go +0 -19
- package/plugins/verify-otel/describe_test.go +0 -11
- package/plugins/verify-otel/main.go +0 -53
- package/plugins/verify-otel/match.go +0 -336
- package/plugins/verify-otel/otlp.go +0 -200
- package/plugins/verify-otel/verify.go +0 -734
- package/plugins/verify-otel/verify_test.go +0 -460
- /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
- /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
package/src/pages/Settings.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useDocumentTitle } from "../app/title";
|
|
2
2
|
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
3
|
-
import { Link } from "react-router";
|
|
3
|
+
import { Link, Navigate, NavLink, Route, Routes } from "react-router";
|
|
4
4
|
import {
|
|
5
5
|
ArrowLeft,
|
|
6
6
|
Box,
|
|
@@ -10,24 +10,14 @@ import {
|
|
|
10
10
|
FolderGit2,
|
|
11
11
|
KeyRound,
|
|
12
12
|
LoaderCircle,
|
|
13
|
-
Moon,
|
|
14
13
|
Play,
|
|
15
14
|
Plus,
|
|
16
|
-
Rows2,
|
|
17
|
-
Rows4,
|
|
18
15
|
ShieldCheck,
|
|
19
|
-
Sun,
|
|
20
16
|
Terminal,
|
|
21
17
|
Trash2,
|
|
22
18
|
X,
|
|
23
19
|
} from "lucide-react";
|
|
24
20
|
import { catalog, catalogSources } from "../data";
|
|
25
|
-
import { useDensity } from "../app/density";
|
|
26
|
-
import { useEditorStore } from "../components/EditorLink";
|
|
27
|
-
import { Select } from "../components/Select";
|
|
28
|
-
import { EDITORS } from "../lib/editor-link";
|
|
29
|
-
import { parseEditor } from "../lib/editor-prefs";
|
|
30
|
-
import { useTheme } from "../app/theme";
|
|
31
21
|
import { useToastStore } from "../app/toast";
|
|
32
22
|
import { absoluteTime, plural, relativeTime } from "../lib/format";
|
|
33
23
|
import { setupInfo as staticSetupInfo } from "../lib/setup-info";
|
|
@@ -60,8 +50,8 @@ import { paths } from "../routes";
|
|
|
60
50
|
import { Empty, SectionTitle } from "../components/PageHeader";
|
|
61
51
|
import { Modal } from "../components/Overlay";
|
|
62
52
|
import { MachineDocs } from "../components/MachineDocs";
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
53
|
+
import { DeliverySettings } from "./settings/DeliverySettings";
|
|
54
|
+
import { PreferencesSettings } from "./settings/PreferencesSettings";
|
|
65
55
|
|
|
66
56
|
type Health = "healthy" | "changed" | "failed" | "unchecked";
|
|
67
57
|
|
|
@@ -339,7 +329,7 @@ function ProjectCard({ project }: { project: SetupProject }) {
|
|
|
339
329
|
</dl>
|
|
340
330
|
|
|
341
331
|
<div className="mt-4 flex flex-wrap gap-1.5" aria-label="Active plugins">
|
|
342
|
-
{pluginNames.map((name) => <
|
|
332
|
+
{pluginNames.map((name) => <Link key={name} to={`${paths.settingsPipeline()}#plugin-${name}`} className="chip border-line-strong hover:border-accent hover:text-accent">{name}</Link>)}
|
|
343
333
|
</div>
|
|
344
334
|
|
|
345
335
|
<details className="group mt-4 border-t border-line pt-3">
|
|
@@ -378,7 +368,9 @@ function Runtime({ plugin, icon = false }: { plugin: SetupPlugin; icon?: boolean
|
|
|
378
368
|
const [Icon, tone, name] =
|
|
379
369
|
plugin.runtime === "wasm"
|
|
380
370
|
? ([ShieldCheck, "text-verified", "WASM sandbox"] as const)
|
|
381
|
-
:
|
|
371
|
+
: plugin.runtime === "host"
|
|
372
|
+
? ([Terminal, "text-declared", "in the host"] as const)
|
|
373
|
+
: ([Terminal, "text-declared", "host process"] as const);
|
|
382
374
|
return icon ? (
|
|
383
375
|
<span className={tone} title={name}>
|
|
384
376
|
<Icon size={14} aria-hidden />
|
|
@@ -469,7 +461,7 @@ function PluginsList() {
|
|
|
469
461
|
<div><div className="label mb-2">last run</div><PipelineSteps steps={runSteps} /></div>
|
|
470
462
|
<div className="space-y-4">
|
|
471
463
|
<div><div className="label mb-2">runtime</div><p className="mono"><Runtime plugin={plugin} /></p></div>
|
|
472
|
-
<div><div className="label mb-2">used by</div>{plugin.projectIds.length > 0 ? <div className="flex flex-wrap gap-1.5">{plugin.projectIds.map((id) => <
|
|
464
|
+
<div><div className="label mb-2">used by</div>{plugin.projectIds.length > 0 ? <div className="flex flex-wrap gap-1.5">{plugin.projectIds.map((id) => <Link key={id} to={`${paths.settingsProjects()}#project-${id}`} className="chip border-line-strong hover:border-accent hover:text-accent">{projectNames.get(id) ?? id}</Link>)}</div> : <p className="mono text-muted">{plugin.stepCount > 0 ? "Estate-wide catalog" : "No pipeline step uses this plugin."}</p>}</div>
|
|
473
465
|
{outputs.length > 0 ? <div><div className="label mb-2">outputs</div><ul className="mono space-y-1 text-muted">{outputs.map((output) => <li key={output} className="truncate"><FileLink path={output} /></li>)}</ul></div> : null}
|
|
474
466
|
</div>
|
|
475
467
|
</div>
|
|
@@ -483,54 +475,6 @@ function PluginsList() {
|
|
|
483
475
|
);
|
|
484
476
|
}
|
|
485
477
|
|
|
486
|
-
/**
|
|
487
|
-
* Which editor the "edit ↗" links hand a file to. The links exist only in
|
|
488
|
-
* local mode; the choice is offered everywhere, because a reader sets it once
|
|
489
|
-
* and the static build they open tomorrow is the same browser.
|
|
490
|
-
*/
|
|
491
|
-
function EditorChoice() {
|
|
492
|
-
const editor = useEditorStore((s) => s.editor);
|
|
493
|
-
const setEditor = useEditorStore((s) => s.set);
|
|
494
|
-
const options = EDITORS.map((e) => ({ value: e.id, label: e.name }));
|
|
495
|
-
return (
|
|
496
|
-
<div className="rounded-card border border-line p-card shadow-xs">
|
|
497
|
-
<div className="label mb-3">open source in</div>
|
|
498
|
-
<Select
|
|
499
|
-
value={editor}
|
|
500
|
-
options={options}
|
|
501
|
-
onChange={(value) => setEditor(parseEditor(value))}
|
|
502
|
-
label="Editor for source links"
|
|
503
|
-
menuWidth={200}
|
|
504
|
-
/>
|
|
505
|
-
<p className="mono mt-2 text-muted">Used by the edit links in local mode.</p>
|
|
506
|
-
</div>
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function Appearance() {
|
|
511
|
-
const { theme, toggle: toggleTheme } = useTheme();
|
|
512
|
-
const { density, toggle: toggleDensity } = useDensity();
|
|
513
|
-
return (
|
|
514
|
-
<div className="grid gap-grid sm:grid-cols-2 xl:grid-cols-3">
|
|
515
|
-
<div className="rounded-card border border-line p-card shadow-xs">
|
|
516
|
-
<div className="label mb-3">theme</div>
|
|
517
|
-
<div className="seg inline-flex" role="group" aria-label="Theme">
|
|
518
|
-
<button type="button" aria-pressed={theme === "dark"} onClick={() => theme !== "dark" && toggleTheme()} className={`flex items-center gap-1.5 ${theme === "dark" ? "is-on" : ""}`}><Moon size={15} aria-hidden /> dark</button>
|
|
519
|
-
<button type="button" aria-pressed={theme === "light"} onClick={() => theme !== "light" && toggleTheme()} className={`flex items-center gap-1.5 ${theme === "light" ? "is-on" : ""}`}><Sun size={15} aria-hidden /> light</button>
|
|
520
|
-
</div>
|
|
521
|
-
</div>
|
|
522
|
-
<div className="rounded-card border border-line p-card shadow-xs">
|
|
523
|
-
<div className="label mb-3">row density</div>
|
|
524
|
-
<div className="seg inline-flex" role="group" aria-label="Row density">
|
|
525
|
-
<button type="button" aria-pressed={density === "comfortable"} onClick={() => density !== "comfortable" && toggleDensity()} className={`flex items-center gap-1.5 ${density === "comfortable" ? "is-on" : ""}`}><Rows4 size={15} aria-hidden /> comfortable</button>
|
|
526
|
-
<button type="button" aria-pressed={density === "compact"} onClick={() => density !== "compact" && toggleDensity()} className={`flex items-center gap-1.5 ${density === "compact" ? "is-on" : ""}`}><Rows2 size={15} aria-hidden /> compact</button>
|
|
527
|
-
</div>
|
|
528
|
-
</div>
|
|
529
|
-
<EditorChoice />
|
|
530
|
-
</div>
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
478
|
const FIELD = "mono w-full rounded-control border border-line bg-canvas px-3 py-2 text-ink outline-none focus:border-accent";
|
|
535
479
|
|
|
536
480
|
function Field({ label, value, onChange, placeholder, required = false }: { label: string; value: string; onChange: (value: string) => void; placeholder?: string; required?: boolean }) {
|
|
@@ -822,7 +766,7 @@ function Wizard({ open, onClose, onAdded, onRunStarted }: { open: boolean; onClo
|
|
|
822
766
|
<div className="space-y-5">
|
|
823
767
|
<div className={`rounded-control border px-3 py-3 ${trial ? "border-verified bg-surface" : finished?.status === "failed" ? "border-unresolved" : "border-line bg-surface"}`}>
|
|
824
768
|
<div className="flex items-start gap-3">{trial ? <Check size={18} className="mt-0.5 shrink-0 text-verified" /> : finished?.status === "failed" ? <CircleAlert size={18} className="mt-0.5 shrink-0 text-unresolved" /> : <LoaderCircle size={18} className="mt-0.5 shrink-0 animate-spin text-accent" />}<div><div className="font-medium text-ink">{trial ? "Extraction succeeded — repository unchanged" : finished?.status === "failed" ? "Trial extraction failed" : activeStep ? `Running ${activeStep.plugin}` : "Creating an isolated workspace…"}</div><p className="mt-0.5 text-muted">{trial ? "These results came from real catalog fragments. Apply is now safe to continue." : "Portolan is running the selected extractors without writing to portolan.json or your project."}</p></div></div>
|
|
825
|
-
{trial?.previewUrl ? <a className="
|
|
769
|
+
{trial?.previewUrl ? <a className="product-primary mt-3 inline-flex" href={trial.previewUrl} target="_blank" rel="noreferrer">Open catalog preview ↗</a> : null}
|
|
826
770
|
{trial?.previewError ? <p className="mt-2 text-declared">The extraction is valid, but the temporary site could not start: {trial.previewError}</p> : null}
|
|
827
771
|
{!finished ? <><div className="mt-3 h-1.5 overflow-hidden rounded-full bg-canvas"><div className="h-full bg-accent transition-[width]" style={{ width: `${percent}%` }} /></div><div className="mono mt-1 text-right text-muted">{completedSteps.length} / {totalSteps || "?"} steps</div></> : null}
|
|
828
772
|
</div>
|
|
@@ -837,12 +781,12 @@ function Wizard({ open, onClose, onAdded, onRunStarted }: { open: boolean; onClo
|
|
|
837
781
|
</div>
|
|
838
782
|
<div className="flex flex-wrap justify-end gap-2 border-t border-line px-5 py-4">
|
|
839
783
|
<button type="button" className="tbtn" onClick={onClose} disabled={busy || trialRunning}>Cancel</button>
|
|
840
|
-
{stage === "source" ? <button key={repositoryError?.retryable ? "retry-inspection" : source} type="button" className="
|
|
841
|
-
{stage === "scope" ? <button key={repositoryError?.retryable ? "retry-component" : "review-components"} type="button" className="
|
|
842
|
-
{stage === "configure" ? <button type="button" className="
|
|
784
|
+
{stage === "source" ? <button key={repositoryError?.retryable ? "retry-inspection" : source} type="button" className="product-primary" aria-label={repositoryError?.retryable ? credentialToken.trim() ? "Save token and retry" : "Retry inspection" : source === "external" ? "Inspect repository" : "Detect project"} onClick={() => void (repositoryError?.retryable ? credentialToken.trim() ? authenticateRepository() : retryInspection() : detect())} disabled={busy || (source === "local" ? !path.trim() : !repository.trim())}>{busy ? <LoaderCircle size={15} className="animate-spin" /> : null} {repositoryError?.retryable ? credentialToken.trim() ? "Save token & retry" : "Retry inspection" : source === "external" ? "Inspect repository" : "Detect project"}</button> : null}
|
|
785
|
+
{stage === "scope" ? <button key={repositoryError?.retryable ? "retry-component" : "review-components"} type="button" className="product-primary" aria-label={repositoryError?.retryable ? credentialToken.trim() ? "Save token and retry" : "Retry inspection" : `Review ${selectedComponents.length || "selected"} ${selectedComponents.length === 1 ? "component" : "components"}`} onClick={() => void (repositoryError?.retryable ? credentialToken.trim() ? authenticateRepository() : retryInspection() : reviewSelectedComponents())} disabled={busy || (!repositoryError?.retryable && !selectedComponents.length)}>{busy ? <LoaderCircle size={15} className="animate-spin" /> : <Play size={15} />} {repositoryError?.retryable ? credentialToken.trim() ? "Save token & retry" : "Retry inspection" : <>Review {selectedComponents.length || "selected"} {selectedComponents.length === 1 ? "component" : "components"}</>}</button> : null}
|
|
786
|
+
{stage === "configure" ? <button type="button" className="product-primary" onClick={() => void runTrial()} disabled={busy || !draft?.plugins.length}>{busy ? <LoaderCircle size={15} className="animate-spin" /> : <Play size={15} />} Run trial extraction</button> : null}
|
|
843
787
|
{stage === "trial" && trialRunning ? <button type="button" className="tbtn" onClick={() => trialRunId && void cancelGeneration(trialRunId)}>Cancel trial</button> : null}
|
|
844
|
-
{stage === "trial" && finished && !trial ? <button type="button" className="
|
|
845
|
-
{stage === "trial" && trial ? pendingComponents.length > 0 ? <button type="button" className="
|
|
788
|
+
{stage === "trial" && finished && !trial ? <button type="button" className="product-primary" onClick={back}>Back to configuration</button> : null}
|
|
789
|
+
{stage === "trial" && trial ? pendingComponents.length > 0 ? <button type="button" className="product-primary" onClick={() => void applyTrial(false)} disabled={busy}>{busy ? <LoaderCircle size={15} className="animate-spin" /> : <Play size={15} />} Add & review next</button> : <><button type="button" className="tbtn" onClick={() => void applyTrial(false)} disabled={busy}>Add without generating</button><button type="button" className="product-primary" onClick={() => void applyTrial(true)} disabled={busy}>{busy ? <LoaderCircle size={15} className="animate-spin" /> : <Play size={15} />} Add & generate</button></> : null}
|
|
846
790
|
</div>
|
|
847
791
|
</Modal>
|
|
848
792
|
);
|
|
@@ -878,55 +822,136 @@ function RunDialog({ runId, open, onClose, onFinished, onApply }: { runId: strin
|
|
|
878
822
|
{preview ? <section className="mt-5"><div className="label mb-2">generated diff · {preview.totalFiles} {plural(preview.totalFiles, "file")}</div>{preview.files.length ? <div className="divide-y divide-line overflow-hidden rounded-control border border-line">{preview.files.map((file) => <details key={file.path} className="group"><summary className="flex cursor-pointer items-center gap-2 px-3 py-2 hover:bg-surface"><span className={`chip ${file.status === "removed" ? "status-unresolved" : file.status === "added" ? "status-verified" : "status-declared"}`}>{file.status}</span><span className="mono truncate text-ink">{file.path}</span><ChevronDown size={15} className="ml-auto shrink-0 text-muted transition-transform group-open:rotate-180" /></summary><pre className="mono max-h-80 overflow-auto whitespace-pre p-3 text-muted bg-surface">{file.diff || "Binary file changed"}</pre></details>)}</div> : <div className="rounded-control border border-line bg-surface px-3 py-3 text-muted">Generated documentation is already up to date.</div>}{preview.truncated ? <p className="mt-2 text-muted">Showing the first {preview.files.length} changed files.</p> : null}</section> : null}
|
|
879
823
|
{logs.length ? <details className="mt-4"><summary className="cursor-pointer text-muted">Generator log · {logs.length} lines</summary><pre className="mono mt-2 max-h-48 overflow-auto whitespace-pre-wrap rounded-control bg-surface p-3 text-muted">{logs.map((event) => event.type === "log" ? event.message : "").join("\n")}</pre></details> : null}
|
|
880
824
|
</div>
|
|
881
|
-
<div className="flex justify-end gap-2 border-t border-line px-5 py-4">{finished ? <><button type="button" className="tbtn" onClick={onClose}>{preview?.files.length ? "Cancel" : "Done"}</button>{preview?.files.length && finished.status === "ok" && runId ? <button type="button" className="
|
|
825
|
+
<div className="flex justify-end gap-2 border-t border-line px-5 py-4">{finished ? <><button type="button" className="tbtn" onClick={onClose}>{preview?.files.length ? "Cancel" : "Done"}</button>{preview?.files.length && finished.status === "ok" && runId ? <button type="button" className="product-primary" onClick={() => onApply(runId)}><Play size={15} /> Apply changes</button> : null}</> : <button type="button" className="tbtn" onClick={() => runId && void cancelGeneration(runId)}>Cancel generation</button>}</div>
|
|
882
826
|
</Modal>
|
|
883
827
|
);
|
|
884
828
|
}
|
|
885
829
|
|
|
886
|
-
|
|
830
|
+
const SETTINGS_LINKS = [
|
|
831
|
+
["Overview", paths.settings()],
|
|
832
|
+
["Projects", paths.settingsProjects()],
|
|
833
|
+
["Pipeline", paths.settingsPipeline()],
|
|
834
|
+
["Delivery", paths.settingsDelivery()],
|
|
835
|
+
["Preferences", paths.settingsPreferences()],
|
|
836
|
+
] as const;
|
|
837
|
+
|
|
838
|
+
function SettingsNav() {
|
|
839
|
+
return (
|
|
840
|
+
<nav className="mt-5 border-b border-line" aria-label="Settings sections">
|
|
841
|
+
<div className="tab-scroll flex overflow-x-auto">
|
|
842
|
+
{SETTINGS_LINKS.map(([label, to], index) => (
|
|
843
|
+
<NavLink
|
|
844
|
+
key={to}
|
|
845
|
+
to={to}
|
|
846
|
+
end={index === 0}
|
|
847
|
+
className={({ isActive }) =>
|
|
848
|
+
`mono shrink-0 border-b-2 px-3 py-2 transition-colors ${
|
|
849
|
+
isActive
|
|
850
|
+
? "border-accent text-ink"
|
|
851
|
+
: "border-transparent text-muted hover:text-ink"
|
|
852
|
+
}`
|
|
853
|
+
}
|
|
854
|
+
>
|
|
855
|
+
{label}
|
|
856
|
+
</NavLink>
|
|
857
|
+
))}
|
|
858
|
+
</div>
|
|
859
|
+
</nav>
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function OverviewSettings() {
|
|
887
864
|
const setupInfo = useSetup();
|
|
888
865
|
const active = setupInfo.plugins.filter((plugin) => plugin.stepCount > 0);
|
|
889
866
|
return (
|
|
890
|
-
|
|
891
|
-
<
|
|
892
|
-
|
|
893
|
-
<
|
|
894
|
-
|
|
895
|
-
<
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
<
|
|
908
|
-
<
|
|
909
|
-
<
|
|
910
|
-
|
|
911
|
-
|
|
867
|
+
<>
|
|
868
|
+
<BuildHealth />
|
|
869
|
+
<div className="mt-4 grid grid-cols-2 gap-grid lg:grid-cols-4">
|
|
870
|
+
<Metric value={setupInfo.projects.length} label="project" />
|
|
871
|
+
<Metric value={active.length} label="active plugin" />
|
|
872
|
+
<Metric value={setupInfo.steps.length} label="pipeline step" />
|
|
873
|
+
<Metric value={catalogSources.length} label="catalog source" />
|
|
874
|
+
</div>
|
|
875
|
+
<div className="mt-section grid gap-grid sm:grid-cols-2">
|
|
876
|
+
<Link to={paths.settingsProjects()} className="card">
|
|
877
|
+
<div className="font-semibold text-ink">Projects</div>
|
|
878
|
+
<p className="mt-1 text-muted">Sources, scopes and extraction coverage for {setupInfo.projects.length} {plural(setupInfo.projects.length, "project")}.</p>
|
|
879
|
+
</Link>
|
|
880
|
+
<Link to={paths.settingsPipeline()} className="card">
|
|
881
|
+
<div className="font-semibold text-ink">Pipeline</div>
|
|
882
|
+
<p className="mt-1 text-muted">Inspect {active.length} active {plural(active.length, "plugin")} and their generated outputs.</p>
|
|
883
|
+
</Link>
|
|
884
|
+
<Link to={paths.settingsDelivery()} className="card">
|
|
885
|
+
<div className="font-semibold text-ink">Delivery</div>
|
|
886
|
+
<p className="mt-1 text-muted">Install review checks and static catalog publishing for GitHub or GitLab.</p>
|
|
887
|
+
</Link>
|
|
888
|
+
<Link to={paths.settingsPreferences()} className="card">
|
|
889
|
+
<div className="font-semibold text-ink">Preferences</div>
|
|
890
|
+
<p className="mt-1 text-muted">Theme, row density, source editor and Ask the catalog.</p>
|
|
891
|
+
</Link>
|
|
892
|
+
</div>
|
|
893
|
+
</>
|
|
894
|
+
);
|
|
895
|
+
}
|
|
912
896
|
|
|
913
|
-
|
|
897
|
+
function ProjectsSettings({ local, onAdd }: { local: boolean; onAdd: () => void }) {
|
|
898
|
+
const setupInfo = useSetup();
|
|
899
|
+
return (
|
|
900
|
+
<section>
|
|
901
|
+
<SectionTitle right={local ? "editable in local mode" : "declared in portolan.json"}>Projects</SectionTitle>
|
|
902
|
+
{setupInfo.projects.length === 0 && !local ? <Empty>portolan.json names no projects — every input here is the estate's own</Empty> : <div className="grid gap-grid xl:grid-cols-2">{setupInfo.projects.map((project) => <ProjectCard key={project.id} project={project} />)}{local ? <AddProjectCard onClick={onAdd} /> : null}</div>}
|
|
903
|
+
</section>
|
|
904
|
+
);
|
|
905
|
+
}
|
|
914
906
|
|
|
915
|
-
|
|
907
|
+
function PipelineSettings() {
|
|
908
|
+
const setupInfo = useSetup();
|
|
909
|
+
const active = setupInfo.plugins.filter((plugin) => plugin.stepCount > 0);
|
|
910
|
+
return (
|
|
911
|
+
<div className="space-y-section">
|
|
912
|
+
<section>
|
|
913
|
+
<SectionTitle right={`${active.length} of ${setupInfo.plugins.length} active`}>Plugins</SectionTitle>
|
|
914
|
+
<PluginsList />
|
|
915
|
+
<p className="mono mt-2 text-muted">WASM runs without network or environment access, and a generator without a filesystem. A host process runs with the permissions of the build; a plugin in the host is Portolan's own code doing what needs a socket, such as fetching another repository.</p>
|
|
916
|
+
</section>
|
|
917
|
+
<details className="rounded-card border border-line shadow-xs">
|
|
918
|
+
<summary className="cursor-pointer select-none px-4 py-3 font-semibold text-ink">Advanced build inputs</summary>
|
|
919
|
+
<div className="border-t border-line p-4">
|
|
920
|
+
<div className="label mb-2">catalog source patterns</div>
|
|
921
|
+
{setupInfo.sources.length === 0 ? <Empty>no source patterns declared</Empty> : <ul className="mono space-y-1 text-muted">{setupInfo.sources.map((source) => <li key={source}>{source}</li>)}</ul>}
|
|
922
|
+
<div className="label mt-5 mb-2">pipeline</div>
|
|
923
|
+
{setupInfo.steps.length === 0 ? <Empty>no pipeline steps declared</Empty> : <div className="space-y-1">{setupInfo.steps.map((step, index) => <div key={`${step.phase}:${step.plugin}:${step.input ?? "catalog"}:${index}`} className="mono grid gap-x-3 text-muted sm:grid-cols-[5rem_9rem_1fr]"><span>{step.phase}</span><span className="text-ink">{step.plugin}</span><span className="truncate" title={step.input ?? "merged catalog"}>{step.input ?? "merged catalog"} → {step.output}</span></div>)}</div>}
|
|
924
|
+
<div className="label mt-5 mb-2">generated documentation</div>
|
|
925
|
+
<MachineDocs />
|
|
926
|
+
</div>
|
|
927
|
+
</details>
|
|
928
|
+
</div>
|
|
929
|
+
);
|
|
930
|
+
}
|
|
916
931
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
<
|
|
925
|
-
<MachineDocs />
|
|
932
|
+
function SettingsContent({ local, onAdd, onGenerate }: { local: boolean; onAdd: () => void; onGenerate: () => void }) {
|
|
933
|
+
return (
|
|
934
|
+
<div className="h-full overflow-y-auto p-gutter">
|
|
935
|
+
<div className="max-w-table">
|
|
936
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
937
|
+
<div>
|
|
938
|
+
<div className="flex items-center gap-2"><h1 className="text-lg font-semibold">Settings</h1>{local ? <span className="chip status-verified">local mode</span> : null}</div>
|
|
939
|
+
<p className="mt-1 max-w-prose text-muted">Configure projects, extraction, delivery automation and local preferences. {local ? "This local session can write reviewed changes." : "Build configuration is read-only here."}</p>
|
|
926
940
|
</div>
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
<
|
|
941
|
+
{local ? <button type="button" className="product-primary" onClick={onGenerate}><Play size={15} /> Preview generated diff</button> : null}
|
|
942
|
+
</div>
|
|
943
|
+
<SettingsNav />
|
|
944
|
+
<div className="mt-section">
|
|
945
|
+
<Routes>
|
|
946
|
+
<Route index element={<OverviewSettings />} />
|
|
947
|
+
<Route path="projects" element={<ProjectsSettings local={local} onAdd={onAdd} />} />
|
|
948
|
+
<Route path="pipeline" element={<PipelineSettings />} />
|
|
949
|
+
<Route path="delivery" element={<section><SectionTitle right={local ? "preview before writing" : "local mode required"}>Delivery presets</SectionTitle><DeliverySettings local={local} /></section>} />
|
|
950
|
+
<Route path="preferences" element={<PreferencesSettings />} />
|
|
951
|
+
<Route path="*" element={<Navigate to={paths.settings()} replace />} />
|
|
952
|
+
</Routes>
|
|
953
|
+
</div>
|
|
954
|
+
<div className="mono mt-section flex items-center gap-2 pb-section text-muted"><Box size={14} aria-hidden />{local ? "Changes are written only after preview; generated files remain reviewable in git." : "Configuration is embedded at build time; changing it requires a new catalog build."}</div>
|
|
930
955
|
</div>
|
|
931
956
|
</div>
|
|
932
957
|
);
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Check,
|
|
4
|
+
ChevronDown,
|
|
5
|
+
CircleAlert,
|
|
6
|
+
FolderGit2,
|
|
7
|
+
LoaderCircle,
|
|
8
|
+
Play,
|
|
9
|
+
ShieldCheck,
|
|
10
|
+
} from "lucide-react";
|
|
11
|
+
import { useToastStore } from "../../app/toast";
|
|
12
|
+
import {
|
|
13
|
+
installDeliveryPreset,
|
|
14
|
+
previewDeliveryPreset,
|
|
15
|
+
} from "../../lib/local-api";
|
|
16
|
+
import type {
|
|
17
|
+
DeliveryPreset,
|
|
18
|
+
DeliveryProvider,
|
|
19
|
+
} from "../../lib/local-api";
|
|
20
|
+
|
|
21
|
+
const STATUS_CLASS: Record<
|
|
22
|
+
DeliveryPreset["files"][number]["status"],
|
|
23
|
+
string
|
|
24
|
+
> = {
|
|
25
|
+
added: "status-verified",
|
|
26
|
+
changed: "status-declared",
|
|
27
|
+
unchanged: "status-verified",
|
|
28
|
+
conflict: "status-unresolved",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function DeliverySettings({ local }: { local: boolean }) {
|
|
32
|
+
const [plan, setPlan] = useState<DeliveryPreset | null>(null);
|
|
33
|
+
const [loading, setLoading] = useState(local);
|
|
34
|
+
const [installing, setInstalling] = useState(false);
|
|
35
|
+
const [error, setError] = useState("");
|
|
36
|
+
const say = useToastStore((state) => state.say);
|
|
37
|
+
|
|
38
|
+
const load = useCallback(async (provider?: DeliveryProvider) => {
|
|
39
|
+
if (!local) return;
|
|
40
|
+
setLoading(true);
|
|
41
|
+
setError("");
|
|
42
|
+
try {
|
|
43
|
+
setPlan(await previewDeliveryPreset(provider));
|
|
44
|
+
} catch (cause) {
|
|
45
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
46
|
+
} finally {
|
|
47
|
+
setLoading(false);
|
|
48
|
+
}
|
|
49
|
+
}, [local]);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
void load();
|
|
53
|
+
}, [load]);
|
|
54
|
+
|
|
55
|
+
async function install() {
|
|
56
|
+
if (!plan) return;
|
|
57
|
+
setInstalling(true);
|
|
58
|
+
setError("");
|
|
59
|
+
try {
|
|
60
|
+
const installed = await installDeliveryPreset(
|
|
61
|
+
plan.provider,
|
|
62
|
+
plan.revision,
|
|
63
|
+
);
|
|
64
|
+
setPlan(installed);
|
|
65
|
+
say(
|
|
66
|
+
installed.written.length === 1
|
|
67
|
+
? `${installed.written[0]} installed`
|
|
68
|
+
: `${installed.written.length} delivery files installed`,
|
|
69
|
+
);
|
|
70
|
+
} catch (cause) {
|
|
71
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
72
|
+
setError(message);
|
|
73
|
+
say(message);
|
|
74
|
+
} finally {
|
|
75
|
+
setInstalling(false);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!local) {
|
|
80
|
+
return (
|
|
81
|
+
<div className="empty">
|
|
82
|
+
Start <code>portolan dev</code> to preview and install delivery presets
|
|
83
|
+
in this repository.
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const conflicts =
|
|
89
|
+
plan?.files.filter((file) => file.status === "conflict") ?? [];
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="rounded-card border border-line p-card shadow-xs">
|
|
93
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
94
|
+
<div>
|
|
95
|
+
<div className="flex items-center gap-2 font-semibold text-ink">
|
|
96
|
+
<FolderGit2 size={16} className="text-accent" /> Delivery automation
|
|
97
|
+
</div>
|
|
98
|
+
<p className="mt-1 max-w-prose text-muted">
|
|
99
|
+
Install review checks and static catalog publishing directly into
|
|
100
|
+
this repository.
|
|
101
|
+
</p>
|
|
102
|
+
</div>
|
|
103
|
+
<div
|
|
104
|
+
className="seg inline-flex"
|
|
105
|
+
role="group"
|
|
106
|
+
aria-label="Delivery provider"
|
|
107
|
+
>
|
|
108
|
+
{(["github", "gitlab"] as const).map((provider) => (
|
|
109
|
+
<button
|
|
110
|
+
key={provider}
|
|
111
|
+
type="button"
|
|
112
|
+
disabled={loading || installing}
|
|
113
|
+
aria-pressed={plan?.provider === provider}
|
|
114
|
+
onClick={() => void load(provider)}
|
|
115
|
+
className={`capitalize ${plan?.provider === provider ? "is-on" : ""}`}
|
|
116
|
+
>
|
|
117
|
+
{provider === "github" ? "GitHub" : "GitLab"}
|
|
118
|
+
</button>
|
|
119
|
+
))}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{loading ? (
|
|
124
|
+
<div className="mt-4 flex items-center gap-2 text-muted">
|
|
125
|
+
<LoaderCircle size={15} className="animate-spin" /> Inspecting
|
|
126
|
+
repository…
|
|
127
|
+
</div>
|
|
128
|
+
) : error && !plan ? (
|
|
129
|
+
<div className="mt-4 rounded-control border border-line bg-surface px-3 py-2 text-unresolved">
|
|
130
|
+
{error}
|
|
131
|
+
</div>
|
|
132
|
+
) : plan ? (
|
|
133
|
+
<>
|
|
134
|
+
<div className="mt-4 flex flex-wrap items-center gap-2">
|
|
135
|
+
<span
|
|
136
|
+
className={`chip ${
|
|
137
|
+
plan.status === "conflict"
|
|
138
|
+
? "status-unresolved"
|
|
139
|
+
: plan.status === "update"
|
|
140
|
+
? "status-declared"
|
|
141
|
+
: "status-verified"
|
|
142
|
+
}`}
|
|
143
|
+
>
|
|
144
|
+
{plan.status}
|
|
145
|
+
</span>
|
|
146
|
+
<span className="mono text-muted">
|
|
147
|
+
{plan.repository} · {plan.defaultBranch}
|
|
148
|
+
</span>
|
|
149
|
+
{plan.detectedProvider === plan.provider ? (
|
|
150
|
+
<span className="text-faint">detected from origin</span>
|
|
151
|
+
) : plan.detectedProvider ? (
|
|
152
|
+
<span className="text-faint">
|
|
153
|
+
origin uses {plan.detectedProvider === "github" ? "GitHub" : "GitLab"}
|
|
154
|
+
</span>
|
|
155
|
+
) : (
|
|
156
|
+
<span className="text-faint">provider selected manually</span>
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<ul className="mt-4 grid gap-2 text-muted sm:grid-cols-2">
|
|
161
|
+
{plan.features.map((feature) => (
|
|
162
|
+
<li key={feature} className="flex items-center gap-2">
|
|
163
|
+
<Check size={14} className="shrink-0 text-verified" /> {feature}
|
|
164
|
+
</li>
|
|
165
|
+
))}
|
|
166
|
+
</ul>
|
|
167
|
+
|
|
168
|
+
<div className="mt-4 divide-y divide-line overflow-hidden rounded-control border border-line">
|
|
169
|
+
{plan.files.map((file) => (
|
|
170
|
+
<details
|
|
171
|
+
key={file.path}
|
|
172
|
+
className="group"
|
|
173
|
+
open={file.status === "conflict"}
|
|
174
|
+
>
|
|
175
|
+
<summary className="flex cursor-pointer items-center gap-2 px-3 py-2 hover:bg-surface">
|
|
176
|
+
<span className={`chip ${STATUS_CLASS[file.status]}`}>
|
|
177
|
+
{file.status}
|
|
178
|
+
</span>
|
|
179
|
+
<span className="mono truncate text-ink">{file.path}</span>
|
|
180
|
+
<ChevronDown
|
|
181
|
+
size={15}
|
|
182
|
+
className="ml-auto shrink-0 text-muted transition-transform group-open:rotate-180"
|
|
183
|
+
/>
|
|
184
|
+
</summary>
|
|
185
|
+
<div className="border-t border-line bg-surface">
|
|
186
|
+
{file.message ? (
|
|
187
|
+
<p className="flex items-center gap-2 px-3 pt-3 text-unresolved">
|
|
188
|
+
<CircleAlert size={14} /> {file.message}
|
|
189
|
+
</p>
|
|
190
|
+
) : null}
|
|
191
|
+
<pre className="mono max-h-72 overflow-auto whitespace-pre p-3 text-muted">
|
|
192
|
+
{file.diff || "No changes — this file is up to date."}
|
|
193
|
+
</pre>
|
|
194
|
+
</div>
|
|
195
|
+
</details>
|
|
196
|
+
))}
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{error ? <p className="mt-3 text-unresolved">{error}</p> : null}
|
|
200
|
+
<div className="mt-4 flex flex-wrap items-center justify-between gap-3">
|
|
201
|
+
<p className="text-faint">
|
|
202
|
+
Only files shown above will be written. Existing unmanaged
|
|
203
|
+
workflows are never replaced.
|
|
204
|
+
</p>
|
|
205
|
+
{plan.status === "installed" ? (
|
|
206
|
+
<span className="flex items-center gap-1.5 text-verified">
|
|
207
|
+
<ShieldCheck size={15} /> Preset installed
|
|
208
|
+
</span>
|
|
209
|
+
) : (
|
|
210
|
+
<button
|
|
211
|
+
type="button"
|
|
212
|
+
className="btn-accent"
|
|
213
|
+
disabled={installing || conflicts.length > 0}
|
|
214
|
+
onClick={() => void install()}
|
|
215
|
+
>
|
|
216
|
+
{installing ? (
|
|
217
|
+
<LoaderCircle size={15} className="animate-spin" />
|
|
218
|
+
) : (
|
|
219
|
+
<Play size={15} />
|
|
220
|
+
)}
|
|
221
|
+
{plan.status === "update" ? "Update preset" : "Install preset"}
|
|
222
|
+
</button>
|
|
223
|
+
)}
|
|
224
|
+
</div>
|
|
225
|
+
</>
|
|
226
|
+
) : null}
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
}
|