@lukoweb/apitogo 0.2.22 → 0.2.24
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 +94 -94
- package/cli.js +13 -13
- package/client.d.ts +8 -8
- package/dist/cli/cli.js +561 -232
- package/dist/declarations/cli/platform/client/platform-api.d.ts +18 -0
- package/dist/declarations/config/validators/InputNavigationSchema.d.ts +7 -7
- package/dist/declarations/config/validators/ZudokuConfig.d.ts +1 -1
- package/docs/cli/commands.md +75 -75
- package/docs/cli/go-live.md +123 -123
- package/docs/cli/overview.md +91 -91
- package/docs/cli/setup.md +141 -141
- package/docs/cli/shape.md +127 -127
- package/docs/components/alert.mdx +130 -130
- package/docs/components/badge.mdx +70 -70
- package/docs/components/button.mdx +132 -132
- package/docs/components/callout.mdx +112 -112
- package/docs/components/card.mdx +104 -104
- package/docs/components/checkbox.mdx +72 -72
- package/docs/components/client-only.mdx +79 -79
- package/docs/components/code-tabs.mdx +179 -179
- package/docs/components/dialog.mdx +167 -167
- package/docs/components/head.mdx +200 -200
- package/docs/components/icons.mdx +27 -27
- package/docs/components/input.mdx +96 -96
- package/docs/components/label.mdx +86 -86
- package/docs/components/link.mdx +242 -242
- package/docs/components/markdown.mdx +151 -151
- package/docs/components/mermaid.mdx +81 -81
- package/docs/components/playground.mdx +85 -85
- package/docs/components/secret.mdx +79 -79
- package/docs/components/select.mdx +176 -176
- package/docs/components/shadcn.mdx +73 -73
- package/docs/components/slider.mdx +108 -108
- package/docs/components/slot.mdx +119 -119
- package/docs/components/stepper.mdx +138 -138
- package/docs/components/switch.mdx +96 -96
- package/docs/components/syntax-highlight.mdx +602 -602
- package/docs/components/textarea.mdx +78 -78
- package/docs/components/tooltip.mdx +195 -195
- package/docs/components/typography.mdx +61 -61
- package/docs/components.mdx +60 -60
- package/docs/concepts/auth-provider-api-identities.md +105 -105
- package/docs/configuration/ai-assistants.md +65 -65
- package/docs/configuration/api-catalog.md +109 -109
- package/docs/configuration/api-reference.md +397 -397
- package/docs/configuration/authentication-auth0.md +181 -181
- package/docs/configuration/authentication-azure-ad.md +245 -245
- package/docs/configuration/authentication-clerk.md +117 -117
- package/docs/configuration/authentication-firebase.md +69 -69
- package/docs/configuration/authentication-pingfederate.md +143 -143
- package/docs/configuration/authentication-supabase.md +233 -233
- package/docs/configuration/authentication.md +159 -159
- package/docs/configuration/build-configuration.mdx +147 -147
- package/docs/configuration/docs.md +282 -282
- package/docs/configuration/footer.mdx +118 -118
- package/docs/configuration/gateway.md +221 -221
- package/docs/configuration/llms.md +90 -90
- package/docs/configuration/make-config.mdx +124 -124
- package/docs/configuration/manifest.md +128 -128
- package/docs/configuration/navigation.mdx +421 -421
- package/docs/configuration/overview.md +166 -166
- package/docs/configuration/plans.md +153 -153
- package/docs/configuration/protected-routes.md +150 -150
- package/docs/configuration/search.md +164 -164
- package/docs/configuration/sentry.mdx +44 -44
- package/docs/configuration/site.md +124 -124
- package/docs/configuration/slots.mdx +125 -125
- package/docs/configuration/vite-config.md +18 -18
- package/docs/custom-plugins.md +285 -285
- package/docs/customization/colors-theme.mdx +278 -278
- package/docs/customization/fonts.md +110 -110
- package/docs/customization/landing-page.md +188 -188
- package/docs/deploy/apache-nginx.md +41 -41
- package/docs/deploy/apitogo.md +17 -17
- package/docs/deploy/cloudflare-pages.md +74 -74
- package/docs/deploy/direct-upload.md +17 -17
- package/docs/deploy/github-pages.md +49 -49
- package/docs/deploy/vercel.md +101 -101
- package/docs/deployment.md +9 -9
- package/docs/extending/events.md +124 -124
- package/docs/guides/custom-pages.md +110 -110
- package/docs/guides/environment-variables.md +94 -94
- package/docs/guides/managing-api-keys-and-identities.md +89 -89
- package/docs/guides/mermaid.mdx +70 -70
- package/docs/guides/navigation-rules.mdx +203 -203
- package/docs/guides/processors.mdx +234 -234
- package/docs/guides/static-files.md +55 -55
- package/docs/guides/transforming-examples.md +156 -156
- package/docs/guides/using-multiple-apis.md +87 -87
- package/docs/markdown/admonitions.md +128 -128
- package/docs/markdown/code-blocks.md +196 -196
- package/docs/markdown/frontmatter.md +173 -173
- package/docs/markdown/mdx.md +68 -68
- package/docs/markdown/overview.md +275 -275
- package/docs/mcp/go-live.md +100 -100
- package/docs/mcp/overview.md +76 -76
- package/docs/mcp/setup.md +173 -173
- package/docs/mcp/shape.md +77 -77
- package/docs/mcp/tools.md +102 -102
- package/docs/plugins.md +24 -24
- package/docs/quickstart.md +72 -72
- package/docs/writing.mdx +72 -72
- package/package.json +1 -1
- package/src/app/ZuploBuildConfig.ts +36 -36
- package/src/app/apitogoBrandTheme.ts +47 -47
- package/src/app/defaultTheme.css +77 -77
- package/src/app/demo-cdn.html +37 -37
- package/src/app/demo.html +21 -21
- package/src/app/demo.tsx +77 -77
- package/src/app/entry.client.tsx +107 -107
- package/src/app/entry.server.tsx +160 -160
- package/src/app/env.ts +40 -40
- package/src/app/main.css +400 -400
- package/src/app/main.tsx +167 -167
- package/src/app/polyfills.ts +47 -47
- package/src/app/processRoutes.tsx +51 -51
- package/src/app/sentry.ts +24 -24
- package/src/app/standalone.html +23 -23
- package/src/app/standalone.tsx +60 -60
- package/src/app/utils/createRedirectRoutes.ts +11 -11
- package/src/config/apitogo-config-core.ts +7 -3
- package/src/config/apitogo-config-loader.browser.ts +17 -17
- package/src/config/apitogo-config-loader.node.ts +88 -88
- package/src/config/apitogo-config-loader.ts +6 -6
- package/src/config/build-apitogo-config.ts +61 -61
- package/src/config/config.ts +74 -74
- package/src/config/create-plugin.ts +71 -71
- package/src/config/default-landing-content.ts +149 -149
- package/src/config/file-exists.ts +6 -6
- package/src/config/gateway-limits.ts +128 -128
- package/src/config/landing-manifest.ts +11 -11
- package/src/config/landing-openapi-showcase.ts +124 -124
- package/src/config/loader.ts +375 -375
- package/src/config/local-manifest.ts +545 -545
- package/src/config/resolve-modules.ts +354 -354
- package/src/config/v2/__fixtures__/scenario-a-docs-only.json +6 -6
- package/src/config/v2/__fixtures__/scenario-b-managed-origin.json +14 -14
- package/src/config/v2/__fixtures__/scenario-c-proxy-oidc.json +15 -15
- package/src/config/v2/__fixtures__/scenario-h-full-stack.json +28 -28
- package/src/config/v2/docs-navigation.ts +72 -72
- package/src/config/v2/errors.ts +42 -42
- package/src/config/v2/gateway-reference.ts +220 -220
- package/src/config/v2/gateway-request.ts +123 -123
- package/src/config/v2/home-redirect.ts +98 -98
- package/src/config/v2/index.ts +126 -126
- package/src/config/v2/init.ts +273 -273
- package/src/config/v2/layout.ts +237 -237
- package/src/config/v2/ledger.ts +235 -235
- package/src/config/v2/link-directory.ts +34 -0
- package/src/config/v2/load.ts +70 -70
- package/src/config/v2/manifest.ts +452 -452
- package/src/config/v2/parse.ts +214 -214
- package/src/config/v2/paths.ts +54 -54
- package/src/config/v2/portal-profile.ts +88 -88
- package/src/config/v2/resolve.ts +188 -188
- package/src/config/v2/rules.ts +714 -714
- package/src/config/v2/schema.ts +245 -245
- package/src/config/v2/services.ts +663 -663
- package/src/config/v2/workspace.ts +3 -2
- package/src/config/v2/wrapper.ts +24 -2
- package/src/config/validators/BuildSchema.ts +95 -95
- package/src/config/validators/HeaderNavigationSchema.ts +44 -44
- package/src/config/validators/InputNavigationSchema.test-d.ts +178 -178
- package/src/config/validators/InputNavigationSchema.ts +206 -206
- package/src/config/validators/ModulesSchema.ts +502 -502
- package/src/config/validators/NavigationSchema.ts +352 -352
- package/src/config/validators/ProtectedRoutesSchema.ts +35 -35
- package/src/config/validators/ZudokuConfig.ts +792 -792
- package/src/config/validators/auth.ts +3 -3
- package/src/config/validators/reason-codes.ts +7 -7
- package/src/env.d.ts +11 -11
- package/src/index.ts +83 -83
- package/src/lib/MissingIcon.tsx +22 -22
- package/src/lib/assets/language-icons/bun.tsx +50 -50
- package/src/lib/assets/language-icons/c.tsx +31 -31
- package/src/lib/assets/language-icons/commonlisp.tsx +22 -22
- package/src/lib/assets/language-icons/cpp.tsx +35 -35
- package/src/lib/assets/language-icons/csharp.tsx +35 -35
- package/src/lib/assets/language-icons/css.tsx +36 -36
- package/src/lib/assets/language-icons/dart.tsx +39 -39
- package/src/lib/assets/language-icons/elixir.tsx +19 -19
- package/src/lib/assets/language-icons/go.tsx +19 -19
- package/src/lib/assets/language-icons/graphql.tsx +19 -19
- package/src/lib/assets/language-icons/html.tsx +24 -24
- package/src/lib/assets/language-icons/java.tsx +35 -35
- package/src/lib/assets/language-icons/javascript.tsx +11 -11
- package/src/lib/assets/language-icons/json.tsx +19 -19
- package/src/lib/assets/language-icons/kotlin.tsx +30 -30
- package/src/lib/assets/language-icons/markdown.tsx +19 -19
- package/src/lib/assets/language-icons/mdx.tsx +23 -23
- package/src/lib/assets/language-icons/npm.tsx +15 -15
- package/src/lib/assets/language-icons/objectivec.tsx +23 -23
- package/src/lib/assets/language-icons/ocaml.tsx +34 -34
- package/src/lib/assets/language-icons/php.tsx +19 -19
- package/src/lib/assets/language-icons/pnpm.tsx +32 -32
- package/src/lib/assets/language-icons/powershell.tsx +27 -27
- package/src/lib/assets/language-icons/python.tsx +23 -23
- package/src/lib/assets/language-icons/react.tsx +21 -21
- package/src/lib/assets/language-icons/ruby.tsx +19 -19
- package/src/lib/assets/language-icons/rust.tsx +19 -19
- package/src/lib/assets/language-icons/scala.tsx +19 -19
- package/src/lib/assets/language-icons/shell.tsx +19 -19
- package/src/lib/assets/language-icons/swift.tsx +19 -19
- package/src/lib/assets/language-icons/toml.tsx +23 -23
- package/src/lib/assets/language-icons/typescript.tsx +23 -23
- package/src/lib/assets/language-icons/xml.tsx +19 -19
- package/src/lib/assets/language-icons/yaml.tsx +23 -23
- package/src/lib/assets/language-icons/yarn.tsx +17 -17
- package/src/lib/assets/language-icons/zig.tsx +32 -32
- package/src/lib/auth/issuer.ts +34 -34
- package/src/lib/authentication/AuthenticationPlugin.tsx +33 -33
- package/src/lib/authentication/auth-header-nav.ts +46 -46
- package/src/lib/authentication/authentication.ts +51 -51
- package/src/lib/authentication/components/CallbackHandler.tsx +45 -45
- package/src/lib/authentication/components/OAuthErrorPage.tsx +197 -197
- package/src/lib/authentication/components/ProductionUnlockPage.tsx +26 -26
- package/src/lib/authentication/components/SignIn.tsx +46 -46
- package/src/lib/authentication/components/SignOut.tsx +15 -15
- package/src/lib/authentication/components/SignUp.tsx +48 -48
- package/src/lib/authentication/constants.ts +1 -1
- package/src/lib/authentication/errors.ts +34 -34
- package/src/lib/authentication/header-nav-filter.ts +35 -35
- package/src/lib/authentication/hook.ts +141 -141
- package/src/lib/authentication/providers/auth0.tsx +91 -91
- package/src/lib/authentication/providers/azureb2c.tsx +223 -223
- package/src/lib/authentication/providers/clerk.tsx +224 -224
- package/src/lib/authentication/providers/dev-portal-auth-pages.tsx +602 -602
- package/src/lib/authentication/providers/dev-portal-constants.ts +38 -38
- package/src/lib/authentication/providers/dev-portal-demo.ts +151 -151
- package/src/lib/authentication/providers/dev-portal-utils.ts +279 -279
- package/src/lib/authentication/providers/dev-portal.tsx +448 -448
- package/src/lib/authentication/providers/firebase.tsx +486 -486
- package/src/lib/authentication/providers/openid.tsx +516 -516
- package/src/lib/authentication/providers/supabase.tsx +412 -412
- package/src/lib/authentication/providers/util.ts +26 -26
- package/src/lib/authentication/state.ts +180 -180
- package/src/lib/authentication/ui/AuthCard.tsx +7 -7
- package/src/lib/authentication/ui/EmailLinkCallbackUi.tsx +129 -129
- package/src/lib/authentication/ui/EmailLinkSentUi.tsx +105 -105
- package/src/lib/authentication/ui/EmailLinkSignInUi.tsx +100 -100
- package/src/lib/authentication/ui/EmailVerificationUi.tsx +128 -128
- package/src/lib/authentication/ui/ZudokuAuthUi.tsx +620 -620
- package/src/lib/authentication/ui/icons/Apple.tsx +10 -10
- package/src/lib/authentication/ui/icons/Facebook.tsx +15 -15
- package/src/lib/authentication/ui/icons/Github.tsx +16 -16
- package/src/lib/authentication/ui/icons/Google.tsx +16 -16
- package/src/lib/authentication/ui/icons/Microsoft.tsx +12 -12
- package/src/lib/authentication/ui/icons/X.tsx +10 -10
- package/src/lib/authentication/utils/relativeRedirectUrl.ts +16 -16
- package/src/lib/components/AiAssistantMenuItems.tsx +102 -102
- package/src/lib/components/AnchorLink.tsx +24 -24
- package/src/lib/components/Autocomplete.tsx +123 -123
- package/src/lib/components/Banner.tsx +52 -52
- package/src/lib/components/Bootstrap.tsx +77 -77
- package/src/lib/components/BuildCheck.tsx +87 -87
- package/src/lib/components/CategoryHeading.tsx +19 -19
- package/src/lib/components/ClientOnly.tsx +19 -19
- package/src/lib/components/DeveloperHint.tsx +39 -39
- package/src/lib/components/ErrorPage.tsx +26 -26
- package/src/lib/components/Footer.tsx +146 -146
- package/src/lib/components/Framed.tsx +51 -51
- package/src/lib/components/Header.tsx +261 -261
- package/src/lib/components/HeaderAuthActions.tsx +32 -32
- package/src/lib/components/HeaderNavigation.tsx +176 -176
- package/src/lib/components/Heading.tsx +84 -84
- package/src/lib/components/InlineCode.tsx +15 -15
- package/src/lib/components/LandingLayout.tsx +27 -27
- package/src/lib/components/LanguageIcon.tsx +199 -199
- package/src/lib/components/Layout.tsx +59 -59
- package/src/lib/components/Main.tsx +52 -52
- package/src/lib/components/Markdown.tsx +55 -55
- package/src/lib/components/Mermaid.tsx +74 -74
- package/src/lib/components/Meta.tsx +46 -46
- package/src/lib/components/MobileTopNavigation.tsx +283 -283
- package/src/lib/components/MultiSelect.tsx +86 -86
- package/src/lib/components/NotFoundPage.tsx +37 -37
- package/src/lib/components/PageProgress.tsx +28 -28
- package/src/lib/components/PagefindSearchMeta.tsx +14 -14
- package/src/lib/components/Pagination.tsx +44 -44
- package/src/lib/components/PathRenderer.tsx +61 -61
- package/src/lib/components/PluginHeads.tsx +17 -17
- package/src/lib/components/Search.tsx +70 -70
- package/src/lib/components/SiteTitle.tsx +58 -58
- package/src/lib/components/Slot.tsx +65 -65
- package/src/lib/components/Spinner.tsx +5 -5
- package/src/lib/components/StatusPage.tsx +96 -96
- package/src/lib/components/ThemeSwitch.tsx +99 -99
- package/src/lib/components/TopNavigation.tsx +90 -90
- package/src/lib/components/Typography.tsx +12 -12
- package/src/lib/components/Zudoku.tsx +105 -105
- package/src/lib/components/cache.ts +27 -27
- package/src/lib/components/context/RenderContext.ts +11 -11
- package/src/lib/components/context/RouterEventsEmitter.tsx +19 -19
- package/src/lib/components/context/SlotProvider.tsx +149 -149
- package/src/lib/components/context/ViewportAnchorContext.tsx +137 -137
- package/src/lib/components/context/ZudokuContext.ts +140 -140
- package/src/lib/components/context/ZudokuProvider.tsx +20 -20
- package/src/lib/components/context/ZudokuReactContext.tsx +17 -17
- package/src/lib/components/index.ts +36 -36
- package/src/lib/components/navigation/Navigation.tsx +51 -51
- package/src/lib/components/navigation/NavigationBadge.tsx +48 -48
- package/src/lib/components/navigation/NavigationCategory.tsx +157 -157
- package/src/lib/components/navigation/NavigationFilterContext.tsx +28 -28
- package/src/lib/components/navigation/NavigationFilterInput.tsx +35 -35
- package/src/lib/components/navigation/NavigationItem.tsx +181 -181
- package/src/lib/components/navigation/NavigationWrapper.tsx +49 -49
- package/src/lib/components/navigation/PoweredByZudoku.tsx +39 -39
- package/src/lib/components/navigation/Toc.tsx +137 -137
- package/src/lib/components/navigation/ZudokuLogo.tsx +25 -25
- package/src/lib/components/navigation/ZuploLogo.tsx +14 -14
- package/src/lib/components/navigation/utils.ts +234 -234
- package/src/lib/core/RouteGuard.tsx +260 -260
- package/src/lib/core/ZudokuContext.ts +272 -272
- package/src/lib/core/plugins.ts +139 -139
- package/src/lib/core/react-query.ts +1 -1
- package/src/lib/core/router.ts +1 -1
- package/src/lib/core/transform-config.ts +67 -67
- package/src/lib/demo/DemoAnnouncement.tsx +17 -17
- package/src/lib/errors/ErrorAlert.tsx +28 -28
- package/src/lib/errors/ErrorMessage.tsx +50 -50
- package/src/lib/errors/RouterError.tsx +18 -18
- package/src/lib/errors/ServerError.tsx +5 -5
- package/src/lib/errors/TopLevelError.tsx +6 -6
- package/src/lib/hooks/index.ts +13 -13
- package/src/lib/hooks/useEvent.ts +41 -41
- package/src/lib/hooks/useHighlighter.ts +15 -15
- package/src/lib/hooks/useHotkey.ts +70 -70
- package/src/lib/icons.ts +2 -2
- package/src/lib/navigation/applyRules.ts +127 -127
- package/src/lib/navigation/pathMatcher.ts +81 -81
- package/src/lib/oas/graphql/circular.ts +69 -69
- package/src/lib/oas/graphql/constants.ts +2 -2
- package/src/lib/oas/graphql/index.ts +791 -791
- package/src/lib/oas/parser/dereference/index.ts +84 -84
- package/src/lib/oas/parser/dereference/resolveRef.ts +32 -32
- package/src/lib/oas/parser/index.ts +129 -129
- package/src/lib/oas/parser/schemas/v3.0.json +1489 -1489
- package/src/lib/oas/parser/schemas/v3.1.json +1298 -1298
- package/src/lib/oas/parser/upgrade/index.ts +174 -174
- package/src/lib/plugins/api-catalog/Catalog.tsx +80 -80
- package/src/lib/plugins/api-catalog/index.tsx +119 -119
- package/src/lib/plugins/api-keys/CreateApiKey.tsx +128 -128
- package/src/lib/plugins/api-keys/CreateApiKeyDialog.tsx +49 -49
- package/src/lib/plugins/api-keys/ProtectedRoute.tsx +33 -33
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +72 -72
- package/src/lib/plugins/api-keys/index.tsx +274 -274
- package/src/lib/plugins/api-keys/settings/ApiKeyItem.tsx +342 -342
- package/src/lib/plugins/api-keys/settings/ApiKeyList.tsx +67 -67
- package/src/lib/plugins/api-keys/settings/RevealApiKey.tsx +127 -127
- package/src/lib/plugins/custom-pages/index.tsx +32 -32
- package/src/lib/plugins/markdown/MdxPage.tsx +260 -260
- package/src/lib/plugins/markdown/assets/ChatGPTLogo.tsx +11 -11
- package/src/lib/plugins/markdown/assets/ClaudeLogo.tsx +19 -19
- package/src/lib/plugins/markdown/index.tsx +82 -82
- package/src/lib/plugins/openapi/ApiHeader.tsx +96 -96
- package/src/lib/plugins/openapi/CollapsibleCode.tsx +88 -88
- package/src/lib/plugins/openapi/ColorizedParam.tsx +115 -115
- package/src/lib/plugins/openapi/DownloadSchemaButton.tsx +94 -94
- package/src/lib/plugins/openapi/Endpoint.tsx +85 -85
- package/src/lib/plugins/openapi/GeneratedExampleSidecarBox.tsx +52 -52
- package/src/lib/plugins/openapi/MCPEndpoint.tsx +273 -273
- package/src/lib/plugins/openapi/OasProvider.tsx +74 -74
- package/src/lib/plugins/openapi/OperationList.tsx +275 -275
- package/src/lib/plugins/openapi/OperationListItem.tsx +183 -183
- package/src/lib/plugins/openapi/ParamInfos.tsx +96 -96
- package/src/lib/plugins/openapi/ParameterList.tsx +53 -53
- package/src/lib/plugins/openapi/ParameterListItem.tsx +161 -161
- package/src/lib/plugins/openapi/PlaygroundDialogWrapper.tsx +67 -67
- package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +63 -63
- package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +120 -120
- package/src/lib/plugins/openapi/SchemaInfo.tsx +344 -344
- package/src/lib/plugins/openapi/SchemaList.tsx +113 -113
- package/src/lib/plugins/openapi/Sidecar.tsx +314 -314
- package/src/lib/plugins/openapi/SidecarBox.tsx +57 -57
- package/src/lib/plugins/openapi/SidecarExamples.tsx +177 -177
- package/src/lib/plugins/openapi/SimpleSelect.tsx +46 -46
- package/src/lib/plugins/openapi/client/GraphQLClient.tsx +73 -73
- package/src/lib/plugins/openapi/client/GraphQLContext.tsx +16 -16
- package/src/lib/plugins/openapi/client/createServer.ts +35 -35
- package/src/lib/plugins/openapi/client/useCreateQuery.ts +44 -44
- package/src/lib/plugins/openapi/components/ConstValue.tsx +24 -24
- package/src/lib/plugins/openapi/components/EnumValues.tsx +58 -58
- package/src/lib/plugins/openapi/components/NonHighlightedCode.tsx +22 -22
- package/src/lib/plugins/openapi/components/ResponseContent.tsx +117 -117
- package/src/lib/plugins/openapi/components/SelectOnClick.tsx +29 -29
- package/src/lib/plugins/openapi/context.tsx +16 -16
- package/src/lib/plugins/openapi/graphql/fragment-masking.ts +104 -104
- package/src/lib/plugins/openapi/graphql/gql.ts +95 -95
- package/src/lib/plugins/openapi/graphql/graphql.ts +835 -835
- package/src/lib/plugins/openapi/graphql/index.ts +2 -2
- package/src/lib/plugins/openapi/index.tsx +221 -221
- package/src/lib/plugins/openapi/interfaces.ts +100 -100
- package/src/lib/plugins/openapi/playground/BodyPanel.tsx +274 -274
- package/src/lib/plugins/openapi/playground/CollapsibleHeader.tsx +51 -51
- package/src/lib/plugins/openapi/playground/ExamplesDropdown.tsx +60 -60
- package/src/lib/plugins/openapi/playground/Headers.tsx +194 -194
- package/src/lib/plugins/openapi/playground/IdentityDialog.tsx +75 -75
- package/src/lib/plugins/openapi/playground/IdentitySelector.tsx +38 -38
- package/src/lib/plugins/openapi/playground/ParamsGrid.tsx +45 -45
- package/src/lib/plugins/openapi/playground/PathParams.tsx +53 -53
- package/src/lib/plugins/openapi/playground/Playground.tsx +602 -602
- package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +62 -62
- package/src/lib/plugins/openapi/playground/QueryParams.tsx +151 -151
- package/src/lib/plugins/openapi/playground/RequestLoginDialog.tsx +70 -70
- package/src/lib/plugins/openapi/playground/Spinner.tsx +87 -87
- package/src/lib/plugins/openapi/playground/createUrl.ts +25 -25
- package/src/lib/plugins/openapi/playground/fileUtils.ts +36 -36
- package/src/lib/plugins/openapi/playground/rememberedIdentity.ts +26 -26
- package/src/lib/plugins/openapi/playground/request-panel/MultipartField.tsx +91 -91
- package/src/lib/plugins/openapi/playground/request-panel/UrlPath.tsx +31 -31
- package/src/lib/plugins/openapi/playground/request-panel/UrlQueryParams.tsx +29 -29
- package/src/lib/plugins/openapi/playground/request-panel/useKeyValueFieldManager.ts +340 -340
- package/src/lib/plugins/openapi/playground/result-panel/AudioPlayer.tsx +50 -50
- package/src/lib/plugins/openapi/playground/result-panel/ResponseStatusBar.tsx +117 -117
- package/src/lib/plugins/openapi/playground/result-panel/ResponseTab.tsx +355 -355
- package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +106 -106
- package/src/lib/plugins/openapi/playground/result-panel/convertToTypes.ts +36 -36
- package/src/lib/plugins/openapi/playground/serializeQueryParams.ts +125 -125
- package/src/lib/plugins/openapi/playground/useRememberSkipLoginDialog.tsx +23 -23
- package/src/lib/plugins/openapi/processors/injectApitogoGatewayServer.ts +36 -36
- package/src/lib/plugins/openapi/processors/removeExtensions.ts +29 -29
- package/src/lib/plugins/openapi/processors/removeParameters.ts +103 -103
- package/src/lib/plugins/openapi/processors/removePaths.ts +57 -57
- package/src/lib/plugins/openapi/processors/traverse.ts +1 -1
- package/src/lib/plugins/openapi/schema/SchemaExampleAndDefault.tsx +39 -39
- package/src/lib/plugins/openapi/schema/SchemaPropertyItem.tsx +189 -189
- package/src/lib/plugins/openapi/schema/SchemaView.tsx +299 -299
- package/src/lib/plugins/openapi/schema/UnionView.tsx +132 -132
- package/src/lib/plugins/openapi/schema/union-helpers.ts +123 -123
- package/src/lib/plugins/openapi/schema/utils.ts +49 -49
- package/src/lib/plugins/openapi/state.ts +36 -36
- package/src/lib/plugins/openapi/util/buildTagCategories.ts +54 -54
- package/src/lib/plugins/openapi/util/createHttpSnippet.ts +135 -135
- package/src/lib/plugins/openapi/util/createNavigationCategory.tsx +39 -39
- package/src/lib/plugins/openapi/util/generateSchemaExample.ts +191 -191
- package/src/lib/plugins/openapi/util/getRoutes.tsx +265 -265
- package/src/lib/plugins/openapi/util/methodColorMap.tsx +11 -11
- package/src/lib/plugins/openapi/util/methodToColor.ts +27 -27
- package/src/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.tsx +32 -32
- package/src/lib/plugins/openapi/util/useWarmupSchema.ts +23 -23
- package/src/lib/plugins/search-inkeep/index.tsx +108 -108
- package/src/lib/plugins/search-inkeep/inkeep.ts +24 -24
- package/src/lib/plugins/search-pagefind/IndexingDialog.tsx +162 -162
- package/src/lib/plugins/search-pagefind/PagefindSearch.tsx +202 -202
- package/src/lib/plugins/search-pagefind/ResultList.tsx +122 -122
- package/src/lib/plugins/search-pagefind/get-results.tsx +74 -74
- package/src/lib/plugins/search-pagefind/index.tsx +21 -21
- package/src/lib/plugins/search-pagefind/types.ts +118 -118
- package/src/lib/shiki-constants.ts +24 -24
- package/src/lib/shiki.ts +179 -179
- package/src/lib/testing/index.tsx +147 -147
- package/src/lib/ui/Accordion.tsx +55 -55
- package/src/lib/ui/ActionButton.tsx +21 -21
- package/src/lib/ui/Alert.tsx +84 -84
- package/src/lib/ui/AlertDialog.tsx +195 -195
- package/src/lib/ui/AspectRatio.tsx +5 -5
- package/src/lib/ui/Badge.tsx +48 -48
- package/src/lib/ui/Breadcrumb.tsx +115 -115
- package/src/lib/ui/Button.tsx +65 -65
- package/src/lib/ui/ButtonGroup.tsx +80 -80
- package/src/lib/ui/Callout.tsx +113 -113
- package/src/lib/ui/Card.tsx +95 -95
- package/src/lib/ui/Carousel.tsx +259 -259
- package/src/lib/ui/Checkbox.tsx +28 -28
- package/src/lib/ui/CodeBlock.tsx +92 -92
- package/src/lib/ui/CodeTabPanel.tsx +13 -13
- package/src/lib/ui/CodeTabs.tsx +161 -161
- package/src/lib/ui/Collapsible.tsx +31 -31
- package/src/lib/ui/Command.tsx +191 -191
- package/src/lib/ui/Dialog.tsx +138 -138
- package/src/lib/ui/DismissibleAlert.tsx +74 -74
- package/src/lib/ui/Drawer.tsx +121 -121
- package/src/lib/ui/DropdownMenu.tsx +254 -254
- package/src/lib/ui/EmbeddedCodeBlock.tsx +99 -99
- package/src/lib/ui/Form.tsx +176 -176
- package/src/lib/ui/Frame.tsx +81 -81
- package/src/lib/ui/HoverCard.tsx +26 -26
- package/src/lib/ui/Input.tsx +23 -23
- package/src/lib/ui/InputGroup.tsx +168 -168
- package/src/lib/ui/Item.tsx +213 -213
- package/src/lib/ui/Kbd.tsx +28 -28
- package/src/lib/ui/Label.tsx +23 -23
- package/src/lib/ui/NativeSelect.tsx +48 -48
- package/src/lib/ui/NavigationMenu.tsx +169 -169
- package/src/lib/ui/Pagination.tsx +115 -115
- package/src/lib/ui/Popover.tsx +28 -28
- package/src/lib/ui/Progress.tsx +25 -25
- package/src/lib/ui/RadioGroup.tsx +41 -41
- package/src/lib/ui/ReactComponentDoc.tsx +68 -68
- package/src/lib/ui/ScrollArea.tsx +45 -45
- package/src/lib/ui/Secret.tsx +133 -133
- package/src/lib/ui/Select.tsx +184 -184
- package/src/lib/ui/Separator.tsx +25 -25
- package/src/lib/ui/Skeleton.tsx +15 -15
- package/src/lib/ui/Slider.tsx +25 -25
- package/src/lib/ui/Stepper.tsx +8 -8
- package/src/lib/ui/Switch.tsx +26 -26
- package/src/lib/ui/SyntaxHighlight.tsx +54 -54
- package/src/lib/ui/Tabs.tsx +52 -52
- package/src/lib/ui/Textarea.tsx +22 -22
- package/src/lib/ui/Toggle.tsx +42 -42
- package/src/lib/ui/ToggleGroup.tsx +58 -58
- package/src/lib/ui/Tooltip.tsx +65 -65
- package/src/lib/ui/Value.tsx +42 -42
- package/src/lib/ui/util.tsx +8 -8
- package/src/lib/util/MdxComponents.tsx +150 -150
- package/src/lib/util/cn.ts +6 -6
- package/src/lib/util/createVariantComponent.tsx +61 -61
- package/src/lib/util/ensureArray.ts +3 -3
- package/src/lib/util/flattenAllOf.ts +72 -72
- package/src/lib/util/flattenAllOfProcessor.ts +74 -74
- package/src/lib/util/hastToJsx.tsx +67 -67
- package/src/lib/util/humanFileSize.ts +15 -15
- package/src/lib/util/invariant.ts +52 -52
- package/src/lib/util/joinUrl.ts +60 -60
- package/src/lib/util/logInit.ts +9 -9
- package/src/lib/util/objectEntries.ts +5 -5
- package/src/lib/util/os.ts +18 -18
- package/src/lib/util/pastellize.ts +25 -25
- package/src/lib/util/problemJson.ts +63 -63
- package/src/lib/util/readFrontmatter.ts +14 -14
- package/src/lib/util/renderIf.ts +4 -4
- package/src/lib/util/scrollIntoViewIfNeeded.ts +18 -18
- package/src/lib/util/slugify.ts +21 -21
- package/src/lib/util/syncZustandState.ts +22 -22
- package/src/lib/util/traverse.ts +70 -70
- package/src/lib/util/types.ts +7 -7
- package/src/lib/util/url.ts +18 -18
- package/src/lib/util/useCopyToClipboard.ts +17 -17
- package/src/lib/util/useExposedProps.tsx +27 -27
- package/src/lib/util/useIsomorphicLayoutEffect.ts +6 -6
- package/src/lib/util/useLatest.ts +18 -18
- package/src/lib/util/useOnScreen.ts +34 -34
- package/src/lib/util/useScrollToAnchor.ts +75 -75
- package/src/lib/util/useScrollToTop.ts +18 -18
- package/src/types.d.ts +66 -66
- package/src/vite/api/SchemaManager.ts +371 -371
- package/src/vite/api/schema-codegen.ts +163 -163
- package/src/vite/build.ts +237 -237
- package/src/vite/config.ts +273 -273
- package/src/vite/css/collect.ts +42 -42
- package/src/vite/css/plugin.ts +105 -105
- package/src/vite/debug.ts +18 -18
- package/src/vite/dev-portal-env.ts +29 -29
- package/src/vite/dev-server.ts +294 -294
- package/src/vite/html.ts +52 -52
- package/src/vite/index.ts +2 -2
- package/src/vite/llms.ts +99 -99
- package/src/vite/mdx/rehype-extract-toc-with-jsx-export.ts +19 -19
- package/src/vite/mdx/rehype-extract-toc-with-jsx.ts +70 -70
- package/src/vite/mdx/remark-code-tabs.ts +47 -47
- package/src/vite/mdx/remark-inject-filepath.ts +13 -13
- package/src/vite/mdx/remark-last-modified.ts +103 -103
- package/src/vite/mdx/remark-link-rewrite.ts +23 -23
- package/src/vite/mdx/remark-normalize-image-url.ts +15 -15
- package/src/vite/mdx/remark-static-generation.ts +149 -149
- package/src/vite/mdx/utils.ts +32 -32
- package/src/vite/output.ts +261 -261
- package/src/vite/package-root.ts +12 -12
- package/src/vite/pagefind-dev-index.ts +100 -100
- package/src/vite/pagefind-dev-stub.ts +17 -17
- package/src/vite/plugin-api-keys.ts +55 -55
- package/src/vite/plugin-api.ts +328 -328
- package/src/vite/plugin-auth.ts +41 -41
- package/src/vite/plugin-component.ts +45 -45
- package/src/vite/plugin-config-reload.ts +47 -47
- package/src/vite/plugin-config.ts +131 -131
- package/src/vite/plugin-custom-pages.ts +36 -36
- package/src/vite/plugin-doc-metadata.ts +45 -45
- package/src/vite/plugin-docs.ts +205 -205
- package/src/vite/plugin-local-manifest.ts +100 -100
- package/src/vite/plugin-markdown-export.ts +214 -214
- package/src/vite/plugin-mdx.ts +149 -149
- package/src/vite/plugin-metadata.ts +30 -30
- package/src/vite/plugin-modules.ts +188 -188
- package/src/vite/plugin-navigation.ts +106 -106
- package/src/vite/plugin-offroot-resolve.ts +67 -67
- package/src/vite/plugin-search.ts +59 -59
- package/src/vite/plugin-shiki-register.ts +97 -97
- package/src/vite/plugin-theme.ts +458 -458
- package/src/vite/plugin.ts +48 -48
- package/src/vite/prerender/prerender.ts +242 -242
- package/src/vite/prerender/utils.ts +76 -76
- package/src/vite/prerender/worker.ts +118 -118
- package/src/vite/reporter.ts +34 -34
- package/src/vite/shadcn-registry.ts +48 -48
- package/src/vite/sitemap.ts +78 -78
- package/src/vite/ssr-templates/cloudflare.ts +19 -19
- package/src/vite/ssr-templates/node.ts +25 -25
- package/src/vite/ssr-templates/vercel.ts +22 -22
- package/src/vite/watch-ignore.ts +70 -70
- package/src/vite/zuplo.ts +30 -30
- package/src/vite-env.d.ts +6 -6
- package/src/zuplo/enrich-with-zuplo-mcp.ts +168 -168
- package/src/zuplo/enrich-with-zuplo.ts +256 -256
- package/src/zuplo/policy-types.ts +46 -46
- package/src/zuplo/with-zuplo-processors.ts +36 -36
- package/src/zuplo/with-zuplo.ts +14 -14
- package/dist/declarations/cli/platform/client/deploy-server.d.ts +0 -16
|
@@ -1,452 +1,452 @@
|
|
|
1
|
-
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { ConfigError, type ConfigIssue } from "./errors.js";
|
|
5
|
-
import { manifestPathFor, type StatePaths } from "./paths.js";
|
|
6
|
-
import type { ResolvedConfigV2 } from "./resolve.js";
|
|
7
|
-
|
|
8
|
-
/** Highest `schemaVersion` this build of the portal understands. */
|
|
9
|
-
export const SUPPORTED_MANIFEST_SCHEMA = 1;
|
|
10
|
-
|
|
11
|
-
/** Object of booleans with camelCase names. A name that is absent means false (decision 19). */
|
|
12
|
-
const Capabilities = z.record(z.string(), z.boolean()).default({});
|
|
13
|
-
|
|
14
|
-
const Status = z.string().optional();
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* The service manifest, `services.json` (Reference, section 4.1). Every service owns its entry; a service that is
|
|
18
|
-
* missing from the manifest is disabled or not yet deployed. Unknown fields are kept so a newer deploy does not
|
|
19
|
-
* break an older reader.
|
|
20
|
-
*/
|
|
21
|
-
const ServiceManifestSchema = z.looseObject({
|
|
22
|
-
schemaVersion: z.number().int(),
|
|
23
|
-
generatedAt: z.string().optional(),
|
|
24
|
-
/** Set only on the stub written for local runs; never present in a deployed manifest. */
|
|
25
|
-
preview: z.boolean().optional(),
|
|
26
|
-
project: z.looseObject({ id: z.string().optional(), name: z.string() }),
|
|
27
|
-
services: z
|
|
28
|
-
.looseObject({
|
|
29
|
-
portal: z
|
|
30
|
-
.looseObject({
|
|
31
|
-
url: z.string().optional(),
|
|
32
|
-
docs: z
|
|
33
|
-
.looseObject({ provider: z.string(), url: z.string().optional() })
|
|
34
|
-
.optional(),
|
|
35
|
-
console: z
|
|
36
|
-
.looseObject({ links: z.record(z.string(), z.string()).optional() })
|
|
37
|
-
.optional(),
|
|
38
|
-
})
|
|
39
|
-
.optional(),
|
|
40
|
-
identity: z
|
|
41
|
-
.looseObject({
|
|
42
|
-
provider: z.string().default("apitogo"),
|
|
43
|
-
status: Status,
|
|
44
|
-
issuer: z.string().optional(),
|
|
45
|
-
clientId: z.string().optional(),
|
|
46
|
-
scopes: z.array(z.string()).optional(),
|
|
47
|
-
audience: z.string().optional(),
|
|
48
|
-
capabilities: Capabilities,
|
|
49
|
-
})
|
|
50
|
-
.optional(),
|
|
51
|
-
gateway: z
|
|
52
|
-
.looseObject({
|
|
53
|
-
provider: z.string().optional(),
|
|
54
|
-
status: Status,
|
|
55
|
-
baseUrl: z.string().optional(),
|
|
56
|
-
openapiUrl: z.string().optional(),
|
|
57
|
-
openapiHash: z.string().optional(),
|
|
58
|
-
audience: z.string().optional(),
|
|
59
|
-
keyHeaderName: z.string().optional(),
|
|
60
|
-
capabilities: Capabilities,
|
|
61
|
-
})
|
|
62
|
-
.optional(),
|
|
63
|
-
plans: z
|
|
64
|
-
.looseObject({
|
|
65
|
-
provider: z.string().optional(),
|
|
66
|
-
status: Status,
|
|
67
|
-
apiBaseUrl: z.string().optional(),
|
|
68
|
-
keysApiBaseUrl: z.string().optional(),
|
|
69
|
-
billing: z.string().optional(),
|
|
70
|
-
audience: z.string().optional(),
|
|
71
|
-
scopes: z.array(z.string()).optional(),
|
|
72
|
-
catalogRevision: z.number().int().optional(),
|
|
73
|
-
capabilities: Capabilities,
|
|
74
|
-
})
|
|
75
|
-
.optional(),
|
|
76
|
-
})
|
|
77
|
-
.default({}),
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
export type ServiceManifest = z.infer<typeof ServiceManifestSchema>;
|
|
81
|
-
export type ManifestServiceName = "identity" | "gateway" | "plans" | "portal";
|
|
82
|
-
|
|
83
|
-
const issue = (
|
|
84
|
-
code: string,
|
|
85
|
-
path: string,
|
|
86
|
-
message: string,
|
|
87
|
-
hint?: string,
|
|
88
|
-
): ConfigIssue => ({ code, path, message, hint });
|
|
89
|
-
|
|
90
|
-
/** Validates parsed `services.json` content. Throws a ConfigError with stable codes. */
|
|
91
|
-
export function parseManifest(
|
|
92
|
-
raw: unknown,
|
|
93
|
-
file = "services.json",
|
|
94
|
-
): ServiceManifest {
|
|
95
|
-
const version =
|
|
96
|
-
typeof raw === "object" && raw !== null && "schemaVersion" in raw
|
|
97
|
-
? raw.schemaVersion
|
|
98
|
-
: undefined;
|
|
99
|
-
if (typeof version === "number" && version > SUPPORTED_MANIFEST_SCHEMA) {
|
|
100
|
-
throw new ConfigError(file, [
|
|
101
|
-
issue(
|
|
102
|
-
"PORTAL_MANIFEST_SCHEMA",
|
|
103
|
-
"schemaVersion",
|
|
104
|
-
`services.json has schemaVersion ${version} but this CLI supports ${SUPPORTED_MANIFEST_SCHEMA}.`,
|
|
105
|
-
"Upgrade @lukoweb/apitogo.",
|
|
106
|
-
),
|
|
107
|
-
]);
|
|
108
|
-
}
|
|
109
|
-
const result = ServiceManifestSchema.safeParse(raw);
|
|
110
|
-
if (!result.success) {
|
|
111
|
-
throw new ConfigError(
|
|
112
|
-
file,
|
|
113
|
-
result.error.issues.map((problem) =>
|
|
114
|
-
issue(
|
|
115
|
-
"PORTAL_MANIFEST_INVALID",
|
|
116
|
-
problem.path.join("."),
|
|
117
|
-
problem.message,
|
|
118
|
-
"Run `apitogo deploy` to write a fresh services.json.",
|
|
119
|
-
),
|
|
120
|
-
),
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
return result.data;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** Reads `services.json` from the state directory. Returns undefined when the file does not exist. */
|
|
127
|
-
export async function readManifest(
|
|
128
|
-
state: Pick<StatePaths, "dir">,
|
|
129
|
-
): Promise<ServiceManifest | undefined> {
|
|
130
|
-
const file = manifestPathFor(state);
|
|
131
|
-
let text: string;
|
|
132
|
-
try {
|
|
133
|
-
text = await readFile(file, "utf8");
|
|
134
|
-
} catch (error) {
|
|
135
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
136
|
-
throw error;
|
|
137
|
-
}
|
|
138
|
-
try {
|
|
139
|
-
return parseManifest(JSON.parse(text), file);
|
|
140
|
-
} catch (error) {
|
|
141
|
-
if (error instanceof SyntaxError) {
|
|
142
|
-
throw new ConfigError(file, [
|
|
143
|
-
issue(
|
|
144
|
-
"PORTAL_MANIFEST_INVALID",
|
|
145
|
-
"",
|
|
146
|
-
`is not valid JSON (${error.message}).`,
|
|
147
|
-
"Run `apitogo deploy` to write a fresh services.json.",
|
|
148
|
-
),
|
|
149
|
-
]);
|
|
150
|
-
}
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
type Entry = { status?: string; capabilities: Record<string, boolean> };
|
|
156
|
-
|
|
157
|
-
/** A service is usable when it has an entry and is not still provisioning. A missing status counts as ready. */
|
|
158
|
-
export function serviceReady(entry: { status?: string } | undefined): boolean {
|
|
159
|
-
return entry !== undefined && (entry.status ?? "ready") === "ready";
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** True only when the service exists, is ready and declares the capability. Absent means false. */
|
|
163
|
-
export function hasCapability(entry: Entry | undefined, name: string): boolean {
|
|
164
|
-
return serviceReady(entry) && entry?.capabilities[name] === true;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Services the configured portal depends on. A production build needs a manifest entry for each of them
|
|
169
|
-
* (PORTAL_MANIFEST_MISSING); a docs-only portal with no gateway needs none.
|
|
170
|
-
*/
|
|
171
|
-
export function requiredServices(
|
|
172
|
-
resolved: ResolvedConfigV2,
|
|
173
|
-
): ManifestServiceName[] {
|
|
174
|
-
const portal = resolved.portal;
|
|
175
|
-
if (!portal?.enabled) return [];
|
|
176
|
-
const needs: ManifestServiceName[] = [];
|
|
177
|
-
const consoleOn = portal.console.enabled;
|
|
178
|
-
const gatewayOn = resolved.gateway?.enabled === true;
|
|
179
|
-
const identityOn = resolved.identity?.enabled === true;
|
|
180
|
-
if (identityOn && (consoleOn || portal.requireAuth)) needs.push("identity");
|
|
181
|
-
if (gatewayOn && (consoleOn || portal.docs.enabled)) needs.push("gateway");
|
|
182
|
-
if (resolved.plans?.enabled === true && consoleOn) needs.push("plans");
|
|
183
|
-
return needs;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** Problems that stop a production build: a required service without a manifest entry. */
|
|
187
|
-
export function missingManifestServices(
|
|
188
|
-
resolved: ResolvedConfigV2,
|
|
189
|
-
manifest: ServiceManifest | undefined,
|
|
190
|
-
): ConfigIssue[] {
|
|
191
|
-
return requiredServices(resolved)
|
|
192
|
-
.filter((name) => manifest?.services[name] === undefined)
|
|
193
|
-
.map((name) =>
|
|
194
|
-
issue(
|
|
195
|
-
"PORTAL_MANIFEST_MISSING",
|
|
196
|
-
`services.${name}`,
|
|
197
|
-
`the portal needs the ${name} service, but services.json has no entry for it.`,
|
|
198
|
-
`Run \`apitogo deploy --only ${name}\` first.`,
|
|
199
|
-
),
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* The stand-in manifest for local runs before identity and plans exist. It is labelled `preview: true`, uses
|
|
205
|
-
* the demo issuer-less shape (no URLs are invented) and is refused by production builds.
|
|
206
|
-
*/
|
|
207
|
-
export function stubManifest(resolved: ResolvedConfigV2): ServiceManifest {
|
|
208
|
-
const portal = resolved.portal;
|
|
209
|
-
const consoleOn = portal?.enabled === true && portal.console.enabled;
|
|
210
|
-
return parseManifest({
|
|
211
|
-
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
212
|
-
preview: true,
|
|
213
|
-
project: { id: resolved.project.id, name: resolved.project.name },
|
|
214
|
-
services: {
|
|
215
|
-
...(consoleOn && resolved.identity?.enabled
|
|
216
|
-
? {
|
|
217
|
-
identity: {
|
|
218
|
-
provider: "apitogo",
|
|
219
|
-
status: "ready",
|
|
220
|
-
capabilities: { nativeSignup: true },
|
|
221
|
-
},
|
|
222
|
-
}
|
|
223
|
-
: {}),
|
|
224
|
-
...(consoleOn && resolved.plans?.enabled
|
|
225
|
-
? {
|
|
226
|
-
plans: {
|
|
227
|
-
status: "ready",
|
|
228
|
-
capabilities: { catalog: true },
|
|
229
|
-
},
|
|
230
|
-
}
|
|
231
|
-
: {}),
|
|
232
|
-
},
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Adds what a portal deploy learned (project, public URL, docs provider) to the manifest. Entries of other services
|
|
238
|
-
* are kept as they are, so deploying the portal never erases what identity or plans wrote.
|
|
239
|
-
*/
|
|
240
|
-
export function withPortalEntry(
|
|
241
|
-
existing: ServiceManifest | undefined,
|
|
242
|
-
info: {
|
|
243
|
-
projectId: string;
|
|
244
|
-
projectName: string;
|
|
245
|
-
url: string;
|
|
246
|
-
docs: { provider: string; url?: string };
|
|
247
|
-
now?: Date;
|
|
248
|
-
},
|
|
249
|
-
): ServiceManifest {
|
|
250
|
-
const base: ServiceManifest =
|
|
251
|
-
existing ??
|
|
252
|
-
parseManifest({
|
|
253
|
-
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
254
|
-
project: { name: info.projectName },
|
|
255
|
-
});
|
|
256
|
-
const previousPortal = base.services.portal;
|
|
257
|
-
return {
|
|
258
|
-
...base,
|
|
259
|
-
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
260
|
-
generatedAt: (info.now ?? new Date()).toISOString(),
|
|
261
|
-
preview: undefined,
|
|
262
|
-
project: { ...base.project, id: info.projectId, name: info.projectName },
|
|
263
|
-
services: {
|
|
264
|
-
...base.services,
|
|
265
|
-
portal: {
|
|
266
|
-
...previousPortal,
|
|
267
|
-
url: info.url.replace(/\/+$/, ""),
|
|
268
|
-
docs: info.docs,
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Adds the gateway entry a gateway deploy learned. Only public values: the base URL and what the platform says the
|
|
276
|
-
* gateway can do. Entries of other services are kept as they are.
|
|
277
|
-
*/
|
|
278
|
-
export function withGatewayEntry(
|
|
279
|
-
existing: ServiceManifest | undefined,
|
|
280
|
-
info: {
|
|
281
|
-
projectId: string;
|
|
282
|
-
projectName: string;
|
|
283
|
-
provider: string;
|
|
284
|
-
baseUrl: string;
|
|
285
|
-
capabilities: Record<string, boolean>;
|
|
286
|
-
openapiUrl?: string;
|
|
287
|
-
openapiHash?: string;
|
|
288
|
-
keyHeaderName?: string;
|
|
289
|
-
now?: Date;
|
|
290
|
-
},
|
|
291
|
-
): ServiceManifest {
|
|
292
|
-
const base: ServiceManifest =
|
|
293
|
-
existing ??
|
|
294
|
-
parseManifest({
|
|
295
|
-
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
296
|
-
project: { name: info.projectName },
|
|
297
|
-
});
|
|
298
|
-
const baseUrl = info.baseUrl.replace(/\/+$/, "");
|
|
299
|
-
return {
|
|
300
|
-
...base,
|
|
301
|
-
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
302
|
-
generatedAt: (info.now ?? new Date()).toISOString(),
|
|
303
|
-
preview: undefined,
|
|
304
|
-
project: { ...base.project, id: info.projectId, name: info.projectName },
|
|
305
|
-
services: {
|
|
306
|
-
...base.services,
|
|
307
|
-
gateway: {
|
|
308
|
-
...base.services.gateway,
|
|
309
|
-
provider: info.provider,
|
|
310
|
-
status: "ready",
|
|
311
|
-
baseUrl,
|
|
312
|
-
audience: baseUrl,
|
|
313
|
-
capabilities: info.capabilities,
|
|
314
|
-
...(info.openapiUrl ? { openapiUrl: info.openapiUrl } : {}),
|
|
315
|
-
...(info.openapiHash ? { openapiHash: info.openapiHash } : {}),
|
|
316
|
-
...(info.keyHeaderName ? { keyHeaderName: info.keyHeaderName } : {}),
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Adds the identity entry an identity deploy learned: the issuer address, the portal's sign-in client and the audience
|
|
324
|
-
* of its tokens. Public values only. Entries of other services are kept as they are.
|
|
325
|
-
*/
|
|
326
|
-
export function withIdentityEntry(
|
|
327
|
-
existing: ServiceManifest | undefined,
|
|
328
|
-
info: {
|
|
329
|
-
projectId: string;
|
|
330
|
-
projectName: string;
|
|
331
|
-
/** `apitogo` for the platform's own identity, `oidc` for the customer's provider (nothing is made for it). */
|
|
332
|
-
provider?: "apitogo" | "oidc";
|
|
333
|
-
issuer: string;
|
|
334
|
-
clientId: string;
|
|
335
|
-
audience?: string;
|
|
336
|
-
nativeSignup: boolean;
|
|
337
|
-
now?: Date;
|
|
338
|
-
},
|
|
339
|
-
): ServiceManifest {
|
|
340
|
-
const base: ServiceManifest =
|
|
341
|
-
existing ??
|
|
342
|
-
parseManifest({
|
|
343
|
-
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
344
|
-
project: { name: info.projectName },
|
|
345
|
-
});
|
|
346
|
-
return {
|
|
347
|
-
...base,
|
|
348
|
-
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
349
|
-
generatedAt: (info.now ?? new Date()).toISOString(),
|
|
350
|
-
preview: undefined,
|
|
351
|
-
project: { ...base.project, id: info.projectId, name: info.projectName },
|
|
352
|
-
services: {
|
|
353
|
-
...base.services,
|
|
354
|
-
identity: {
|
|
355
|
-
...base.services.identity,
|
|
356
|
-
provider: info.provider ?? "apitogo",
|
|
357
|
-
status: "ready",
|
|
358
|
-
issuer: info.issuer.replace(/\/+$/, ""),
|
|
359
|
-
clientId: info.clientId,
|
|
360
|
-
scopes: ["openid", "profile", "email"],
|
|
361
|
-
...(info.audience ? { audience: info.audience } : {}),
|
|
362
|
-
capabilities: { nativeSignup: info.nativeSignup },
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Adds the plans entry a plans deploy learned: where the portal reaches the plans backend and what it can do. Public
|
|
370
|
-
* values only. Entries of other services are kept as they are.
|
|
371
|
-
*/
|
|
372
|
-
export function withPlansEntry(
|
|
373
|
-
existing: ServiceManifest | undefined,
|
|
374
|
-
info: {
|
|
375
|
-
projectId: string;
|
|
376
|
-
projectName: string;
|
|
377
|
-
apiBaseUrl: string;
|
|
378
|
-
billing: string;
|
|
379
|
-
catalogRevision: number;
|
|
380
|
-
capabilities: Record<string, boolean>;
|
|
381
|
-
now?: Date;
|
|
382
|
-
},
|
|
383
|
-
): ServiceManifest {
|
|
384
|
-
const base: ServiceManifest =
|
|
385
|
-
existing ??
|
|
386
|
-
parseManifest({
|
|
387
|
-
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
388
|
-
project: { name: info.projectName },
|
|
389
|
-
});
|
|
390
|
-
return {
|
|
391
|
-
...base,
|
|
392
|
-
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
393
|
-
generatedAt: (info.now ?? new Date()).toISOString(),
|
|
394
|
-
preview: undefined,
|
|
395
|
-
project: { ...base.project, id: info.projectId, name: info.projectName },
|
|
396
|
-
services: {
|
|
397
|
-
...base.services,
|
|
398
|
-
plans: {
|
|
399
|
-
...base.services.plans,
|
|
400
|
-
provider: "apitogo",
|
|
401
|
-
status: "ready",
|
|
402
|
-
apiBaseUrl: info.apiBaseUrl.replace(/\/+$/, ""),
|
|
403
|
-
billing: info.billing,
|
|
404
|
-
catalogRevision: info.catalogRevision,
|
|
405
|
-
capabilities: info.capabilities,
|
|
406
|
-
},
|
|
407
|
-
},
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/** Replaces `services.json` atomically. Public values only: this file ships inside the portal. */
|
|
412
|
-
export async function writeManifest(
|
|
413
|
-
state: Pick<StatePaths, "dir">,
|
|
414
|
-
manifest: ServiceManifest,
|
|
415
|
-
): Promise<void> {
|
|
416
|
-
const file = manifestPathFor(state);
|
|
417
|
-
await mkdir(path.dirname(file), { recursive: true });
|
|
418
|
-
const temporary = `${file}.${process.pid}.tmp`;
|
|
419
|
-
try {
|
|
420
|
-
await writeFile(
|
|
421
|
-
temporary,
|
|
422
|
-
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
423
|
-
"utf8",
|
|
424
|
-
);
|
|
425
|
-
await rename(temporary, file);
|
|
426
|
-
} catch (error) {
|
|
427
|
-
await rm(temporary, { force: true });
|
|
428
|
-
throw error;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Drops the entries of services that were torn down. The file is deleted when no service is left, because a
|
|
434
|
-
* manifest with no services says nothing and would only look like a deployed project.
|
|
435
|
-
*/
|
|
436
|
-
export async function removeServicesFromManifest(
|
|
437
|
-
state: Pick<StatePaths, "dir">,
|
|
438
|
-
names: readonly ManifestServiceName[],
|
|
439
|
-
): Promise<void> {
|
|
440
|
-
const existing = await readManifest(state);
|
|
441
|
-
if (!existing) return;
|
|
442
|
-
const services = Object.fromEntries(
|
|
443
|
-
Object.entries(existing.services).filter(
|
|
444
|
-
([name]) => !(names as readonly string[]).includes(name),
|
|
445
|
-
),
|
|
446
|
-
);
|
|
447
|
-
if (Object.keys(services).length === 0) {
|
|
448
|
-
await rm(manifestPathFor(state), { force: true });
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
await writeManifest(state, { ...existing, services });
|
|
452
|
-
}
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { ConfigError, type ConfigIssue } from "./errors.js";
|
|
5
|
+
import { manifestPathFor, type StatePaths } from "./paths.js";
|
|
6
|
+
import type { ResolvedConfigV2 } from "./resolve.js";
|
|
7
|
+
|
|
8
|
+
/** Highest `schemaVersion` this build of the portal understands. */
|
|
9
|
+
export const SUPPORTED_MANIFEST_SCHEMA = 1;
|
|
10
|
+
|
|
11
|
+
/** Object of booleans with camelCase names. A name that is absent means false (decision 19). */
|
|
12
|
+
const Capabilities = z.record(z.string(), z.boolean()).default({});
|
|
13
|
+
|
|
14
|
+
const Status = z.string().optional();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The service manifest, `services.json` (Reference, section 4.1). Every service owns its entry; a service that is
|
|
18
|
+
* missing from the manifest is disabled or not yet deployed. Unknown fields are kept so a newer deploy does not
|
|
19
|
+
* break an older reader.
|
|
20
|
+
*/
|
|
21
|
+
const ServiceManifestSchema = z.looseObject({
|
|
22
|
+
schemaVersion: z.number().int(),
|
|
23
|
+
generatedAt: z.string().optional(),
|
|
24
|
+
/** Set only on the stub written for local runs; never present in a deployed manifest. */
|
|
25
|
+
preview: z.boolean().optional(),
|
|
26
|
+
project: z.looseObject({ id: z.string().optional(), name: z.string() }),
|
|
27
|
+
services: z
|
|
28
|
+
.looseObject({
|
|
29
|
+
portal: z
|
|
30
|
+
.looseObject({
|
|
31
|
+
url: z.string().optional(),
|
|
32
|
+
docs: z
|
|
33
|
+
.looseObject({ provider: z.string(), url: z.string().optional() })
|
|
34
|
+
.optional(),
|
|
35
|
+
console: z
|
|
36
|
+
.looseObject({ links: z.record(z.string(), z.string()).optional() })
|
|
37
|
+
.optional(),
|
|
38
|
+
})
|
|
39
|
+
.optional(),
|
|
40
|
+
identity: z
|
|
41
|
+
.looseObject({
|
|
42
|
+
provider: z.string().default("apitogo"),
|
|
43
|
+
status: Status,
|
|
44
|
+
issuer: z.string().optional(),
|
|
45
|
+
clientId: z.string().optional(),
|
|
46
|
+
scopes: z.array(z.string()).optional(),
|
|
47
|
+
audience: z.string().optional(),
|
|
48
|
+
capabilities: Capabilities,
|
|
49
|
+
})
|
|
50
|
+
.optional(),
|
|
51
|
+
gateway: z
|
|
52
|
+
.looseObject({
|
|
53
|
+
provider: z.string().optional(),
|
|
54
|
+
status: Status,
|
|
55
|
+
baseUrl: z.string().optional(),
|
|
56
|
+
openapiUrl: z.string().optional(),
|
|
57
|
+
openapiHash: z.string().optional(),
|
|
58
|
+
audience: z.string().optional(),
|
|
59
|
+
keyHeaderName: z.string().optional(),
|
|
60
|
+
capabilities: Capabilities,
|
|
61
|
+
})
|
|
62
|
+
.optional(),
|
|
63
|
+
plans: z
|
|
64
|
+
.looseObject({
|
|
65
|
+
provider: z.string().optional(),
|
|
66
|
+
status: Status,
|
|
67
|
+
apiBaseUrl: z.string().optional(),
|
|
68
|
+
keysApiBaseUrl: z.string().optional(),
|
|
69
|
+
billing: z.string().optional(),
|
|
70
|
+
audience: z.string().optional(),
|
|
71
|
+
scopes: z.array(z.string()).optional(),
|
|
72
|
+
catalogRevision: z.number().int().optional(),
|
|
73
|
+
capabilities: Capabilities,
|
|
74
|
+
})
|
|
75
|
+
.optional(),
|
|
76
|
+
})
|
|
77
|
+
.default({}),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export type ServiceManifest = z.infer<typeof ServiceManifestSchema>;
|
|
81
|
+
export type ManifestServiceName = "identity" | "gateway" | "plans" | "portal";
|
|
82
|
+
|
|
83
|
+
const issue = (
|
|
84
|
+
code: string,
|
|
85
|
+
path: string,
|
|
86
|
+
message: string,
|
|
87
|
+
hint?: string,
|
|
88
|
+
): ConfigIssue => ({ code, path, message, hint });
|
|
89
|
+
|
|
90
|
+
/** Validates parsed `services.json` content. Throws a ConfigError with stable codes. */
|
|
91
|
+
export function parseManifest(
|
|
92
|
+
raw: unknown,
|
|
93
|
+
file = "services.json",
|
|
94
|
+
): ServiceManifest {
|
|
95
|
+
const version =
|
|
96
|
+
typeof raw === "object" && raw !== null && "schemaVersion" in raw
|
|
97
|
+
? raw.schemaVersion
|
|
98
|
+
: undefined;
|
|
99
|
+
if (typeof version === "number" && version > SUPPORTED_MANIFEST_SCHEMA) {
|
|
100
|
+
throw new ConfigError(file, [
|
|
101
|
+
issue(
|
|
102
|
+
"PORTAL_MANIFEST_SCHEMA",
|
|
103
|
+
"schemaVersion",
|
|
104
|
+
`services.json has schemaVersion ${version} but this CLI supports ${SUPPORTED_MANIFEST_SCHEMA}.`,
|
|
105
|
+
"Upgrade @lukoweb/apitogo.",
|
|
106
|
+
),
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
const result = ServiceManifestSchema.safeParse(raw);
|
|
110
|
+
if (!result.success) {
|
|
111
|
+
throw new ConfigError(
|
|
112
|
+
file,
|
|
113
|
+
result.error.issues.map((problem) =>
|
|
114
|
+
issue(
|
|
115
|
+
"PORTAL_MANIFEST_INVALID",
|
|
116
|
+
problem.path.join("."),
|
|
117
|
+
problem.message,
|
|
118
|
+
"Run `apitogo deploy` to write a fresh services.json.",
|
|
119
|
+
),
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return result.data;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Reads `services.json` from the state directory. Returns undefined when the file does not exist. */
|
|
127
|
+
export async function readManifest(
|
|
128
|
+
state: Pick<StatePaths, "dir">,
|
|
129
|
+
): Promise<ServiceManifest | undefined> {
|
|
130
|
+
const file = manifestPathFor(state);
|
|
131
|
+
let text: string;
|
|
132
|
+
try {
|
|
133
|
+
text = await readFile(file, "utf8");
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
return parseManifest(JSON.parse(text), file);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof SyntaxError) {
|
|
142
|
+
throw new ConfigError(file, [
|
|
143
|
+
issue(
|
|
144
|
+
"PORTAL_MANIFEST_INVALID",
|
|
145
|
+
"",
|
|
146
|
+
`is not valid JSON (${error.message}).`,
|
|
147
|
+
"Run `apitogo deploy` to write a fresh services.json.",
|
|
148
|
+
),
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
type Entry = { status?: string; capabilities: Record<string, boolean> };
|
|
156
|
+
|
|
157
|
+
/** A service is usable when it has an entry and is not still provisioning. A missing status counts as ready. */
|
|
158
|
+
export function serviceReady(entry: { status?: string } | undefined): boolean {
|
|
159
|
+
return entry !== undefined && (entry.status ?? "ready") === "ready";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** True only when the service exists, is ready and declares the capability. Absent means false. */
|
|
163
|
+
export function hasCapability(entry: Entry | undefined, name: string): boolean {
|
|
164
|
+
return serviceReady(entry) && entry?.capabilities[name] === true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Services the configured portal depends on. A production build needs a manifest entry for each of them
|
|
169
|
+
* (PORTAL_MANIFEST_MISSING); a docs-only portal with no gateway needs none.
|
|
170
|
+
*/
|
|
171
|
+
export function requiredServices(
|
|
172
|
+
resolved: ResolvedConfigV2,
|
|
173
|
+
): ManifestServiceName[] {
|
|
174
|
+
const portal = resolved.portal;
|
|
175
|
+
if (!portal?.enabled) return [];
|
|
176
|
+
const needs: ManifestServiceName[] = [];
|
|
177
|
+
const consoleOn = portal.console.enabled;
|
|
178
|
+
const gatewayOn = resolved.gateway?.enabled === true;
|
|
179
|
+
const identityOn = resolved.identity?.enabled === true;
|
|
180
|
+
if (identityOn && (consoleOn || portal.requireAuth)) needs.push("identity");
|
|
181
|
+
if (gatewayOn && (consoleOn || portal.docs.enabled)) needs.push("gateway");
|
|
182
|
+
if (resolved.plans?.enabled === true && consoleOn) needs.push("plans");
|
|
183
|
+
return needs;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Problems that stop a production build: a required service without a manifest entry. */
|
|
187
|
+
export function missingManifestServices(
|
|
188
|
+
resolved: ResolvedConfigV2,
|
|
189
|
+
manifest: ServiceManifest | undefined,
|
|
190
|
+
): ConfigIssue[] {
|
|
191
|
+
return requiredServices(resolved)
|
|
192
|
+
.filter((name) => manifest?.services[name] === undefined)
|
|
193
|
+
.map((name) =>
|
|
194
|
+
issue(
|
|
195
|
+
"PORTAL_MANIFEST_MISSING",
|
|
196
|
+
`services.${name}`,
|
|
197
|
+
`the portal needs the ${name} service, but services.json has no entry for it.`,
|
|
198
|
+
`Run \`apitogo deploy --only ${name}\` first.`,
|
|
199
|
+
),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* The stand-in manifest for local runs before identity and plans exist. It is labelled `preview: true`, uses
|
|
205
|
+
* the demo issuer-less shape (no URLs are invented) and is refused by production builds.
|
|
206
|
+
*/
|
|
207
|
+
export function stubManifest(resolved: ResolvedConfigV2): ServiceManifest {
|
|
208
|
+
const portal = resolved.portal;
|
|
209
|
+
const consoleOn = portal?.enabled === true && portal.console.enabled;
|
|
210
|
+
return parseManifest({
|
|
211
|
+
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
212
|
+
preview: true,
|
|
213
|
+
project: { id: resolved.project.id, name: resolved.project.name },
|
|
214
|
+
services: {
|
|
215
|
+
...(consoleOn && resolved.identity?.enabled
|
|
216
|
+
? {
|
|
217
|
+
identity: {
|
|
218
|
+
provider: "apitogo",
|
|
219
|
+
status: "ready",
|
|
220
|
+
capabilities: { nativeSignup: true },
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
: {}),
|
|
224
|
+
...(consoleOn && resolved.plans?.enabled
|
|
225
|
+
? {
|
|
226
|
+
plans: {
|
|
227
|
+
status: "ready",
|
|
228
|
+
capabilities: { catalog: true },
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
: {}),
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Adds what a portal deploy learned (project, public URL, docs provider) to the manifest. Entries of other services
|
|
238
|
+
* are kept as they are, so deploying the portal never erases what identity or plans wrote.
|
|
239
|
+
*/
|
|
240
|
+
export function withPortalEntry(
|
|
241
|
+
existing: ServiceManifest | undefined,
|
|
242
|
+
info: {
|
|
243
|
+
projectId: string;
|
|
244
|
+
projectName: string;
|
|
245
|
+
url: string;
|
|
246
|
+
docs: { provider: string; url?: string };
|
|
247
|
+
now?: Date;
|
|
248
|
+
},
|
|
249
|
+
): ServiceManifest {
|
|
250
|
+
const base: ServiceManifest =
|
|
251
|
+
existing ??
|
|
252
|
+
parseManifest({
|
|
253
|
+
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
254
|
+
project: { name: info.projectName },
|
|
255
|
+
});
|
|
256
|
+
const previousPortal = base.services.portal;
|
|
257
|
+
return {
|
|
258
|
+
...base,
|
|
259
|
+
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
260
|
+
generatedAt: (info.now ?? new Date()).toISOString(),
|
|
261
|
+
preview: undefined,
|
|
262
|
+
project: { ...base.project, id: info.projectId, name: info.projectName },
|
|
263
|
+
services: {
|
|
264
|
+
...base.services,
|
|
265
|
+
portal: {
|
|
266
|
+
...previousPortal,
|
|
267
|
+
url: info.url.replace(/\/+$/, ""),
|
|
268
|
+
docs: info.docs,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Adds the gateway entry a gateway deploy learned. Only public values: the base URL and what the platform says the
|
|
276
|
+
* gateway can do. Entries of other services are kept as they are.
|
|
277
|
+
*/
|
|
278
|
+
export function withGatewayEntry(
|
|
279
|
+
existing: ServiceManifest | undefined,
|
|
280
|
+
info: {
|
|
281
|
+
projectId: string;
|
|
282
|
+
projectName: string;
|
|
283
|
+
provider: string;
|
|
284
|
+
baseUrl: string;
|
|
285
|
+
capabilities: Record<string, boolean>;
|
|
286
|
+
openapiUrl?: string;
|
|
287
|
+
openapiHash?: string;
|
|
288
|
+
keyHeaderName?: string;
|
|
289
|
+
now?: Date;
|
|
290
|
+
},
|
|
291
|
+
): ServiceManifest {
|
|
292
|
+
const base: ServiceManifest =
|
|
293
|
+
existing ??
|
|
294
|
+
parseManifest({
|
|
295
|
+
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
296
|
+
project: { name: info.projectName },
|
|
297
|
+
});
|
|
298
|
+
const baseUrl = info.baseUrl.replace(/\/+$/, "");
|
|
299
|
+
return {
|
|
300
|
+
...base,
|
|
301
|
+
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
302
|
+
generatedAt: (info.now ?? new Date()).toISOString(),
|
|
303
|
+
preview: undefined,
|
|
304
|
+
project: { ...base.project, id: info.projectId, name: info.projectName },
|
|
305
|
+
services: {
|
|
306
|
+
...base.services,
|
|
307
|
+
gateway: {
|
|
308
|
+
...base.services.gateway,
|
|
309
|
+
provider: info.provider,
|
|
310
|
+
status: "ready",
|
|
311
|
+
baseUrl,
|
|
312
|
+
audience: baseUrl,
|
|
313
|
+
capabilities: info.capabilities,
|
|
314
|
+
...(info.openapiUrl ? { openapiUrl: info.openapiUrl } : {}),
|
|
315
|
+
...(info.openapiHash ? { openapiHash: info.openapiHash } : {}),
|
|
316
|
+
...(info.keyHeaderName ? { keyHeaderName: info.keyHeaderName } : {}),
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Adds the identity entry an identity deploy learned: the issuer address, the portal's sign-in client and the audience
|
|
324
|
+
* of its tokens. Public values only. Entries of other services are kept as they are.
|
|
325
|
+
*/
|
|
326
|
+
export function withIdentityEntry(
|
|
327
|
+
existing: ServiceManifest | undefined,
|
|
328
|
+
info: {
|
|
329
|
+
projectId: string;
|
|
330
|
+
projectName: string;
|
|
331
|
+
/** `apitogo` for the platform's own identity, `oidc` for the customer's provider (nothing is made for it). */
|
|
332
|
+
provider?: "apitogo" | "oidc";
|
|
333
|
+
issuer: string;
|
|
334
|
+
clientId: string;
|
|
335
|
+
audience?: string;
|
|
336
|
+
nativeSignup: boolean;
|
|
337
|
+
now?: Date;
|
|
338
|
+
},
|
|
339
|
+
): ServiceManifest {
|
|
340
|
+
const base: ServiceManifest =
|
|
341
|
+
existing ??
|
|
342
|
+
parseManifest({
|
|
343
|
+
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
344
|
+
project: { name: info.projectName },
|
|
345
|
+
});
|
|
346
|
+
return {
|
|
347
|
+
...base,
|
|
348
|
+
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
349
|
+
generatedAt: (info.now ?? new Date()).toISOString(),
|
|
350
|
+
preview: undefined,
|
|
351
|
+
project: { ...base.project, id: info.projectId, name: info.projectName },
|
|
352
|
+
services: {
|
|
353
|
+
...base.services,
|
|
354
|
+
identity: {
|
|
355
|
+
...base.services.identity,
|
|
356
|
+
provider: info.provider ?? "apitogo",
|
|
357
|
+
status: "ready",
|
|
358
|
+
issuer: info.issuer.replace(/\/+$/, ""),
|
|
359
|
+
clientId: info.clientId,
|
|
360
|
+
scopes: ["openid", "profile", "email"],
|
|
361
|
+
...(info.audience ? { audience: info.audience } : {}),
|
|
362
|
+
capabilities: { nativeSignup: info.nativeSignup },
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Adds the plans entry a plans deploy learned: where the portal reaches the plans backend and what it can do. Public
|
|
370
|
+
* values only. Entries of other services are kept as they are.
|
|
371
|
+
*/
|
|
372
|
+
export function withPlansEntry(
|
|
373
|
+
existing: ServiceManifest | undefined,
|
|
374
|
+
info: {
|
|
375
|
+
projectId: string;
|
|
376
|
+
projectName: string;
|
|
377
|
+
apiBaseUrl: string;
|
|
378
|
+
billing: string;
|
|
379
|
+
catalogRevision: number;
|
|
380
|
+
capabilities: Record<string, boolean>;
|
|
381
|
+
now?: Date;
|
|
382
|
+
},
|
|
383
|
+
): ServiceManifest {
|
|
384
|
+
const base: ServiceManifest =
|
|
385
|
+
existing ??
|
|
386
|
+
parseManifest({
|
|
387
|
+
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
388
|
+
project: { name: info.projectName },
|
|
389
|
+
});
|
|
390
|
+
return {
|
|
391
|
+
...base,
|
|
392
|
+
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
393
|
+
generatedAt: (info.now ?? new Date()).toISOString(),
|
|
394
|
+
preview: undefined,
|
|
395
|
+
project: { ...base.project, id: info.projectId, name: info.projectName },
|
|
396
|
+
services: {
|
|
397
|
+
...base.services,
|
|
398
|
+
plans: {
|
|
399
|
+
...base.services.plans,
|
|
400
|
+
provider: "apitogo",
|
|
401
|
+
status: "ready",
|
|
402
|
+
apiBaseUrl: info.apiBaseUrl.replace(/\/+$/, ""),
|
|
403
|
+
billing: info.billing,
|
|
404
|
+
catalogRevision: info.catalogRevision,
|
|
405
|
+
capabilities: info.capabilities,
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Replaces `services.json` atomically. Public values only: this file ships inside the portal. */
|
|
412
|
+
export async function writeManifest(
|
|
413
|
+
state: Pick<StatePaths, "dir">,
|
|
414
|
+
manifest: ServiceManifest,
|
|
415
|
+
): Promise<void> {
|
|
416
|
+
const file = manifestPathFor(state);
|
|
417
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
418
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
419
|
+
try {
|
|
420
|
+
await writeFile(
|
|
421
|
+
temporary,
|
|
422
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
423
|
+
"utf8",
|
|
424
|
+
);
|
|
425
|
+
await rename(temporary, file);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
await rm(temporary, { force: true });
|
|
428
|
+
throw error;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Drops the entries of services that were torn down. The file is deleted when no service is left, because a
|
|
434
|
+
* manifest with no services says nothing and would only look like a deployed project.
|
|
435
|
+
*/
|
|
436
|
+
export async function removeServicesFromManifest(
|
|
437
|
+
state: Pick<StatePaths, "dir">,
|
|
438
|
+
names: readonly ManifestServiceName[],
|
|
439
|
+
): Promise<void> {
|
|
440
|
+
const existing = await readManifest(state);
|
|
441
|
+
if (!existing) return;
|
|
442
|
+
const services = Object.fromEntries(
|
|
443
|
+
Object.entries(existing.services).filter(
|
|
444
|
+
([name]) => !(names as readonly string[]).includes(name),
|
|
445
|
+
),
|
|
446
|
+
);
|
|
447
|
+
if (Object.keys(services).length === 0) {
|
|
448
|
+
await rm(manifestPathFor(state), { force: true });
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
await writeManifest(state, { ...existing, services });
|
|
452
|
+
}
|