akanjs 3.0.2 → 3.1.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/base/baseEnv.ts +0 -4
- package/base/primitiveRegistry.ts +2 -2
- package/client/cookie.ts +5 -1
- package/client/csrLocation.ts +122 -0
- package/client/csrTypes.ts +32 -4
- package/client/device.ts +2 -1
- package/client/frameConfig.ts +78 -7
- package/client/index.ts +3 -0
- package/client/makePageProto.tsx +3 -2
- package/client/native/external-view.ts +1 -0
- package/client/route/RootLayoutDefinition.ts +7 -1
- package/client/route/RouteHead.ts +108 -0
- package/client/route/index.ts +1 -0
- package/client/router.ts +36 -23
- package/client/rscNavigation.ts +8 -2
- package/client/viewportMeta.ts +60 -0
- package/client/webManifest.ts +18 -0
- package/common/Logger.ts +7 -2
- package/common/PipelineNames.ts +31 -0
- package/common/PipelinePattern.ts +70 -0
- package/common/PipelinePlanSchema.ts +490 -0
- package/common/backoff.ts +14 -0
- package/common/index.ts +39 -0
- package/common/pipelinePlan.ts +259 -0
- package/common/routeConvention.ts +1 -0
- package/common/types.ts +17 -0
- package/constant/constantRegistry.ts +26 -6
- package/dictionary/base.dictionary.ts +8 -0
- package/dictionary/trans.ts +9 -1
- package/document/into.ts +5 -0
- package/fetch/client/fetchClient.ts +135 -31
- package/fetch/client/httpClient.ts +45 -0
- package/fetch/client/wsClient.ts +125 -16
- package/fetch/fetchType/buildFetch.type.ts +1 -0
- package/index.ts +243 -10
- package/package.json +1 -5
- package/server/akanApp.ts +5 -2
- package/server/akanServer.ts +44 -8
- package/server/artifact/builderRpc.ts +8 -3
- package/server/artifact/ipcTypes.ts +5 -2
- package/server/di/diLifecycle.ts +39 -3
- package/server/di/utils.ts +28 -1
- package/server/ed25519Key.ts +48 -0
- package/server/index.ts +1 -0
- package/server/machine/index.ts +2 -0
- package/server/machine/machineAccount.ts +27 -0
- package/server/machine/machineToken.ts +151 -0
- package/server/mcp/McpApps.ts +256 -0
- package/server/mcp/McpDispatcher.ts +31 -7
- package/server/mcp/McpRouter.ts +209 -46
- package/server/ops/opsToken.ts +3 -10
- package/server/resolver/database.resolver.ts +3 -0
- package/server/resolver/signal.resolver.ts +40 -3
- package/server/routeElementComposer.tsx +78 -2
- package/server/routeState.ts +6 -0
- package/server/routeTreeBuilder.ts +26 -9
- package/server/routing/apiRouter.ts +6 -3
- package/server/routing/roomReplay.ts +88 -0
- package/server/rscClient.tsx +53 -8
- package/server/rscWorker.tsx +141 -27
- package/server/rscWorkerHost.ts +18 -5
- package/server/sitemap.ts +14 -0
- package/server/ssrFromRscRenderer.tsx +12 -6
- package/server/ssrTypes.ts +3 -1
- package/server/systemPageDocument.tsx +38 -30
- package/server/types.tsx +2 -0
- package/server/webRouter.ts +74 -5
- package/service/injectInfo.ts +9 -1
- package/service/ipcTypes.ts +2 -1
- package/service/predefinedAdaptor/database.adaptor.ts +1 -146
- package/service/predefinedAdaptor/queue.adaptor.ts +44 -6
- package/service/predefinedAdaptor/queueBackoff.ts +12 -0
- package/service/predefinedAdaptor/solidQueue.adaptor.ts +17 -13
- package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +44 -2
- package/service/predefinedAdaptor/sql/dialect/postgres.ts +3 -0
- package/service/predefinedAdaptor/sql/dialect/sqlite.ts +5 -2
- package/service/predefinedAdaptor/sql/search/fts5.ts +1 -1
- package/service/predefinedAdaptor/sql/types.ts +8 -6
- package/service/predefinedAdaptor/sql/values.ts +0 -23
- package/service/serve.ts +5 -1
- package/signal/agent.signal.ts +1 -1
- package/signal/base.signal.ts +2 -2
- package/signal/endpointIdempotency.ts +53 -0
- package/signal/internalArg.ts +14 -0
- package/signal/mcp/McpDocument.ts +38 -1
- package/signal/mcp/mcpProtocol.ts +18 -0
- package/signal/signalContext.ts +58 -30
- package/signal/signalRegistry.ts +14 -4
- package/signal/types.ts +18 -3
- package/store/agent/StoreSurfaceSource.ts +1 -1
- package/store/agentic/StToolBuilder.ts +3 -0
- package/test/conformance.ts +9 -33
- package/types/base/baseEnv.d.ts +0 -4
- package/types/base/primitiveRegistry.d.ts +1 -1
- package/types/client/csrLocation.d.ts +32 -0
- package/types/client/csrTypes.d.ts +40 -4
- package/types/client/frameConfig.d.ts +14 -2
- package/types/client/index.d.ts +3 -0
- package/types/client/native/external-view.d.ts +1 -0
- package/types/client/route/RootLayoutDefinition.d.ts +2 -1
- package/types/client/route/RouteHead.d.ts +13 -0
- package/types/client/route/index.d.ts +1 -0
- package/types/client/router.d.ts +6 -0
- package/types/client/rscNavigation.d.ts +4 -0
- package/types/client/viewportMeta.d.ts +18 -0
- package/types/client/webManifest.d.ts +4 -0
- package/types/common/PipelineNames.d.ts +6 -0
- package/types/common/PipelinePattern.d.ts +14 -0
- package/types/common/PipelinePlanSchema.d.ts +9 -0
- package/types/common/backoff.d.ts +10 -0
- package/types/common/index.d.ts +5 -0
- package/types/common/pipelinePlan.d.ts +224 -0
- package/types/common/types.d.ts +16 -0
- package/types/constant/constantRegistry.d.ts +5 -2
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/document/into.d.ts +9 -0
- package/types/fetch/client/fetchClient.d.ts +31 -4
- package/types/fetch/client/httpClient.d.ts +8 -0
- package/types/fetch/client/wsClient.d.ts +26 -2
- package/types/fetch/fetchType/buildFetch.type.d.ts +1 -0
- package/types/index.d.ts +230 -9
- package/types/server/akanServer.d.ts +16 -1
- package/types/server/artifact/builderRpc.d.ts +7 -2
- package/types/server/artifact/ipcTypes.d.ts +5 -1
- package/types/server/di/utils.d.ts +1 -0
- package/types/server/ed25519Key.d.ts +13 -0
- package/types/server/index.d.ts +1 -0
- package/types/server/machine/index.d.ts +2 -0
- package/types/server/machine/machineAccount.d.ts +5 -0
- package/types/server/machine/machineToken.d.ts +29 -0
- package/types/server/machine.d.ts +1 -0
- package/types/server/mcp/McpApps.d.ts +50 -0
- package/types/server/mcp/McpDispatcher.d.ts +4 -1
- package/types/server/mcp/McpRouter.d.ts +4 -0
- package/types/server/resolver/signal.resolver.d.ts +2 -1
- package/types/server/routeElementComposer.d.ts +24 -4
- package/types/server/routeState.d.ts +4 -1
- package/types/server/routing/roomReplay.d.ts +24 -0
- package/types/server/rscWorkerHost.d.ts +5 -2
- package/types/server/sitemap.d.ts +2 -0
- package/types/server/ssrFromRscRenderer.d.ts +3 -3
- package/types/server/ssrTypes.d.ts +4 -1
- package/types/server/systemPageDocument.d.ts +2 -0
- package/types/server/types.d.ts +4 -1
- package/types/server/webRouter.d.ts +3 -1
- package/types/service/ipcTypes.d.ts +2 -0
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +1 -24
- package/types/service/predefinedAdaptor/queue.adaptor.d.ts +10 -3
- package/types/service/predefinedAdaptor/queueBackoff.d.ts +4 -0
- package/types/service/predefinedAdaptor/solidQueue.adaptor.d.ts +3 -3
- package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +6 -1
- package/types/service/predefinedAdaptor/sql/dialect/postgres.d.ts +1 -0
- package/types/service/predefinedAdaptor/sql/dialect/sqlite.d.ts +1 -0
- package/types/service/predefinedAdaptor/sql/search/fts5.d.ts +1 -1
- package/types/service/predefinedAdaptor/sql/types.d.ts +9 -5
- package/types/service/predefinedAdaptor/sql/values.d.ts +0 -3
- package/types/service/serve.d.ts +3 -0
- package/types/signal/endpointIdempotency.d.ts +7 -0
- package/types/signal/internalArg.d.ts +10 -0
- package/types/signal/mcp/McpDocument.d.ts +8 -0
- package/types/signal/mcp/mcpProtocol.d.ts +17 -0
- package/types/signal/signalContext.d.ts +5 -3
- package/types/signal/signalRegistry.d.ts +4 -4
- package/types/signal/types.d.ts +21 -1
- package/types/store/agentic/StToolBuilder.d.ts +2 -0
- package/types/test/conformance.d.ts +7 -10
- package/types/ui/Agent/Chat.d.ts +5 -1
- package/types/ui/Agent/EmbedApproval.d.ts +1 -0
- package/types/ui/Agent/index.d.ts +3 -0
- package/types/ui/Agent/useKeyboardFrame.d.ts +2 -0
- package/types/ui/Agent/useScreenActions.d.ts +18 -0
- package/types/ui/BottomSheet.d.ts +1 -0
- package/types/ui/Constant/Doc.d.ts +11 -17
- package/types/ui/Constant/index.d.ts +7 -2
- package/types/ui/Constant/schemaDoc.d.ts +11 -0
- package/types/ui/Dialog/Modal.d.ts +6 -1
- package/types/ui/Dialog/index.d.ts +1 -1
- package/types/ui/Dialog/modalSurface.d.ts +12 -0
- package/types/ui/Layout/BottomTab.d.ts +11 -2
- package/types/ui/Layout/FrameSlotFallback.d.ts +9 -0
- package/types/ui/Layout/Navbar.d.ts +4 -1
- package/types/ui/Layout/TopInset.d.ts +1 -1
- package/types/ui/Layout/index.d.ts +3 -3
- package/types/ui/Link/Action.d.ts +5 -1
- package/types/ui/Link/Anchor.d.ts +5 -3
- package/types/ui/Link/activePath.d.ts +13 -0
- package/types/ui/Link/index.d.ts +1 -1
- package/types/ui/Modal.d.ts +6 -1
- package/types/ui/Model/EditModal.d.ts +6 -1
- package/types/ui/Model/New.d.ts +6 -1
- package/types/ui/Model/ViewEditModal.d.ts +6 -1
- package/types/ui/Model/ViewModal.d.ts +6 -1
- package/types/ui/Model/newFormOrigin.d.ts +7 -0
- package/types/ui/Portal.d.ts +3 -1
- package/types/ui/Reference/origin.d.ts +12 -0
- package/types/ui/Signal/Doc.d.ts +23 -18
- package/types/ui/Signal/Listener.d.ts +2 -6
- package/types/ui/Signal/Object.d.ts +6 -10
- package/types/ui/Signal/Response.d.ts +3 -6
- package/types/ui/Signal/RestApi.d.ts +11 -15
- package/types/ui/Signal/WebSocket.d.ts +11 -21
- package/types/ui/Signal/endpointEntries.d.ts +8 -0
- package/types/ui/Signal/index.d.ts +35 -12
- package/types/ui/Signal/signalText.d.ts +178 -0
- package/types/ui/System/CSR.d.ts +2 -2
- package/types/ui/System/Client.d.ts +4 -3
- package/types/ui/System/Common.d.ts +27 -6
- package/types/ui/System/SSR.d.ts +6 -6
- package/types/ui/Tooltip.d.ts +4 -2
- package/types/ui/index.d.ts +2 -1
- package/types/ui/overlayLayer.d.ts +6 -0
- package/types/ui/overlayPosition.d.ts +17 -0
- package/types/vendor/@akanjs/native/packages/cli/src/api.d.ts +14 -3
- package/types/vendor/@akanjs/native/packages/cli/src/commands/update.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +30 -5
- package/types/vendor/@akanjs/native/packages/cli/src/lib/compat.d.ts +8 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.d.ts +5 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/distribution.d.ts +22 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/project.d.ts +4 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/publish.d.ts +16 -10
- package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +51 -7
- package/types/vendor/@akanjs/native/packages/cli/src/lib/vectors.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.d.ts +7 -0
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows.d.ts +5 -0
- package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +36 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/plugin.d.ts +2 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/server.d.ts +11 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src/desktop.d.ts +17 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src/index.d.ts +132 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src.d.ts +1 -0
- package/types/vendor/@akanjs/native/plugins/updates/src/index.d.ts +8 -0
- package/types/vendor/@akanjs/native/plugins/window/src/desktop.d.ts +4 -1
- package/types/vendor/@akanjs/native/plugins/window/src/index.d.ts +49 -0
- package/types/vendor/use-agentic/types.d.ts +2 -0
- package/types/webkit/CsrStack.d.ts +1 -1
- package/types/webkit/McpAppAgent.d.ts +14 -0
- package/types/webkit/McpAppApprovals.d.ts +8 -0
- package/types/webkit/McpAppBridge.d.ts +101 -0
- package/types/webkit/McpAppView.d.ts +21 -0
- package/types/webkit/index.d.ts +1 -0
- package/types/webkit/useLocation.d.ts +1 -1
- package/types/webkit/useVisualViewportFrame.d.ts +13 -0
- package/ui/Agent/Chat.tsx +29 -10
- package/ui/Agent/Composer.tsx +2 -1
- package/ui/Agent/EmbedApproval.tsx +17 -0
- package/ui/Agent/RichInput.tsx +1 -1
- package/ui/Agent/index.ts +2 -0
- package/ui/Agent/useKeyboardFrame.ts +10 -0
- package/ui/Agent/useScreenActions.ts +45 -0
- package/ui/BottomSheet.tsx +82 -53
- package/ui/Constant/Doc.tsx +113 -60
- package/ui/Constant/index.ts +2 -2
- package/ui/Constant/schemaDoc.ts +86 -26
- package/ui/Dialog/LegacyModal.tsx +53 -52
- package/ui/Dialog/Modal.tsx +93 -49
- package/ui/Dialog/modalSurface.ts +61 -0
- package/ui/DragAction.tsx +1 -1
- package/ui/DraggableList.tsx +1 -1
- package/ui/Dropdown.tsx +4 -6
- package/ui/KeyboardAvoiding.tsx +1 -1
- package/ui/Layout/BottomInset.tsx +20 -14
- package/ui/Layout/BottomTab.tsx +42 -16
- package/ui/Layout/FrameSlotFallback.tsx +59 -0
- package/ui/Layout/Navbar.tsx +37 -11
- package/ui/Layout/Sider.tsx +1 -1
- package/ui/Layout/TopInset.tsx +15 -14
- package/ui/Link/Action.tsx +15 -5
- package/ui/Link/Anchor.tsx +25 -13
- package/ui/Link/activePath.ts +25 -0
- package/ui/Link/index.tsx +1 -1
- package/ui/Load/Pagination.tsx +1 -1
- package/ui/Load/Units.tsx +1 -1
- package/ui/Loading/ProgressBar.tsx +1 -1
- package/ui/Modal.tsx +9 -0
- package/ui/Model/EditModal.tsx +16 -1
- package/ui/Model/New.tsx +25 -2
- package/ui/Model/NewWrapper_Client.tsx +2 -0
- package/ui/Model/ViewEditModal.tsx +9 -0
- package/ui/Model/ViewModal.tsx +16 -1
- package/ui/Model/newFormOrigin.ts +45 -0
- package/ui/Popconfirm.tsx +5 -7
- package/ui/Portal.tsx +4 -7
- package/ui/Reference/origin.ts +31 -0
- package/ui/ScreenNavigator.tsx +1 -1
- package/ui/Select.tsx +2 -7
- package/ui/Signal/Arg.tsx +2 -2
- package/ui/Signal/Doc.tsx +125 -63
- package/ui/Signal/Endpoint.tsx +11 -10
- package/ui/Signal/Listener.tsx +7 -10
- package/ui/Signal/Object.tsx +7 -14
- package/ui/Signal/Response.tsx +24 -24
- package/ui/Signal/RestApi.tsx +63 -52
- package/ui/Signal/WebSocket.tsx +65 -72
- package/ui/Signal/endpointEntries.ts +25 -0
- package/ui/Signal/index.ts +23 -6
- package/ui/Signal/signalText.ts +59 -0
- package/ui/System/CSR.tsx +34 -8
- package/ui/System/Client.tsx +36 -5
- package/ui/System/Common.tsx +76 -32
- package/ui/System/SSR.tsx +73 -31
- package/ui/Toast.tsx +1 -1
- package/ui/Tooltip.tsx +84 -30
- package/ui/index.ts +11 -1
- package/ui/overlayLayer.ts +25 -2
- package/ui/overlayPosition.ts +90 -0
- package/ui/recipe/inputRecipe.ts +8 -1
- package/ui/styles.css +152 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +22 -2
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +37 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdateReports.kt +169 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdates.kt +110 -38
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +20 -0
- package/vendor/@akanjs/native/native/desktop/Cargo.lock +16 -16
- package/vendor/@akanjs/native/native/desktop/src/external.rs +651 -0
- package/vendor/@akanjs/native/native/desktop/src/json.rs +23 -0
- package/vendor/@akanjs/native/native/desktop/src/lib.rs +228 -5
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeApp.swift +2 -1
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +47 -1
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdateReports.swift +145 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdates.swift +112 -43
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +18 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +24 -2
- package/vendor/@akanjs/native/packages/cli/src/api.ts +22 -6
- package/vendor/@akanjs/native/packages/cli/src/commands/build.ts +24 -8
- package/vendor/@akanjs/native/packages/cli/src/commands/update.ts +11 -9
- package/vendor/@akanjs/native/packages/cli/src/config.ts +35 -7
- package/vendor/@akanjs/native/packages/cli/src/lib/compat.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +6 -4
- package/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.ts +11 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/distribution.ts +73 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/project.ts +15 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +70 -23
- package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +159 -23
- package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/platforms/android.ts +9 -4
- package/vendor/@akanjs/native/packages/cli/src/platforms/desktop-update.ts +1 -1
- package/vendor/@akanjs/native/packages/cli/src/platforms/desktop.ts +4 -2
- package/vendor/@akanjs/native/packages/cli/src/platforms/ios.ts +9 -4
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.ts +25 -11
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows.ts +14 -3
- package/vendor/@akanjs/native/packages/core/src/kernel.ts +49 -0
- package/vendor/@akanjs/native/packages/core/vectors/updates.json +31 -0
- package/vendor/@akanjs/native/packages/desktop/src/main.ts +3 -0
- package/vendor/@akanjs/native/packages/desktop/src/plugin.ts +2 -0
- package/vendor/@akanjs/native/packages/desktop/src/server.ts +70 -6
- package/vendor/@akanjs/native/plugins/external-view/native-plugin.json +23 -0
- package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +321 -0
- package/vendor/@akanjs/native/plugins/external-view/src/index.ts +106 -0
- package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +133 -41
- package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +101 -36
- package/vendor/@akanjs/native/plugins/updates/native-plugin.json +4 -2
- package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +15 -1
- package/vendor/@akanjs/native/plugins/updates/src/index.ts +7 -1
- package/vendor/@akanjs/native/plugins/window/native-plugin.json +4 -0
- package/vendor/@akanjs/native/plugins/window/src/desktop.ts +68 -1
- package/vendor/@akanjs/native/plugins/window/src/index.ts +47 -0
- package/vendor/use-agentic/ToolRunner.ts +8 -1
- package/vendor/use-agentic/types.ts +2 -0
- package/webkit/CsrRouteTable.ts +1 -0
- package/webkit/CsrStack.ts +9 -2
- package/webkit/McpAppAgent.ts +138 -0
- package/webkit/McpAppApprovals.ts +56 -0
- package/webkit/McpAppBridge.ts +291 -0
- package/webkit/McpAppView.ts +258 -0
- package/webkit/bootCsr.tsx +54 -5
- package/webkit/index.ts +1 -0
- package/webkit/useCsrValues.ts +16 -10
- package/webkit/useFrameRuntime.ts +10 -1
- package/webkit/useLocation.ts +4 -3
- package/webkit/useVisualViewportFrame.tsx +52 -0
- package/service/predefinedAdaptor/sql/driver/libsql.ts +0 -45
- package/types/service/predefinedAdaptor/sql/driver/libsql.d.ts +0 -20
- package/types/ui/Agent/useKeyboardInset.d.ts +0 -1
- package/types/ui/ServerPortal.d.ts +0 -15
- package/ui/Agent/useKeyboardInset.ts +0 -19
- package/ui/ServerPortal.tsx +0 -49
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import { PipelineNames } from "./PipelineNames";
|
|
2
|
+
import { PipelinePattern } from "./PipelinePattern";
|
|
3
|
+
import {
|
|
4
|
+
type PipelineActionName,
|
|
5
|
+
type PipelineDiagnostic,
|
|
6
|
+
type PipelinePlan,
|
|
7
|
+
type PipelinePlanActionStep,
|
|
8
|
+
type PipelinePlanContext,
|
|
9
|
+
type PipelinePlanOtaStep,
|
|
10
|
+
type PipelinePlanPhase,
|
|
11
|
+
type PipelinePlanPost,
|
|
12
|
+
type PipelinePlanResult,
|
|
13
|
+
type PipelinePlanRetry,
|
|
14
|
+
type PipelinePlanShellStep,
|
|
15
|
+
type PipelinePlanSource,
|
|
16
|
+
type PipelinePlanStage,
|
|
17
|
+
type PipelinePlanStep,
|
|
18
|
+
pipelineActionNames,
|
|
19
|
+
pipelineActions,
|
|
20
|
+
pipelineMilestones,
|
|
21
|
+
pipelineOtaPlatforms,
|
|
22
|
+
pipelinePlanLimits,
|
|
23
|
+
pipelinePlanSlots,
|
|
24
|
+
pipelinePlanVersion,
|
|
25
|
+
pipelineTriggers,
|
|
26
|
+
} from "./pipelinePlan";
|
|
27
|
+
|
|
28
|
+
type JsonObject = Record<string, unknown>;
|
|
29
|
+
|
|
30
|
+
/** Strict: an unknown key, kind, action or version is refused, never ignored. Structure only, no executor policy. */
|
|
31
|
+
export class PipelinePlanSchema {
|
|
32
|
+
static readonly #stageIdPattern = /^[A-Za-z0-9][A-Za-z0-9_-]*(?:[:.][A-Za-z0-9][A-Za-z0-9_-]*)*$/;
|
|
33
|
+
static readonly #stepIdPattern = /^[A-Za-z0-9][A-Za-z0-9_.:/-]*$/;
|
|
34
|
+
static readonly #sha256Pattern = /^[0-9a-f]{64}$/;
|
|
35
|
+
static readonly #idChars = 256;
|
|
36
|
+
static readonly #textChars = 1024;
|
|
37
|
+
static readonly #maxSeconds = 86_400;
|
|
38
|
+
static readonly #shellKeys = [
|
|
39
|
+
"id",
|
|
40
|
+
"name",
|
|
41
|
+
"timeoutSeconds",
|
|
42
|
+
"retry",
|
|
43
|
+
"allowFailure",
|
|
44
|
+
"kind",
|
|
45
|
+
"run",
|
|
46
|
+
"cwd",
|
|
47
|
+
"env",
|
|
48
|
+
"secrets",
|
|
49
|
+
"skipExitCodes",
|
|
50
|
+
] as const;
|
|
51
|
+
static readonly #actionKeys = [
|
|
52
|
+
"id",
|
|
53
|
+
"name",
|
|
54
|
+
"timeoutSeconds",
|
|
55
|
+
"retry",
|
|
56
|
+
"allowFailure",
|
|
57
|
+
"kind",
|
|
58
|
+
"action",
|
|
59
|
+
"actionVersion",
|
|
60
|
+
"with",
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
/** What an executor reads off `--out`: the size is checked before the text is parsed. */
|
|
64
|
+
static parseText(text: string): PipelinePlanResult {
|
|
65
|
+
const bytes = new TextEncoder().encode(text).length;
|
|
66
|
+
if (bytes > pipelinePlanLimits.planBytes)
|
|
67
|
+
throw new Error(`pipeline plan: ${bytes} bytes is over the ${pipelinePlanLimits.planBytes}-byte limit`);
|
|
68
|
+
let value: unknown;
|
|
69
|
+
try {
|
|
70
|
+
value = JSON.parse(text);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new Error(`pipeline plan: not JSON (${error instanceof Error ? error.message : String(error)})`);
|
|
73
|
+
}
|
|
74
|
+
return PipelinePlanSchema.parseResult(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static parseResult(value: unknown): PipelinePlanResult {
|
|
78
|
+
return new PipelinePlanSchema().#result(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static parse(value: unknown): PipelinePlan {
|
|
82
|
+
return new PipelinePlanSchema().#plan(value, "plan");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
readonly #phaseIds = new Set<string>();
|
|
86
|
+
readonly #stageIds = new Set<string>();
|
|
87
|
+
readonly #stepIds = new Set<string>();
|
|
88
|
+
readonly #stepSecrets = new Set<string>();
|
|
89
|
+
#apps = new Set<string>();
|
|
90
|
+
#stageCount = 0;
|
|
91
|
+
#stepCount = 0;
|
|
92
|
+
|
|
93
|
+
#result(value: unknown): PipelinePlanResult {
|
|
94
|
+
const result = this.#object(value, "result", ["ok", "plan", "diagnostics"]);
|
|
95
|
+
const ok = this.#boolean(result.ok, "result.ok");
|
|
96
|
+
const diagnostics = this.#array(result.diagnostics, "result.diagnostics").map((item, idx) =>
|
|
97
|
+
this.#diagnostic(item, `result.diagnostics[${idx}]`),
|
|
98
|
+
);
|
|
99
|
+
if (ok) return { ok, plan: this.#plan(result.plan, "result.plan"), diagnostics };
|
|
100
|
+
if (result.plan !== null) this.#fail("result.plan", "must be null when ok is false");
|
|
101
|
+
if (!diagnostics.some((diagnostic) => diagnostic.severity === "error"))
|
|
102
|
+
this.#fail("result.diagnostics", "must hold an error when ok is false");
|
|
103
|
+
return { ok, plan: null, diagnostics };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#diagnostic(value: unknown, path: string): PipelineDiagnostic {
|
|
107
|
+
const diagnostic = this.#object(value, path, ["severity", "code", "message", "file", "path"]);
|
|
108
|
+
return {
|
|
109
|
+
severity: this.#oneOf(diagnostic.severity, `${path}.severity`, ["error", "warning"] as const),
|
|
110
|
+
code: this.#string(diagnostic.code, `${path}.code`),
|
|
111
|
+
message: this.#string(diagnostic.message, `${path}.message`, pipelinePlanLimits.shBytes),
|
|
112
|
+
file: this.#nullable(diagnostic.file, (file) => this.#string(file, `${path}.file`)),
|
|
113
|
+
path: this.#nullable(diagnostic.path, (where) => this.#string(where, `${path}.path`)),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#plan(value: unknown, path: string): PipelinePlan {
|
|
118
|
+
const plan = this.#object(value, path, [
|
|
119
|
+
"schemaVersion",
|
|
120
|
+
"sources",
|
|
121
|
+
"planner",
|
|
122
|
+
"context",
|
|
123
|
+
"appOrder",
|
|
124
|
+
"timeoutSeconds",
|
|
125
|
+
"requiredSecrets",
|
|
126
|
+
"phases",
|
|
127
|
+
"post",
|
|
128
|
+
]);
|
|
129
|
+
if (plan.schemaVersion !== pipelinePlanVersion)
|
|
130
|
+
this.#fail(
|
|
131
|
+
`${path}.schemaVersion`,
|
|
132
|
+
`${JSON.stringify(plan.schemaVersion)} is not version ${pipelinePlanVersion}`,
|
|
133
|
+
);
|
|
134
|
+
const context = this.#context(plan.context, `${path}.context`);
|
|
135
|
+
this.#apps = new Set(context.apps);
|
|
136
|
+
const appOrder = this.#stringList(plan.appOrder, `${path}.appOrder`);
|
|
137
|
+
if (
|
|
138
|
+
appOrder.length !== context.apps.length ||
|
|
139
|
+
new Set(appOrder).size !== appOrder.length ||
|
|
140
|
+
appOrder.some((app) => !this.#apps.has(app))
|
|
141
|
+
)
|
|
142
|
+
this.#fail(`${path}.appOrder`, "must order exactly the apps of context.apps");
|
|
143
|
+
const planner = this.#object(plan.planner, `${path}.planner`, ["akanVersion"]);
|
|
144
|
+
const sources = this.#sources(plan.sources, `${path}.sources`);
|
|
145
|
+
const phases = this.#array(plan.phases, `${path}.phases`, pipelinePlanLimits.phases).map((phase, idx) =>
|
|
146
|
+
this.#phase(phase, `${path}.phases[${idx}]`),
|
|
147
|
+
);
|
|
148
|
+
const post = this.#post(plan.post, `${path}.post`);
|
|
149
|
+
const requiredSecrets = this.#secretList(plan.requiredSecrets, `${path}.requiredSecrets`);
|
|
150
|
+
if (
|
|
151
|
+
requiredSecrets.length !== this.#stepSecrets.size ||
|
|
152
|
+
requiredSecrets.some((name) => !this.#stepSecrets.has(name))
|
|
153
|
+
)
|
|
154
|
+
this.#fail(`${path}.requiredSecrets`, "must list exactly the secrets its steps name");
|
|
155
|
+
return {
|
|
156
|
+
schemaVersion: pipelinePlanVersion,
|
|
157
|
+
sources,
|
|
158
|
+
planner: { akanVersion: this.#string(planner.akanVersion, `${path}.planner.akanVersion`) },
|
|
159
|
+
context,
|
|
160
|
+
appOrder,
|
|
161
|
+
timeoutSeconds: this.#seconds(plan.timeoutSeconds, `${path}.timeoutSeconds`),
|
|
162
|
+
requiredSecrets,
|
|
163
|
+
phases,
|
|
164
|
+
post,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#sources(value: unknown, path: string): PipelinePlan["sources"] {
|
|
169
|
+
const sources = this.#object(value, path, ["workspace", "apps"]);
|
|
170
|
+
const apps = this.#object(sources.apps, `${path}.apps`);
|
|
171
|
+
return {
|
|
172
|
+
workspace: this.#nullable(sources.workspace, (source) => this.#source(source, `${path}.workspace`)),
|
|
173
|
+
apps: Object.fromEntries(
|
|
174
|
+
Object.entries(apps).map(([app, source]) => [app, this.#source(source, `${path}.apps.${app}`)]),
|
|
175
|
+
),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#source(value: unknown, path: string): PipelinePlanSource {
|
|
180
|
+
const source = this.#object(value, path, ["file", "sha256", "declared"]);
|
|
181
|
+
const sha256 = this.#string(source.sha256, `${path}.sha256`);
|
|
182
|
+
if (!PipelinePlanSchema.#sha256Pattern.test(sha256)) this.#fail(`${path}.sha256`, "must be a lowercase sha256");
|
|
183
|
+
return {
|
|
184
|
+
file: this.#string(source.file, `${path}.file`),
|
|
185
|
+
sha256,
|
|
186
|
+
declared: this.#boolean(source.declared, `${path}.declared`),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#context(value: unknown, path: string): PipelinePlanContext {
|
|
191
|
+
const context = this.#object(value, path, ["branch", "env", "apps", "trigger", "buildNum", "commit"]);
|
|
192
|
+
const apps = this.#stringList(context.apps, `${path}.apps`);
|
|
193
|
+
if (new Set(apps).size !== apps.length) this.#fail(`${path}.apps`, "lists an app twice");
|
|
194
|
+
return {
|
|
195
|
+
branch: this.#string(context.branch, `${path}.branch`),
|
|
196
|
+
env: this.#string(context.env, `${path}.env`),
|
|
197
|
+
apps,
|
|
198
|
+
trigger: this.#oneOf(context.trigger, `${path}.trigger`, pipelineTriggers),
|
|
199
|
+
buildNum: this.#int(context.buildNum, `${path}.buildNum`, 0),
|
|
200
|
+
commit: this.#nullable(context.commit, (commit) => this.#string(commit, `${path}.commit`)),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#phase(value: unknown, path: string): PipelinePlanPhase {
|
|
205
|
+
const phase = this.#object(value, path, [
|
|
206
|
+
"id",
|
|
207
|
+
"name",
|
|
208
|
+
"description",
|
|
209
|
+
"slot",
|
|
210
|
+
"milestone",
|
|
211
|
+
"action",
|
|
212
|
+
"order",
|
|
213
|
+
"maxParallel",
|
|
214
|
+
"failFast",
|
|
215
|
+
"timeoutSeconds",
|
|
216
|
+
"stages",
|
|
217
|
+
]);
|
|
218
|
+
const id = this.#string(phase.id, `${path}.id`);
|
|
219
|
+
const idError = PipelineNames.idError(id);
|
|
220
|
+
if (idError) this.#fail(`${path}.id`, idError);
|
|
221
|
+
if (this.#phaseIds.has(id)) this.#fail(`${path}.id`, `"${id}" is the id of another phase`);
|
|
222
|
+
this.#phaseIds.add(id);
|
|
223
|
+
const slot = this.#oneOf(phase.slot, `${path}.slot`, pipelinePlanSlots);
|
|
224
|
+
const action = this.#nullable(phase.action, (name) => this.#oneOf(name, `${path}.action`, pipelineActionNames));
|
|
225
|
+
const order = this.#oneOf(phase.order, `${path}.order`, ["parallel", "sequential"] as const);
|
|
226
|
+
if (action && slot !== action) this.#fail(`${path}.slot`, `a ${action} phase sits in the "${action}" slot`);
|
|
227
|
+
if (!action && (pipelineActionNames as readonly string[]).includes(slot))
|
|
228
|
+
this.#fail(`${path}.slot`, `the "${slot}" slot holds the ${slot} action phase only`);
|
|
229
|
+
if (action === "deploy" && order !== "sequential")
|
|
230
|
+
this.#fail(`${path}.order`, "a deploy phase runs its apps one at a time");
|
|
231
|
+
const stages = this.#array(phase.stages, `${path}.stages`).map((stage, idx) =>
|
|
232
|
+
this.#stage(stage, `${path}.stages[${idx}]`, action),
|
|
233
|
+
);
|
|
234
|
+
if (!stages.length) this.#fail(`${path}.stages`, "a phase holds at least one stage");
|
|
235
|
+
return {
|
|
236
|
+
id,
|
|
237
|
+
name: this.#string(phase.name, `${path}.name`, pipelinePlanLimits.nameChars),
|
|
238
|
+
description: this.#description(phase.description, `${path}.description`),
|
|
239
|
+
slot,
|
|
240
|
+
milestone: this.#oneOf(phase.milestone, `${path}.milestone`, pipelineMilestones),
|
|
241
|
+
action,
|
|
242
|
+
order,
|
|
243
|
+
maxParallel: this.#nullable(phase.maxParallel, (limit) => this.#int(limit, `${path}.maxParallel`, 1)),
|
|
244
|
+
failFast: this.#boolean(phase.failFast, `${path}.failFast`),
|
|
245
|
+
timeoutSeconds: this.#seconds(phase.timeoutSeconds, `${path}.timeoutSeconds`),
|
|
246
|
+
stages,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#stage(value: unknown, path: string, action: PipelineActionName | null): PipelinePlanStage {
|
|
251
|
+
const stage = this.#object(value, path, [
|
|
252
|
+
"id",
|
|
253
|
+
"name",
|
|
254
|
+
"description",
|
|
255
|
+
"app",
|
|
256
|
+
"milestone",
|
|
257
|
+
"skip",
|
|
258
|
+
"runtimeWhen",
|
|
259
|
+
"allowFailure",
|
|
260
|
+
"timeoutSeconds",
|
|
261
|
+
"steps",
|
|
262
|
+
]);
|
|
263
|
+
this.#stageCount += 1;
|
|
264
|
+
if (this.#stageCount > pipelinePlanLimits.stages)
|
|
265
|
+
this.#fail(path, `a plan holds at most ${pipelinePlanLimits.stages} stages`);
|
|
266
|
+
const id = this.#string(stage.id, `${path}.id`, PipelinePlanSchema.#idChars);
|
|
267
|
+
if (!PipelinePlanSchema.#stageIdPattern.test(id)) this.#fail(`${path}.id`, `"${id}" is not a stage id`);
|
|
268
|
+
if (this.#stageIds.has(id)) this.#fail(`${path}.id`, `"${id}" is the id of another stage`);
|
|
269
|
+
this.#stageIds.add(id);
|
|
270
|
+
const app = this.#nullable(stage.app, (name) => this.#string(name, `${path}.app`));
|
|
271
|
+
if (app !== null && !this.#apps.has(app)) this.#fail(`${path}.app`, `"${app}" is not in context.apps`);
|
|
272
|
+
const steps = this.#array(stage.steps, `${path}.steps`).map((step, idx) =>
|
|
273
|
+
this.#step(step, `${path}.steps[${idx}]`),
|
|
274
|
+
);
|
|
275
|
+
if (!steps.length) this.#fail(`${path}.steps`, "a stage holds at least one step");
|
|
276
|
+
if (action) {
|
|
277
|
+
const [step] = steps;
|
|
278
|
+
if (steps.length !== 1 || step?.kind !== "action" || step.action !== action || step.with.app !== app)
|
|
279
|
+
this.#fail(`${path}.steps`, `a ${action} stage holds exactly one ${action} action, for the stage's own app`);
|
|
280
|
+
} else if (steps.some((step) => step.kind === "action"))
|
|
281
|
+
this.#fail(`${path}.steps`, "an action runs in its own action phase only");
|
|
282
|
+
return {
|
|
283
|
+
id,
|
|
284
|
+
name: this.#string(stage.name, `${path}.name`, pipelinePlanLimits.nameChars),
|
|
285
|
+
description: this.#description(stage.description, `${path}.description`),
|
|
286
|
+
app,
|
|
287
|
+
milestone: this.#oneOf(stage.milestone, `${path}.milestone`, pipelineMilestones),
|
|
288
|
+
skip: this.#nullable(stage.skip, (skip) => {
|
|
289
|
+
const reason = this.#object(skip, `${path}.skip`, ["reason"]).reason;
|
|
290
|
+
return { reason: this.#string(reason, `${path}.skip.reason`) };
|
|
291
|
+
}),
|
|
292
|
+
runtimeWhen: this.#nullable(stage.runtimeWhen, (when) => {
|
|
293
|
+
const changed = this.#object(when, `${path}.runtimeWhen`, ["changed"]).changed;
|
|
294
|
+
return { changed: this.#patterns(changed, `${path}.runtimeWhen.changed`) };
|
|
295
|
+
}),
|
|
296
|
+
allowFailure: this.#boolean(stage.allowFailure, `${path}.allowFailure`),
|
|
297
|
+
timeoutSeconds: this.#seconds(stage.timeoutSeconds, `${path}.timeoutSeconds`),
|
|
298
|
+
steps,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
#step(value: unknown, path: string): PipelinePlanStep {
|
|
303
|
+
const kind = this.#oneOf(this.#object(value, path).kind, `${path}.kind`, ["sh", "action"] as const);
|
|
304
|
+
const step = this.#object(
|
|
305
|
+
value,
|
|
306
|
+
path,
|
|
307
|
+
kind === "sh" ? PipelinePlanSchema.#shellKeys : PipelinePlanSchema.#actionKeys,
|
|
308
|
+
);
|
|
309
|
+
this.#stepCount += 1;
|
|
310
|
+
if (this.#stepCount > pipelinePlanLimits.steps)
|
|
311
|
+
this.#fail(path, `a plan holds at most ${pipelinePlanLimits.steps} steps`);
|
|
312
|
+
const id = this.#string(step.id, `${path}.id`, PipelinePlanSchema.#idChars);
|
|
313
|
+
if (!PipelinePlanSchema.#stepIdPattern.test(id)) this.#fail(`${path}.id`, `"${id}" is not a step id`);
|
|
314
|
+
if (this.#stepIds.has(id)) this.#fail(`${path}.id`, `"${id}" is the id of another step`);
|
|
315
|
+
this.#stepIds.add(id);
|
|
316
|
+
const base = {
|
|
317
|
+
id,
|
|
318
|
+
name: this.#string(step.name, `${path}.name`, pipelinePlanLimits.nameChars),
|
|
319
|
+
timeoutSeconds: this.#seconds(step.timeoutSeconds, `${path}.timeoutSeconds`),
|
|
320
|
+
retry: this.#nullable(step.retry, (retry) => this.#retry(retry, `${path}.retry`)),
|
|
321
|
+
allowFailure: this.#boolean(step.allowFailure, `${path}.allowFailure`),
|
|
322
|
+
};
|
|
323
|
+
return kind === "sh" ? this.#shell(step, path, base) : this.#action(step, path, base);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
#shell(
|
|
327
|
+
step: JsonObject,
|
|
328
|
+
path: string,
|
|
329
|
+
base: Omit<PipelinePlanShellStep, "kind" | "run" | "cwd" | "env" | "secrets" | "skipExitCodes">,
|
|
330
|
+
): PipelinePlanShellStep {
|
|
331
|
+
const run = this.#string(step.run, `${path}.run`, Number.POSITIVE_INFINITY);
|
|
332
|
+
if (new TextEncoder().encode(run).length > pipelinePlanLimits.shBytes)
|
|
333
|
+
this.#fail(`${path}.run`, `a script is at most ${pipelinePlanLimits.shBytes} bytes`);
|
|
334
|
+
const cwd = this.#string(step.cwd, `${path}.cwd`);
|
|
335
|
+
if (cwd.startsWith("/") || cwd.startsWith("~") || cwd.includes("\\") || cwd.split("/").includes(".."))
|
|
336
|
+
this.#fail(`${path}.cwd`, `"${cwd}" is not a path inside the workspace`);
|
|
337
|
+
const env = this.#object(step.env, `${path}.env`);
|
|
338
|
+
for (const [name, envValue] of Object.entries(env)) {
|
|
339
|
+
const nameError = PipelineNames.envNameError(name);
|
|
340
|
+
if (nameError) this.#fail(`${path}.env`, nameError);
|
|
341
|
+
this.#string(envValue, `${path}.env.${name}`, pipelinePlanLimits.shBytes, { allowEmpty: true });
|
|
342
|
+
}
|
|
343
|
+
const secrets = this.#secretList(step.secrets, `${path}.secrets`);
|
|
344
|
+
for (const name of secrets) this.#stepSecrets.add(name);
|
|
345
|
+
const skipExitCodes = this.#array(step.skipExitCodes, `${path}.skipExitCodes`).map((code, idx) =>
|
|
346
|
+
this.#int(code, `${path}.skipExitCodes[${idx}]`, 1, 255),
|
|
347
|
+
);
|
|
348
|
+
if (new Set(skipExitCodes).size !== skipExitCodes.length) this.#fail(`${path}.skipExitCodes`, "lists a code twice");
|
|
349
|
+
return { ...base, kind: "sh", run, cwd, env: env as Record<string, string>, secrets, skipExitCodes };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
#action(
|
|
353
|
+
step: JsonObject,
|
|
354
|
+
path: string,
|
|
355
|
+
base: Omit<PipelinePlanActionStep, "kind" | "action" | "actionVersion" | "with">,
|
|
356
|
+
): PipelinePlanActionStep {
|
|
357
|
+
const action = this.#oneOf(step.action, `${path}.action`, pipelineActionNames);
|
|
358
|
+
if (step.actionVersion !== pipelineActions[action].version)
|
|
359
|
+
this.#fail(`${path}.actionVersion`, `${JSON.stringify(step.actionVersion)} is not a version of ${action}`);
|
|
360
|
+
const actionVersion = pipelineActions[action].version;
|
|
361
|
+
if (action === "ota")
|
|
362
|
+
return { ...base, kind: "action", action, actionVersion, with: this.#otaWith(step.with, path) };
|
|
363
|
+
const app = this.#object(step.with, `${path}.with`, ["app"]).app;
|
|
364
|
+
return { ...base, kind: "action", action, actionVersion, with: { app: this.#string(app, `${path}.with.app`) } };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
#otaWith(value: unknown, path: string): PipelinePlanOtaStep["with"] {
|
|
368
|
+
const raw = this.#object(value, `${path}.with`, ["app", "targets", "platforms"]);
|
|
369
|
+
const targets = this.#stringList(raw.targets, `${path}.with.targets`);
|
|
370
|
+
const platforms = this.#array(raw.platforms, `${path}.with.platforms`).map((platform, idx) =>
|
|
371
|
+
this.#oneOf(platform, `${path}.with.platforms[${idx}]`, pipelineOtaPlatforms),
|
|
372
|
+
);
|
|
373
|
+
for (const [key, list] of [
|
|
374
|
+
["targets", targets],
|
|
375
|
+
["platforms", platforms],
|
|
376
|
+
] as const) {
|
|
377
|
+
if (!list.length) this.#fail(`${path}.with.${key}`, "may not be empty");
|
|
378
|
+
if (new Set(list).size !== list.length) this.#fail(`${path}.with.${key}`, "lists one twice");
|
|
379
|
+
}
|
|
380
|
+
return { app: this.#string(raw.app, `${path}.with.app`), targets, platforms };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
#post(value: unknown, path: string): PipelinePlanPost {
|
|
384
|
+
const post = this.#object(value, path, ["success", "failure", "canceled", "always"]);
|
|
385
|
+
const steps = (result: keyof PipelinePlanPost) =>
|
|
386
|
+
this.#array(post[result], `${path}.${result}`).map((step, idx) => {
|
|
387
|
+
const parsed = this.#step(step, `${path}.${result}[${idx}]`);
|
|
388
|
+
if (parsed.kind !== "sh") this.#fail(`${path}.${result}[${idx}]`, "a post step is a shell step");
|
|
389
|
+
return parsed;
|
|
390
|
+
});
|
|
391
|
+
return {
|
|
392
|
+
success: steps("success"),
|
|
393
|
+
failure: steps("failure"),
|
|
394
|
+
canceled: steps("canceled"),
|
|
395
|
+
always: steps("always"),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
#retry(value: unknown, path: string): PipelinePlanRetry {
|
|
400
|
+
const retry = this.#object(value, path, ["attempts", "delaySeconds"]);
|
|
401
|
+
return {
|
|
402
|
+
attempts: this.#int(retry.attempts, `${path}.attempts`, 1, pipelinePlanLimits.retryAttempts),
|
|
403
|
+
delaySeconds: this.#int(retry.delaySeconds, `${path}.delaySeconds`, 0, pipelinePlanLimits.retryDelaySeconds),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
#patterns(value: unknown, path: string) {
|
|
408
|
+
const patterns = this.#stringList(value, path);
|
|
409
|
+
const patternError = PipelinePattern.validate(patterns);
|
|
410
|
+
if (patternError) this.#fail(path, patternError);
|
|
411
|
+
return patterns;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
#secretList(value: unknown, path: string) {
|
|
415
|
+
const names = this.#stringList(value, path);
|
|
416
|
+
for (const name of names) {
|
|
417
|
+
const nameError = PipelineNames.secretNameError(name);
|
|
418
|
+
if (nameError) this.#fail(path, nameError);
|
|
419
|
+
}
|
|
420
|
+
if (new Set(names).size !== names.length) this.#fail(path, "lists a secret twice");
|
|
421
|
+
return names;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
#stringList(value: unknown, path: string) {
|
|
425
|
+
return this.#array(value, path).map((item, idx) => this.#string(item, `${path}[${idx}]`));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
#description(value: unknown, path: string) {
|
|
429
|
+
return this.#nullable(value, (text) => this.#string(text, path, pipelinePlanLimits.descriptionChars));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
#seconds(value: unknown, path: string) {
|
|
433
|
+
return this.#nullable(value, (seconds) => this.#int(seconds, path, 1, PipelinePlanSchema.#maxSeconds));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
#nullable<T>(value: unknown, parse: (value: unknown) => T): T | null {
|
|
437
|
+
return value === null ? null : parse(value);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
#object(value: unknown, path: string, keys?: readonly string[]): JsonObject {
|
|
441
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) this.#fail(path, "must be an object");
|
|
442
|
+
const object = value as JsonObject;
|
|
443
|
+
if (!keys) return object;
|
|
444
|
+
for (const key of Object.keys(object)) if (!keys.includes(key)) this.#fail(path, `"${key}" is not a known key`);
|
|
445
|
+
for (const key of keys) if (!(key in object)) this.#fail(path, `"${key}" is missing`);
|
|
446
|
+
return object;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
#array(value: unknown, path: string, max = Number.POSITIVE_INFINITY): unknown[] {
|
|
450
|
+
if (!Array.isArray(value)) this.#fail(path, "must be an array");
|
|
451
|
+
const items = value as unknown[];
|
|
452
|
+
if (items.length > max) this.#fail(path, `holds at most ${max} items`);
|
|
453
|
+
return items;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
#string(
|
|
457
|
+
value: unknown,
|
|
458
|
+
path: string,
|
|
459
|
+
max: number = PipelinePlanSchema.#textChars,
|
|
460
|
+
{ allowEmpty = false }: { allowEmpty?: boolean } = {},
|
|
461
|
+
): string {
|
|
462
|
+
if (typeof value !== "string") this.#fail(path, "must be a string");
|
|
463
|
+
const text = value as string;
|
|
464
|
+
if (!allowEmpty && !text) this.#fail(path, "may not be empty");
|
|
465
|
+
if (text.length > max) this.#fail(path, `is longer than ${max} characters`);
|
|
466
|
+
return text;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
#boolean(value: unknown, path: string): boolean {
|
|
470
|
+
if (typeof value !== "boolean") this.#fail(path, "must be a boolean");
|
|
471
|
+
return value as boolean;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
#int(value: unknown, path: string, min: number, max = Number.MAX_SAFE_INTEGER): number {
|
|
475
|
+
if (typeof value !== "number" || !Number.isInteger(value)) this.#fail(path, "must be an integer");
|
|
476
|
+
const number = value as number;
|
|
477
|
+
if (number < min || number > max) this.#fail(path, `must be between ${min} and ${max}`);
|
|
478
|
+
return number;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
#oneOf<T extends string>(value: unknown, path: string, options: readonly T[]): T {
|
|
482
|
+
if (typeof value !== "string" || !(options as readonly string[]).includes(value))
|
|
483
|
+
this.#fail(path, `${JSON.stringify(value)} is not one of ${options.join(", ")}`);
|
|
484
|
+
return value as T;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
#fail(path: string, reason: string): never {
|
|
488
|
+
throw new Error(`pipeline plan ${path}: ${reason}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface BackoffPolicy {
|
|
2
|
+
/** The first wait in ms; each attempt in a row doubles it. */
|
|
3
|
+
min?: number;
|
|
4
|
+
max?: number;
|
|
5
|
+
/** The share of each wait drawn at random, 0 to 1, so callers one outage dropped together come back apart. */
|
|
6
|
+
jitter?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class Backoff {
|
|
10
|
+
static delay(attempt: number, { min = 1_000, max = 30_000, jitter = 0.5 }: BackoffPolicy = {}) {
|
|
11
|
+
const ceiling = Math.min(max, min * 2 ** Math.max(0, attempt - 1));
|
|
12
|
+
return Math.round(ceiling * (1 - Math.min(1, Math.max(0, jitter)) * Math.random()));
|
|
13
|
+
}
|
|
14
|
+
}
|
package/common/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export {
|
|
|
7
7
|
legacyAuthTokenKey,
|
|
8
8
|
readAuthToken,
|
|
9
9
|
} from "./authToken";
|
|
10
|
+
export { Backoff, type BackoffPolicy } from "./backoff";
|
|
10
11
|
export { CodeAgentClient, type CodeAgentTransport } from "./CodeAgentClient";
|
|
11
12
|
export { CodeTranscript, type CodeTranscriptPart } from "./CodeTranscript";
|
|
12
13
|
export { capitalize, lowerlize } from "./capitalize";
|
|
@@ -127,7 +128,45 @@ export {
|
|
|
127
128
|
} from "./mcpExposure";
|
|
128
129
|
export { deepObjectify, objectify, plainFieldsOf } from "./objectify";
|
|
129
130
|
export { pathGet, pathGetLoose, pathSet, toPathSegments } from "./objectPath";
|
|
131
|
+
export { PipelineNames } from "./PipelineNames";
|
|
132
|
+
export { PipelinePattern } from "./PipelinePattern";
|
|
133
|
+
export { PipelinePlanSchema } from "./PipelinePlanSchema";
|
|
130
134
|
export { formatPhone, isPhoneNumber } from "./phone";
|
|
135
|
+
export {
|
|
136
|
+
type PipelineActionName,
|
|
137
|
+
type PipelineDiagnostic,
|
|
138
|
+
type PipelineMilestone,
|
|
139
|
+
type PipelineOtaPlatform,
|
|
140
|
+
type PipelinePlan,
|
|
141
|
+
type PipelinePlanActionStep,
|
|
142
|
+
type PipelinePlanAppActionStep,
|
|
143
|
+
type PipelinePlanContext,
|
|
144
|
+
type PipelinePlanOtaStep,
|
|
145
|
+
type PipelinePlanPhase,
|
|
146
|
+
type PipelinePlanPost,
|
|
147
|
+
type PipelinePlanResult,
|
|
148
|
+
type PipelinePlanRetry,
|
|
149
|
+
type PipelinePlanShellStep,
|
|
150
|
+
type PipelinePlanSlot,
|
|
151
|
+
type PipelinePlanSource,
|
|
152
|
+
type PipelinePlanStage,
|
|
153
|
+
type PipelinePlanStep,
|
|
154
|
+
type PipelineSkip,
|
|
155
|
+
type PipelineTrigger,
|
|
156
|
+
pipelineActionNames,
|
|
157
|
+
pipelineActions,
|
|
158
|
+
pipelineEnvNamePattern,
|
|
159
|
+
pipelineIdPattern,
|
|
160
|
+
pipelineMilestones,
|
|
161
|
+
pipelineOtaPlatforms,
|
|
162
|
+
pipelinePlanLimits,
|
|
163
|
+
pipelinePlanSlots,
|
|
164
|
+
pipelinePlanVersion,
|
|
165
|
+
pipelineReservedEnv,
|
|
166
|
+
pipelineRuntimeEnv,
|
|
167
|
+
pipelineSecretNamePattern,
|
|
168
|
+
pipelineTriggers,
|
|
169
|
+
} from "./pipelinePlan";
|
|
131
170
|
export { type RouteLayer, RouteLayering } from "./RouteLayering";
|
|
132
171
|
export { randomPick, randomPicks } from "./randomPick";
|
|
133
172
|
export { hostFromRequest, isJsonContentType, originFromRequest } from "./requestOrigin";
|