gitspace 0.2.0-rc.20 → 0.2.0-rc.21
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/package.json +11 -6
- package/.claude/settings.local.json +0 -10
- package/.gitspace/bundle.json +0 -50
- package/.gitspace/events.json +0 -11
- package/.gitspace/processes.json +0 -23
- package/.gitspace/scripts/select/01-status.sh +0 -39
- package/.gitspace/scripts/setup/01-install-deps.sh +0 -12
- package/.gitspace/scripts/setup/02-typecheck.sh +0 -16
- package/AGENTS.md +0 -469
- package/CLAUDE.md +0 -1
- package/bun.lock +0 -794
- package/docs/CONNECTION.md +0 -623
- package/docs/GATEWAY-WORKER.md +0 -319
- package/docs/GETTING-STARTED.md +0 -448
- package/docs/GITSPACE-PLATFORM.md +0 -1819
- package/docs/INFRASTRUCTURE.md +0 -1347
- package/docs/PROTOCOL.md +0 -619
- package/docs/QUICKSTART.md +0 -183
- package/docs/RELAY.md +0 -327
- package/docs/REMOTE-DESIGN.md +0 -554
- package/docs/ROADMAP.md +0 -564
- package/docs/SITE_DOCS_FIGMA_MAKE.md +0 -1176
- package/docs/STACK-DESIGN.md +0 -588
- package/docs/UNIFIED_ARCHITECTURE.md +0 -138
- package/experiments/pty-benchmark.ts +0 -148
- package/experiments/pty-latency.ts +0 -100
- package/experiments/router/client.ts +0 -199
- package/experiments/router/protocol.ts +0 -74
- package/experiments/router/router.ts +0 -217
- package/experiments/router/session.ts +0 -180
- package/experiments/router/test.ts +0 -133
- package/experiments/socket-bandwidth.ts +0 -77
- package/homebrew/gitspace.rb +0 -45
- package/landing-page/ATTRIBUTIONS.md +0 -3
- package/landing-page/README.md +0 -11
- package/landing-page/bun.lock +0 -801
- package/landing-page/guidelines/Guidelines.md +0 -61
- package/landing-page/index.html +0 -37
- package/landing-page/package.json +0 -90
- package/landing-page/postcss.config.mjs +0 -15
- package/landing-page/public/_redirects +0 -1
- package/landing-page/public/favicon.png +0 -0
- package/landing-page/src/app/App.tsx +0 -53
- package/landing-page/src/app/components/figma/ImageWithFallback.tsx +0 -27
- package/landing-page/src/app/components/ui/accordion.tsx +0 -66
- package/landing-page/src/app/components/ui/alert-dialog.tsx +0 -157
- package/landing-page/src/app/components/ui/alert.tsx +0 -66
- package/landing-page/src/app/components/ui/aspect-ratio.tsx +0 -11
- package/landing-page/src/app/components/ui/avatar.tsx +0 -53
- package/landing-page/src/app/components/ui/badge.tsx +0 -46
- package/landing-page/src/app/components/ui/breadcrumb.tsx +0 -109
- package/landing-page/src/app/components/ui/button.tsx +0 -57
- package/landing-page/src/app/components/ui/calendar.tsx +0 -75
- package/landing-page/src/app/components/ui/card.tsx +0 -92
- package/landing-page/src/app/components/ui/carousel.tsx +0 -241
- package/landing-page/src/app/components/ui/chart.tsx +0 -353
- package/landing-page/src/app/components/ui/checkbox.tsx +0 -32
- package/landing-page/src/app/components/ui/collapsible.tsx +0 -33
- package/landing-page/src/app/components/ui/command.tsx +0 -177
- package/landing-page/src/app/components/ui/context-menu.tsx +0 -252
- package/landing-page/src/app/components/ui/dialog.tsx +0 -135
- package/landing-page/src/app/components/ui/drawer.tsx +0 -132
- package/landing-page/src/app/components/ui/dropdown-menu.tsx +0 -257
- package/landing-page/src/app/components/ui/form.tsx +0 -168
- package/landing-page/src/app/components/ui/hover-card.tsx +0 -44
- package/landing-page/src/app/components/ui/input-otp.tsx +0 -77
- package/landing-page/src/app/components/ui/input.tsx +0 -21
- package/landing-page/src/app/components/ui/label.tsx +0 -24
- package/landing-page/src/app/components/ui/menubar.tsx +0 -276
- package/landing-page/src/app/components/ui/navigation-menu.tsx +0 -168
- package/landing-page/src/app/components/ui/pagination.tsx +0 -127
- package/landing-page/src/app/components/ui/popover.tsx +0 -48
- package/landing-page/src/app/components/ui/progress.tsx +0 -31
- package/landing-page/src/app/components/ui/radio-group.tsx +0 -45
- package/landing-page/src/app/components/ui/resizable.tsx +0 -56
- package/landing-page/src/app/components/ui/scroll-area.tsx +0 -58
- package/landing-page/src/app/components/ui/select.tsx +0 -189
- package/landing-page/src/app/components/ui/separator.tsx +0 -28
- package/landing-page/src/app/components/ui/sheet.tsx +0 -139
- package/landing-page/src/app/components/ui/sidebar.tsx +0 -726
- package/landing-page/src/app/components/ui/skeleton.tsx +0 -13
- package/landing-page/src/app/components/ui/slider.tsx +0 -63
- package/landing-page/src/app/components/ui/sonner.tsx +0 -25
- package/landing-page/src/app/components/ui/switch.tsx +0 -31
- package/landing-page/src/app/components/ui/table.tsx +0 -116
- package/landing-page/src/app/components/ui/tabs.tsx +0 -66
- package/landing-page/src/app/components/ui/textarea.tsx +0 -18
- package/landing-page/src/app/components/ui/toggle-group.tsx +0 -73
- package/landing-page/src/app/components/ui/toggle.tsx +0 -47
- package/landing-page/src/app/components/ui/tooltip.tsx +0 -61
- package/landing-page/src/app/components/ui/use-mobile.ts +0 -21
- package/landing-page/src/app/components/ui/utils.ts +0 -6
- package/landing-page/src/components/docs/DocsContent.tsx +0 -801
- package/landing-page/src/components/docs/DocsSidebar.tsx +0 -90
- package/landing-page/src/components/landing/CTA.tsx +0 -59
- package/landing-page/src/components/landing/Comparison.tsx +0 -84
- package/landing-page/src/components/landing/FaultyTerminal.tsx +0 -424
- package/landing-page/src/components/landing/Features.tsx +0 -201
- package/landing-page/src/components/landing/Hero.tsx +0 -142
- package/landing-page/src/components/landing/Pricing.tsx +0 -140
- package/landing-page/src/components/landing/Roadmap.tsx +0 -86
- package/landing-page/src/components/landing/Security.tsx +0 -81
- package/landing-page/src/components/landing/TerminalWindow.tsx +0 -27
- package/landing-page/src/components/landing/UseCases.tsx +0 -55
- package/landing-page/src/components/landing/Workflow.tsx +0 -101
- package/landing-page/src/components/layout/DashboardNavbar.tsx +0 -37
- package/landing-page/src/components/layout/Footer.tsx +0 -55
- package/landing-page/src/components/layout/LandingNavbar.tsx +0 -82
- package/landing-page/src/components/ui/badge.tsx +0 -39
- package/landing-page/src/components/ui/breadcrumb.tsx +0 -115
- package/landing-page/src/components/ui/button.tsx +0 -57
- package/landing-page/src/components/ui/card.tsx +0 -79
- package/landing-page/src/components/ui/mock-terminal.tsx +0 -68
- package/landing-page/src/components/ui/separator.tsx +0 -28
- package/landing-page/src/lib/utils.ts +0 -6
- package/landing-page/src/main.tsx +0 -10
- package/landing-page/src/pages/Dashboard.tsx +0 -133
- package/landing-page/src/pages/DocsPage.tsx +0 -79
- package/landing-page/src/pages/LandingPage.tsx +0 -31
- package/landing-page/src/pages/TerminalView.tsx +0 -106
- package/landing-page/src/styles/fonts.css +0 -0
- package/landing-page/src/styles/index.css +0 -3
- package/landing-page/src/styles/tailwind.css +0 -4
- package/landing-page/src/styles/theme.css +0 -181
- package/landing-page/vite.config.ts +0 -19
- package/scripts/GHOSTTY_TAB_BUG.md +0 -106
- package/scripts/build.ts +0 -298
- package/scripts/migrate-secrets.ts +0 -77
- package/scripts/release.ts +0 -140
- package/scripts/sample-events.ts +0 -263
- package/scripts/test-tabs-minimal.ts +0 -68
- package/scripts/test-tabs-workaround.ts +0 -95
- package/scripts/test-tabs.ts +0 -171
- package/src/__tests__/test-utils.ts +0 -298
- package/src/app/input/__tests__/sessionCommands.test.ts +0 -40
- package/src/app/input/sessionCommands.ts +0 -94
- package/src/app/session/__tests__/useAttachController.test.ts +0 -229
- package/src/app/session/createSessionBackend.bun.ts +0 -76
- package/src/app/session/createSessionBackend.web.ts +0 -104
- package/src/app/session/types.ts +0 -16
- package/src/app/session/useAttachController.ts +0 -220
- package/src/app/session/useProcessActions.ts +0 -201
- package/src/app/session/useSessionClient.ts +0 -35
- package/src/app/session/useWorkspaceDeleteFlow.ts +0 -170
- package/src/app.tui.tsx +0 -2929
- package/src/app.web.tsx +0 -1454
- package/src/commands/__tests__/connect-key.test.ts +0 -10
- package/src/commands/__tests__/events.test.ts +0 -201
- package/src/commands/__tests__/notifications.test.ts +0 -349
- package/src/commands/__tests__/process.test.ts +0 -251
- package/src/commands/__tests__/serve-messages.test.ts +0 -190
- package/src/commands/__tests__/serve-process-hosting.test.ts +0 -63
- package/src/commands/access.ts +0 -298
- package/src/commands/add.ts +0 -455
- package/src/commands/auth.ts +0 -369
- package/src/commands/bundle.ts +0 -232
- package/src/commands/config.ts +0 -242
- package/src/commands/connect-key.ts +0 -1
- package/src/commands/connect.ts +0 -576
- package/src/commands/directory.ts +0 -16
- package/src/commands/events.ts +0 -157
- package/src/commands/host.ts +0 -566
- package/src/commands/identity.ts +0 -184
- package/src/commands/linear.ts +0 -717
- package/src/commands/list.ts +0 -181
- package/src/commands/migrate.ts +0 -52
- package/src/commands/notifications.ts +0 -351
- package/src/commands/process.ts +0 -104
- package/src/commands/relay.ts +0 -315
- package/src/commands/remove.ts +0 -279
- package/src/commands/review.ts +0 -787
- package/src/commands/serve.ts +0 -1946
- package/src/commands/share.ts +0 -451
- package/src/commands/status.ts +0 -125
- package/src/commands/switch.ts +0 -361
- package/src/commands/tmux.ts +0 -317
- package/src/components/DPad.web.tsx +0 -343
- package/src/components/DiffViewer.web.tsx +0 -1192
- package/src/components/Events.tsx +0 -137
- package/src/components/Events.tui.tsx +0 -129
- package/src/components/Events.web.tsx +0 -386
- package/src/components/FloatingControls.web.tsx +0 -112
- package/src/components/FloatingJogWheel.web.tsx +0 -240
- package/src/components/Flow.tsx +0 -458
- package/src/components/Flow.tui.tsx +0 -343
- package/src/components/Flow.web.tsx +0 -442
- package/src/components/Inbox.tsx +0 -448
- package/src/components/Inbox.tui.tsx +0 -262
- package/src/components/Inbox.web.tsx +0 -329
- package/src/components/MachineList.tsx +0 -187
- package/src/components/MachineList.tui.tsx +0 -161
- package/src/components/MachineList.web.tsx +0 -210
- package/src/components/NumPad.web.tsx +0 -270
- package/src/components/ProjectList.tsx +0 -175
- package/src/components/ProjectList.tui.tsx +0 -109
- package/src/components/ProjectList.web.tsx +0 -143
- package/src/components/ProjectOnboardingStep.ts +0 -23
- package/src/components/ProjectOnboardingStep.tui.tsx +0 -88
- package/src/components/ProjectOnboardingStep.web.tsx +0 -59
- package/src/components/RemoteMachineScreen.tui.tsx +0 -690
- package/src/components/ScriptTerminal.tui.tsx +0 -160
- package/src/components/ScriptTerminal.web.tsx +0 -89
- package/src/components/SessionTerminal.tui.tsx +0 -406
- package/src/components/SessionTerminal.web.tsx +0 -467
- package/src/components/SpacesBrowser.tsx +0 -540
- package/src/components/SpacesBrowser.tui.tsx +0 -258
- package/src/components/SpacesBrowser.web.tsx +0 -332
- package/src/components/TerminalControls.web.tsx +0 -464
- package/src/components/ThreadPanel.web.tsx +0 -798
- package/src/components/__tests__/SpacesBrowser.test.ts +0 -541
- package/src/components/__tests__/SpacesBrowser.tui.test.tsx +0 -249
- package/src/components/__tests__/script-terminal-buffer.tui.test.ts +0 -72
- package/src/components/index.ts +0 -105
- package/src/components/review-decision-colors.ts +0 -11
- package/src/components/script-terminal-buffer.tui.ts +0 -37
- package/src/components/session-terminal-page-navigation.ts +0 -48
- package/src/components/terminal-bracketed-paste.tui.test.ts +0 -43
- package/src/components/terminal-bracketed-paste.tui.ts +0 -46
- package/src/core/__tests__/access.test.ts +0 -240
- package/src/core/__tests__/bundle-refresh.test.ts +0 -567
- package/src/core/__tests__/bundle.test.ts +0 -209
- package/src/core/__tests__/github-review.test.ts +0 -781
- package/src/core/__tests__/project-lifecycle.test.ts +0 -137
- package/src/core/__tests__/workspace-lifecycle.test.ts +0 -159
- package/src/core/__tests__/workspace.test.ts +0 -149
- package/src/core/access.ts +0 -277
- package/src/core/bundle-refresh.ts +0 -1064
- package/src/core/bundle.ts +0 -326
- package/src/core/config.ts +0 -405
- package/src/core/git.ts +0 -768
- package/src/core/github-review.ts +0 -761
- package/src/core/github.ts +0 -151
- package/src/core/identity.ts +0 -631
- package/src/core/linear.ts +0 -403
- package/src/core/preferences-service.ts +0 -17
- package/src/core/project-catalog.ts +0 -52
- package/src/core/project-lifecycle.ts +0 -163
- package/src/core/review-executor.ts +0 -316
- package/src/core/review.ts +0 -407
- package/src/core/secret-runtime.ts +0 -167
- package/src/core/shell.ts +0 -117
- package/src/core/trusted-relays.ts +0 -315
- package/src/core/workspace-lifecycle.ts +0 -216
- package/src/core/workspace.ts +0 -363
- package/src/hooks/__tests__/useLocalSession.tui.test.ts +0 -557
- package/src/hooks/index.ts +0 -8
- package/src/hooks/index.tui.ts +0 -32
- package/src/hooks/useDaemonStatus.tui.ts +0 -174
- package/src/hooks/useLocalSession.tui.ts +0 -395
- package/src/hooks/useRelayConnection.web.ts +0 -54
- package/src/hooks/useRemoteMachines.tui.ts +0 -166
- package/src/hooks/useRemoteTerminal.tui.ts +0 -22
- package/src/hooks/useReview.web.ts +0 -248
- package/src/hooks/useTerminal.web.ts +0 -36
- package/src/hooks/useUserActivity.ts +0 -61
- package/src/hooks/useVisualViewport.web.ts +0 -104
- package/src/index.ts +0 -1376
- package/src/lib/events/__tests__/collector-filter.test.ts +0 -105
- package/src/lib/events/__tests__/store-query.test.ts +0 -103
- package/src/lib/events/collector.ts +0 -494
- package/src/lib/events/filters.ts +0 -26
- package/src/lib/events/index.ts +0 -11
- package/src/lib/events/indexer.ts +0 -14
- package/src/lib/events/paths.ts +0 -69
- package/src/lib/events/reader.ts +0 -212
- package/src/lib/events/store.ts +0 -141
- package/src/lib/invite.web.ts +0 -58
- package/src/lib/preferences-service.web.ts +0 -41
- package/src/lib/processes/__tests__/config.test.ts +0 -83
- package/src/lib/processes/__tests__/names.test.ts +0 -125
- package/src/lib/processes/__tests__/schema.test.ts +0 -208
- package/src/lib/processes/__tests__/watchdog.test.ts +0 -210
- package/src/lib/processes/autostart.ts +0 -16
- package/src/lib/processes/config.ts +0 -187
- package/src/lib/processes/control.ts +0 -53
- package/src/lib/processes/editor.ts +0 -32
- package/src/lib/processes/events-config.ts +0 -37
- package/src/lib/processes/index.ts +0 -14
- package/src/lib/processes/instances.ts +0 -20
- package/src/lib/processes/manager.ts +0 -131
- package/src/lib/processes/names.ts +0 -71
- package/src/lib/processes/registry.ts +0 -26
- package/src/lib/processes/runner.ts +0 -211
- package/src/lib/processes/scheduler.ts +0 -17
- package/src/lib/processes/schema.ts +0 -74
- package/src/lib/processes/session-list.ts +0 -15
- package/src/lib/processes/state.ts +0 -82
- package/src/lib/processes/watchdog.test.ts +0 -79
- package/src/lib/processes/watchdog.ts +0 -106
- package/src/lib/remote-session/__tests__/protocol.test.ts +0 -291
- package/src/lib/remote-session/index.ts +0 -7
- package/src/lib/remote-session/protocol.ts +0 -443
- package/src/lib/remote-session/session-handler.ts +0 -1298
- package/src/lib/remote-session/workspace-scanner.ts +0 -161
- package/src/lib/sonner.web.ts +0 -1
- package/src/lib/storage/identity-store.web.ts +0 -94
- package/src/lib/tmux-lite/README.md +0 -81
- package/src/lib/tmux-lite/cli.ts +0 -855
- package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +0 -349
- package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +0 -291
- package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +0 -142
- package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +0 -339
- package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +0 -477
- package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +0 -499
- package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +0 -371
- package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +0 -573
- package/src/lib/tmux-lite/crypto/access-control.test.ts +0 -512
- package/src/lib/tmux-lite/crypto/access-control.ts +0 -320
- package/src/lib/tmux-lite/crypto/frames.test.ts +0 -262
- package/src/lib/tmux-lite/crypto/frames.ts +0 -141
- package/src/lib/tmux-lite/crypto/handshake.ts +0 -894
- package/src/lib/tmux-lite/crypto/identity.test.ts +0 -220
- package/src/lib/tmux-lite/crypto/identity.ts +0 -286
- package/src/lib/tmux-lite/crypto/index.ts +0 -51
- package/src/lib/tmux-lite/crypto/invites.test.ts +0 -381
- package/src/lib/tmux-lite/crypto/invites.ts +0 -215
- package/src/lib/tmux-lite/crypto/keyexchange.ts +0 -435
- package/src/lib/tmux-lite/crypto/keys.test.ts +0 -58
- package/src/lib/tmux-lite/crypto/keys.ts +0 -47
- package/src/lib/tmux-lite/crypto/secretbox.test.ts +0 -169
- package/src/lib/tmux-lite/crypto/secretbox.ts +0 -124
- package/src/lib/tmux-lite/handshake-handler.ts +0 -451
- package/src/lib/tmux-lite/process-run.integration.test.ts +0 -266
- package/src/lib/tmux-lite/protocol.test.ts +0 -307
- package/src/lib/tmux-lite/protocol.ts +0 -291
- package/src/lib/tmux-lite/relay-client.ts +0 -506
- package/src/lib/tmux-lite/server-lifecycle.test.ts +0 -212
- package/src/lib/tmux-lite/server.ts +0 -1412
- package/src/lib/tmux-lite/shell-integration.sh +0 -37
- package/src/lib/tmux-lite/terminal-queries.test.ts +0 -54
- package/src/lib/tmux-lite/terminal-queries.ts +0 -49
- package/src/notifications/__tests__/useNotifications.test.ts +0 -739
- package/src/notifications/index.ts +0 -32
- package/src/notifications/policy.test.ts +0 -424
- package/src/notifications/policy.ts +0 -139
- package/src/notifications/types.ts +0 -82
- package/src/notifications/useNotifications.ts +0 -350
- package/src/pages/ReviewPage.web.tsx +0 -511
- package/src/preferences/index.ts +0 -1
- package/src/preferences/types.ts +0 -9
- package/src/relay/__tests__/e2e-flow.test.ts +0 -1284
- package/src/relay/__tests__/helpers/auth.ts +0 -354
- package/src/relay/__tests__/helpers/ports.ts +0 -51
- package/src/relay/__tests__/protocol-validation.test.ts +0 -265
- package/src/relay/authorization.ts +0 -303
- package/src/relay/embedded-assets.generated.d.ts +0 -15
- package/src/relay/identity.ts +0 -352
- package/src/relay/index.ts +0 -57
- package/src/relay/pipes.test.ts +0 -427
- package/src/relay/pipes.ts +0 -195
- package/src/relay/protocol.ts +0 -804
- package/src/relay/registries.test.ts +0 -437
- package/src/relay/registries.ts +0 -593
- package/src/relay/server.test.ts +0 -1323
- package/src/relay/server.ts +0 -1128
- package/src/relay/signing.ts +0 -238
- package/src/relay/types.ts +0 -69
- package/src/relay-client/__tests__/machine-directory-client.test.ts +0 -152
- package/src/relay-client/__tests__/useMachineDirectory.test.ts +0 -172
- package/src/relay-client/adapters/browser.ts +0 -27
- package/src/relay-client/adapters/node.ts +0 -29
- package/src/relay-client/index.ts +0 -33
- package/src/relay-client/machine-directory-client.ts +0 -244
- package/src/relay-client/useMachineDirectory.ts +0 -175
- package/src/serve/client-session-manager.ts +0 -635
- package/src/serve/daemon.ts +0 -497
- package/src/serve/pty-session.ts +0 -236
- package/src/serve/types.ts +0 -174
- package/src/session/__tests__/backend-manager.test.ts +0 -101
- package/src/session/__tests__/local-session-backend.test.ts +0 -1129
- package/src/session/__tests__/reducer.test.ts +0 -80
- package/src/session/__tests__/remote-session-backend.test.ts +0 -995
- package/src/session/__tests__/session-name.test.ts +0 -35
- package/src/session/__tests__/useBundleRefreshAttachFlow.test.ts +0 -431
- package/src/session/__tests__/useRemoteSessionClient.test.ts +0 -424
- package/src/session/__tests__/workspace-shell-hooks.integration.test.ts +0 -268
- package/src/session/__tests__/workspace-shell-hooks.test.ts +0 -24
- package/src/session/adapters/browser-remote.ts +0 -101
- package/src/session/adapters/node-remote.ts +0 -135
- package/src/session/backend-key.ts +0 -5
- package/src/session/backend-manager.ts +0 -80
- package/src/session/backend.ts +0 -93
- package/src/session/backends/local-session-backend.ts +0 -1119
- package/src/session/backends/remote-session-backend.ts +0 -1378
- package/src/session/crypto/__tests__/web-terminal.test.ts +0 -1158
- package/src/session/crypto/frames.web.ts +0 -205
- package/src/session/crypto/handshake.web.ts +0 -396
- package/src/session/crypto/identity.web.ts +0 -133
- package/src/session/crypto/keyexchange.web.ts +0 -246
- package/src/session/crypto/relay-signing.web.ts +0 -53
- package/src/session/events.ts +0 -38
- package/src/session/index.ts +0 -116
- package/src/session/reducer.ts +0 -274
- package/src/session/selectors.ts +0 -28
- package/src/session/session-name.ts +0 -50
- package/src/session/types.ts +0 -101
- package/src/session/useBundleRefreshAttachFlow.ts +0 -608
- package/src/session/useRemoteSessionClient.ts +0 -424
- package/src/session/useSessionEngine.ts +0 -432
- package/src/session/workspace-shell-hooks.ts +0 -35
- package/src/tui/__tests__/input-text.test.ts +0 -24
- package/src/tui/__tests__/local-terminal-sync.test.ts +0 -82
- package/src/tui/__tests__/session-terminal-page-navigation.test.ts +0 -94
- package/src/tui/app.tsx +0 -2
- package/src/tui/index.ts +0 -18
- package/src/tui/input-text.ts +0 -38
- package/src/tui/local-terminal-sync.ts +0 -41
- package/src/types/bundle-refresh.ts +0 -42
- package/src/types/bundle.ts +0 -130
- package/src/types/config.ts +0 -287
- package/src/types/errors.ts +0 -292
- package/src/types/events.ts +0 -91
- package/src/types/identity.ts +0 -284
- package/src/types/processes.ts +0 -45
- package/src/types/review.ts +0 -349
- package/src/types/script-phase.ts +0 -3
- package/src/types/workspace-fuzzy.ts +0 -49
- package/src/types/workspace.ts +0 -151
- package/src/utils/__tests__/onboarding.test.ts +0 -358
- package/src/utils/__tests__/run-scripts.test.ts +0 -535
- package/src/utils/__tests__/run-workspace-scripts.test.ts +0 -406
- package/src/utils/__tests__/workspace-setup.integration.test.ts +0 -633
- package/src/utils/__tests__/workspace-state.test.ts +0 -78
- package/src/utils/bun-socket-writer.ts +0 -80
- package/src/utils/clipboard.ts +0 -53
- package/src/utils/deps.test.ts +0 -31
- package/src/utils/deps.ts +0 -145
- package/src/utils/device.web.ts +0 -163
- package/src/utils/fuzzy-match.ts +0 -125
- package/src/utils/hostnames.ts +0 -43
- package/src/utils/hunk-header.ts +0 -17
- package/src/utils/id.ts +0 -9
- package/src/utils/logger.ts +0 -127
- package/src/utils/markdown.ts +0 -254
- package/src/utils/normalize-env-key.ts +0 -13
- package/src/utils/onboarding.ts +0 -279
- package/src/utils/prompts.ts +0 -176
- package/src/utils/run-commands.ts +0 -112
- package/src/utils/run-scripts.ts +0 -337
- package/src/utils/run-workspace-scripts.ts +0 -355
- package/src/utils/sanitize.test.ts +0 -149
- package/src/utils/sanitize.ts +0 -162
- package/src/utils/secrets.ts +0 -836
- package/src/utils/shell-escape.ts +0 -40
- package/src/utils/utf8.ts +0 -79
- package/src/utils/workspace-id.ts +0 -55
- package/src/utils/workspace-state.ts +0 -427
- package/src/version.generated.d.ts +0 -2
- package/todo-security.md +0 -92
- package/tsconfig.json +0 -29
- package/web/README.md +0 -73
- package/web/bun.lock +0 -675
- package/web/eslint.config.js +0 -23
- package/web/index.css +0 -249
- package/web/index.html +0 -16
- package/web/main.tsx +0 -10
- package/web/package.json +0 -39
- package/web/public/vite.svg +0 -1
- package/web/tsconfig.app.json +0 -35
- package/web/tsconfig.json +0 -7
- package/web/tsconfig.node.json +0 -26
- package/web/vite.config.ts +0 -39
- package/worker/bun.lock +0 -237
- package/worker/package.json +0 -22
- package/worker/schema.sql +0 -96
- package/worker/src/handlers/auth.ts +0 -451
- package/worker/src/handlers/subdomains.ts +0 -376
- package/worker/src/handlers/user.ts +0 -98
- package/worker/src/index.ts +0 -70
- package/worker/src/middleware/auth.ts +0 -152
- package/worker/src/services/cloudflare.ts +0 -609
- package/worker/src/types.ts +0 -96
- package/worker/tsconfig.json +0 -15
- package/worker/wrangler.toml +0 -26
|
@@ -1,1064 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bundle refresh detection and execution.
|
|
3
|
-
*
|
|
4
|
-
* Tracks bundle state per workspace scope, keeps project-level merged keys,
|
|
5
|
-
* and persists confirm-step history by step fingerprint.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createHash } from 'crypto';
|
|
9
|
-
import { existsSync, readFileSync } from 'fs';
|
|
10
|
-
import { basename, join, resolve, sep } from 'path';
|
|
11
|
-
import { logger } from '../utils/logger.js';
|
|
12
|
-
import {
|
|
13
|
-
getProjectBaseDir,
|
|
14
|
-
getProjectWorkspacesDir,
|
|
15
|
-
readProjectConfig,
|
|
16
|
-
updateProjectConfig,
|
|
17
|
-
} from './config.js';
|
|
18
|
-
import { runOnboarding, KEEP_EXISTING_SECRET, type OnboardingOptions } from '../utils/onboarding.js';
|
|
19
|
-
import { getProjectSecret, getProjectSecrets, setProjectSecret } from '../utils/secrets.js';
|
|
20
|
-
import { checkCommandExists } from '../utils/deps.js';
|
|
21
|
-
import { SpacesError } from '../types/errors.js';
|
|
22
|
-
import type {
|
|
23
|
-
ConfirmStep,
|
|
24
|
-
ConfirmStepResult,
|
|
25
|
-
OnboardingStep,
|
|
26
|
-
SecretStep,
|
|
27
|
-
SpacesBundle,
|
|
28
|
-
} from '../types/bundle.js';
|
|
29
|
-
import type {
|
|
30
|
-
BundleConfirmHistoryEntry,
|
|
31
|
-
ProjectConfig,
|
|
32
|
-
WorkspaceBundleState,
|
|
33
|
-
} from '../types/config.js';
|
|
34
|
-
import type {
|
|
35
|
-
BundleRefreshPlan,
|
|
36
|
-
BundleRefreshStep,
|
|
37
|
-
BundleRefreshSubmission,
|
|
38
|
-
} from '../types/bundle-refresh.js';
|
|
39
|
-
|
|
40
|
-
const BUNDLE_FILENAME = 'bundle.json';
|
|
41
|
-
const BASE_SCOPE = '__base__';
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Result of bundle change detection.
|
|
45
|
-
*/
|
|
46
|
-
export interface BundleChangeResult {
|
|
47
|
-
/** Whether a bundle exists */
|
|
48
|
-
hasBundle: boolean;
|
|
49
|
-
/** Whether the bundle has changed since last processed for this scope */
|
|
50
|
-
hasChanged: boolean;
|
|
51
|
-
/** The current bundle (if exists) */
|
|
52
|
-
currentBundle?: SpacesBundle;
|
|
53
|
-
/** Hash of the current bundle content */
|
|
54
|
-
currentHash?: string;
|
|
55
|
-
/** Hash previously recorded for this scope (or base fallback) */
|
|
56
|
-
previousHash?: string;
|
|
57
|
-
/** Path to the bundle directory */
|
|
58
|
-
bundlePath?: string;
|
|
59
|
-
/** Parse error if bundle.json is invalid */
|
|
60
|
-
parseError?: string;
|
|
61
|
-
/** Scope key used for this detection (workspace name or __base__) */
|
|
62
|
-
scope?: string;
|
|
63
|
-
/** Where the active bundle.json was resolved from */
|
|
64
|
-
bundleSource?: 'workspace' | 'base';
|
|
65
|
-
/** Where baseline state came from for comparison */
|
|
66
|
-
baselineSource?: 'scope' | 'base' | 'inferred' | 'none';
|
|
67
|
-
/** Key-level onboarding requirement diff between baseline and current bundle */
|
|
68
|
-
requirementsDiff?: BundleRequirementsDiff;
|
|
69
|
-
/** Human-readable summary lines explaining change status */
|
|
70
|
-
changeSummary?: string[];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Options for bundle refresh.
|
|
75
|
-
*/
|
|
76
|
-
export interface BundleRefreshOptions {
|
|
77
|
-
/** Force refresh even if no changes detected */
|
|
78
|
-
force?: boolean;
|
|
79
|
-
/** Run in non-interactive mode (skip prompting when changes are detected) */
|
|
80
|
-
nonInteractive?: boolean;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Result of bundle refresh.
|
|
85
|
-
*/
|
|
86
|
-
export interface BundleRefreshResult {
|
|
87
|
-
/** Whether refresh prompts were performed */
|
|
88
|
-
refreshed: boolean;
|
|
89
|
-
/** Whether refresh completed successfully */
|
|
90
|
-
completed: boolean;
|
|
91
|
-
/** Merged non-secret values stored in config */
|
|
92
|
-
newValues?: Record<string, string>;
|
|
93
|
-
/** Merged secret key list stored in config */
|
|
94
|
-
newSecretKeys?: string[];
|
|
95
|
-
/** Error message if failed */
|
|
96
|
-
error?: string;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
interface LoadBundleResult {
|
|
100
|
-
bundle: SpacesBundle | null;
|
|
101
|
-
error?: string;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
interface BundleRequirements {
|
|
105
|
-
requiredInputKeys: string[];
|
|
106
|
-
requiredSecretKeys: string[];
|
|
107
|
-
confirmFingerprints: string[];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
interface BundleSyncResult {
|
|
111
|
-
hasBundle: boolean;
|
|
112
|
-
parseError?: string;
|
|
113
|
-
scope?: string;
|
|
114
|
-
bundle?: SpacesBundle;
|
|
115
|
-
bundleHash?: string;
|
|
116
|
-
bundlePath?: string;
|
|
117
|
-
bundleSource?: 'workspace' | 'base';
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
interface BundleRequirementsDiff {
|
|
121
|
-
inputAdded: string[];
|
|
122
|
-
inputRemoved: string[];
|
|
123
|
-
secretsAdded: string[];
|
|
124
|
-
secretsRemoved: string[];
|
|
125
|
-
confirmsAdded: string[];
|
|
126
|
-
confirmsRemoved: string[];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Compute a hash of the full bundle content for change detection.
|
|
131
|
-
*/
|
|
132
|
-
export function hashBundle(bundle: SpacesBundle): string {
|
|
133
|
-
const stable = deepSortForHash(bundle);
|
|
134
|
-
const content = JSON.stringify(stable);
|
|
135
|
-
return createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Compute a stable fingerprint for a confirm step.
|
|
140
|
-
*
|
|
141
|
-
* Re-check behavior is keyed to this fingerprint, not the whole bundle hash.
|
|
142
|
-
*/
|
|
143
|
-
export function getConfirmStepFingerprint(step: ConfirmStep): string {
|
|
144
|
-
const stable = deepSortForHash({
|
|
145
|
-
type: step.type,
|
|
146
|
-
id: step.id,
|
|
147
|
-
title: step.title,
|
|
148
|
-
description: step.description,
|
|
149
|
-
required: step.required !== false,
|
|
150
|
-
checkCommand: step.checkCommand ?? null,
|
|
151
|
-
installUrl: step.installUrl ?? null,
|
|
152
|
-
confirmPrompt: step.confirmPrompt ?? null,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
return createHash('sha256').update(JSON.stringify(stable)).digest('hex').slice(0, 16);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Get the bundle scope key for this path.
|
|
160
|
-
*
|
|
161
|
-
* - workspace path -> workspace name
|
|
162
|
-
* - base path or unknown path -> __base__
|
|
163
|
-
*/
|
|
164
|
-
export function getBundleScopeKey(projectName: string, workspacePath?: string): string {
|
|
165
|
-
if (!workspacePath) {
|
|
166
|
-
return BASE_SCOPE;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const resolvedWorkspacePath = resolve(workspacePath);
|
|
170
|
-
const resolvedWorkspacesDir = resolve(getProjectWorkspacesDir(projectName));
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
resolvedWorkspacePath === resolvedWorkspacesDir ||
|
|
174
|
-
!resolvedWorkspacePath.startsWith(`${resolvedWorkspacesDir}${sep}`)
|
|
175
|
-
) {
|
|
176
|
-
return BASE_SCOPE;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const relative = resolvedWorkspacePath.slice(resolvedWorkspacesDir.length + 1);
|
|
180
|
-
const workspaceName = relative.split(sep)[0];
|
|
181
|
-
return workspaceName || BASE_SCOPE;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function deepSortForHash(value: unknown): unknown {
|
|
185
|
-
if (Array.isArray(value)) {
|
|
186
|
-
return value.map((item) => deepSortForHash(item));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (value && typeof value === 'object') {
|
|
190
|
-
const record = value as Record<string, unknown>;
|
|
191
|
-
const sorted: Record<string, unknown> = {};
|
|
192
|
-
for (const key of Object.keys(record).sort()) {
|
|
193
|
-
sorted[key] = deepSortForHash(record[key]);
|
|
194
|
-
}
|
|
195
|
-
return sorted;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return value;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function uniqueSorted(values: string[]): string[] {
|
|
202
|
-
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function diffKeys(previous: string[], current: string[]): { added: string[]; removed: string[] } {
|
|
206
|
-
const previousSet = new Set(previous);
|
|
207
|
-
const currentSet = new Set(current);
|
|
208
|
-
|
|
209
|
-
const added = current.filter((key) => !previousSet.has(key));
|
|
210
|
-
const removed = previous.filter((key) => !currentSet.has(key));
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
added: uniqueSorted(added),
|
|
214
|
-
removed: uniqueSorted(removed),
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function buildRequirementsDiff(
|
|
219
|
-
previous: BundleRequirements,
|
|
220
|
-
current: BundleRequirements
|
|
221
|
-
): BundleRequirementsDiff {
|
|
222
|
-
const input = diffKeys(previous.requiredInputKeys, current.requiredInputKeys);
|
|
223
|
-
const secrets = diffKeys(previous.requiredSecretKeys, current.requiredSecretKeys);
|
|
224
|
-
const confirms = diffKeys(previous.confirmFingerprints, current.confirmFingerprints);
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
inputAdded: input.added,
|
|
228
|
-
inputRemoved: input.removed,
|
|
229
|
-
secretsAdded: secrets.added,
|
|
230
|
-
secretsRemoved: secrets.removed,
|
|
231
|
-
confirmsAdded: confirms.added,
|
|
232
|
-
confirmsRemoved: confirms.removed,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function sourceLabel(source?: 'workspace' | 'base'): string {
|
|
237
|
-
if (source === 'workspace') {
|
|
238
|
-
return 'workspace bundle (.gitspace/bundle.json in workspace)';
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return 'project base bundle (.gitspace/bundle.json in base repo)';
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function baselineLabel(source?: 'scope' | 'base' | 'inferred' | 'none'): string {
|
|
245
|
-
switch (source) {
|
|
246
|
-
case 'scope':
|
|
247
|
-
return 'workspace scope';
|
|
248
|
-
case 'base':
|
|
249
|
-
return 'base scope (__base__)';
|
|
250
|
-
case 'inferred':
|
|
251
|
-
return 'inferred matching workspace scope';
|
|
252
|
-
case 'none':
|
|
253
|
-
default:
|
|
254
|
-
return 'none';
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function buildBundleChangeSummary(changes: BundleChangeResult): string[] {
|
|
259
|
-
const lines: string[] = [];
|
|
260
|
-
|
|
261
|
-
if (!changes.hasBundle) {
|
|
262
|
-
lines.push('No bundle detected for this workspace/project scope.');
|
|
263
|
-
return lines;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
lines.push(`Bundle source: ${sourceLabel(changes.bundleSource)}.`);
|
|
267
|
-
lines.push(`Baseline source: ${baselineLabel(changes.baselineSource)}.`);
|
|
268
|
-
|
|
269
|
-
if (changes.hasChanged) {
|
|
270
|
-
lines.push(
|
|
271
|
-
`Bundle hash changed (${changes.previousHash ?? 'none'} -> ${changes.currentHash ?? 'unknown'}).`
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
if (changes.baselineSource === 'none') {
|
|
275
|
-
lines.push(
|
|
276
|
-
`No previously recorded bundle state for scope "${changes.scope ?? BASE_SCOPE}".`
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const diff = changes.requirementsDiff;
|
|
281
|
-
if (diff) {
|
|
282
|
-
if (diff.inputAdded.length > 0) {
|
|
283
|
-
lines.push(`Required input keys added: ${diff.inputAdded.join(', ')}.`);
|
|
284
|
-
}
|
|
285
|
-
if (diff.inputRemoved.length > 0) {
|
|
286
|
-
lines.push(`Required input keys removed: ${diff.inputRemoved.join(', ')}.`);
|
|
287
|
-
}
|
|
288
|
-
if (diff.secretsAdded.length > 0) {
|
|
289
|
-
lines.push(`Required secret keys added: ${diff.secretsAdded.join(', ')}.`);
|
|
290
|
-
}
|
|
291
|
-
if (diff.secretsRemoved.length > 0) {
|
|
292
|
-
lines.push(`Required secret keys removed: ${diff.secretsRemoved.join(', ')}.`);
|
|
293
|
-
}
|
|
294
|
-
if (diff.confirmsAdded.length > 0 || diff.confirmsRemoved.length > 0) {
|
|
295
|
-
lines.push(
|
|
296
|
-
`Confirm checks changed (+${diff.confirmsAdded.length} / -${diff.confirmsRemoved.length}).`
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
} else {
|
|
301
|
-
lines.push('Bundle hash matches the recorded baseline for this scope.');
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return lines;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Format bundle change details for user-facing messages.
|
|
309
|
-
*/
|
|
310
|
-
export function formatBundleChangeDetails(changes: BundleChangeResult): string {
|
|
311
|
-
const summary = changes.changeSummary ?? buildBundleChangeSummary(changes);
|
|
312
|
-
return summary.join('\n');
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function loadBundle(bundleDir: string): LoadBundleResult {
|
|
316
|
-
const bundlePath = join(bundleDir, BUNDLE_FILENAME);
|
|
317
|
-
if (!existsSync(bundlePath)) {
|
|
318
|
-
return { bundle: null };
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
try {
|
|
322
|
-
const content = readFileSync(bundlePath, 'utf-8');
|
|
323
|
-
const bundle = JSON.parse(content) as SpacesBundle;
|
|
324
|
-
return { bundle };
|
|
325
|
-
} catch (error) {
|
|
326
|
-
const errorMessage = error instanceof SyntaxError
|
|
327
|
-
? `Invalid JSON in bundle.json: ${error.message}`
|
|
328
|
-
: `Failed to load bundle: ${error}`;
|
|
329
|
-
logger.warning(errorMessage);
|
|
330
|
-
return { bundle: null, error: errorMessage };
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function resolveBundleLocation(
|
|
335
|
-
projectName: string,
|
|
336
|
-
workspacePath?: string
|
|
337
|
-
): { bundleDir: string; bundleSource: 'workspace' | 'base' } | null {
|
|
338
|
-
if (workspacePath) {
|
|
339
|
-
const workspaceBundleDir = join(workspacePath, '.gitspace');
|
|
340
|
-
if (existsSync(join(workspaceBundleDir, BUNDLE_FILENAME))) {
|
|
341
|
-
return {
|
|
342
|
-
bundleDir: workspaceBundleDir,
|
|
343
|
-
bundleSource: 'workspace',
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const baseDir = getProjectBaseDir(projectName);
|
|
349
|
-
const baseBundleDir = join(baseDir, '.gitspace');
|
|
350
|
-
if (existsSync(join(baseBundleDir, BUNDLE_FILENAME))) {
|
|
351
|
-
return {
|
|
352
|
-
bundleDir: baseBundleDir,
|
|
353
|
-
bundleSource: 'base',
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return null;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function getBundleRequirements(bundle: SpacesBundle): BundleRequirements {
|
|
361
|
-
const steps = bundle.onboarding || [];
|
|
362
|
-
const inputKeys: string[] = [];
|
|
363
|
-
const secretKeys: string[] = [];
|
|
364
|
-
const confirmFingerprints: string[] = [];
|
|
365
|
-
|
|
366
|
-
for (const step of steps) {
|
|
367
|
-
if (step.type === 'input') {
|
|
368
|
-
inputKeys.push(step.configKey);
|
|
369
|
-
continue;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (step.type === 'secret') {
|
|
373
|
-
secretKeys.push(step.configKey);
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
if (step.type === 'confirm') {
|
|
378
|
-
confirmFingerprints.push(getConfirmStepFingerprint(step));
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
return {
|
|
383
|
-
requiredInputKeys: uniqueSorted(inputKeys),
|
|
384
|
-
requiredSecretKeys: uniqueSorted(secretKeys),
|
|
385
|
-
confirmFingerprints: uniqueSorted(confirmFingerprints),
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function applyWorkspaceState(
|
|
390
|
-
config: ProjectConfig,
|
|
391
|
-
scope: string,
|
|
392
|
-
bundleHash: string,
|
|
393
|
-
bundle: SpacesBundle
|
|
394
|
-
): {
|
|
395
|
-
bundleWorkspaceState: Record<string, WorkspaceBundleState>;
|
|
396
|
-
mergedSecretKeys: string[];
|
|
397
|
-
requirements: BundleRequirements;
|
|
398
|
-
} {
|
|
399
|
-
const existingState = config.bundleWorkspaceState || {};
|
|
400
|
-
const requirements = getBundleRequirements(bundle);
|
|
401
|
-
|
|
402
|
-
const nextState: Record<string, WorkspaceBundleState> = {
|
|
403
|
-
...existingState,
|
|
404
|
-
[scope]: {
|
|
405
|
-
scope,
|
|
406
|
-
bundleHash,
|
|
407
|
-
requiredInputKeys: requirements.requiredInputKeys,
|
|
408
|
-
requiredSecretKeys: requirements.requiredSecretKeys,
|
|
409
|
-
confirmFingerprints: requirements.confirmFingerprints,
|
|
410
|
-
updatedAt: new Date().toISOString(),
|
|
411
|
-
},
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
const requiredSecretUnion = Object.values(nextState)
|
|
415
|
-
.flatMap((entry) => entry.requiredSecretKeys);
|
|
416
|
-
const mergedSecretKeys = uniqueSorted([
|
|
417
|
-
...(config.bundleSecretKeys || []),
|
|
418
|
-
...requiredSecretUnion,
|
|
419
|
-
]);
|
|
420
|
-
|
|
421
|
-
return {
|
|
422
|
-
bundleWorkspaceState: nextState,
|
|
423
|
-
mergedSecretKeys,
|
|
424
|
-
requirements,
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Sync bundle metadata for a workspace scope without running onboarding prompts.
|
|
430
|
-
*
|
|
431
|
-
* This keeps per-workspace requirements and merged project-level key lists up to date.
|
|
432
|
-
*/
|
|
433
|
-
export function syncBundleWorkspaceState(projectName: string, workspacePath?: string): BundleSyncResult {
|
|
434
|
-
const bundleLocation = resolveBundleLocation(projectName, workspacePath);
|
|
435
|
-
if (!bundleLocation) {
|
|
436
|
-
return { hasBundle: false };
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const { bundleDir, bundleSource } = bundleLocation;
|
|
440
|
-
|
|
441
|
-
const { bundle, error } = loadBundle(bundleDir);
|
|
442
|
-
if (!bundle) {
|
|
443
|
-
return {
|
|
444
|
-
hasBundle: false,
|
|
445
|
-
parseError: error,
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const scope = getBundleScopeKey(projectName, workspacePath);
|
|
450
|
-
const bundleHash = hashBundle(bundle);
|
|
451
|
-
const config = readProjectConfig(projectName);
|
|
452
|
-
let nextState = config.bundleWorkspaceState || {};
|
|
453
|
-
let mergedSecretKeys = config.bundleSecretKeys || [];
|
|
454
|
-
|
|
455
|
-
if (bundleSource === 'base' || scope === BASE_SCOPE) {
|
|
456
|
-
const baseApplied = applyWorkspaceState(config, BASE_SCOPE, bundleHash, bundle);
|
|
457
|
-
nextState = baseApplied.bundleWorkspaceState;
|
|
458
|
-
mergedSecretKeys = baseApplied.mergedSecretKeys;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (scope !== BASE_SCOPE) {
|
|
462
|
-
const scopedApplied = applyWorkspaceState(
|
|
463
|
-
{
|
|
464
|
-
...config,
|
|
465
|
-
bundleWorkspaceState: nextState,
|
|
466
|
-
bundleSecretKeys: mergedSecretKeys,
|
|
467
|
-
},
|
|
468
|
-
scope,
|
|
469
|
-
bundleHash,
|
|
470
|
-
bundle
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
nextState = scopedApplied.bundleWorkspaceState;
|
|
474
|
-
mergedSecretKeys = scopedApplied.mergedSecretKeys;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
updateProjectConfig(projectName, {
|
|
478
|
-
bundleWorkspaceState: nextState,
|
|
479
|
-
bundleSecretKeys: mergedSecretKeys.length > 0 ? mergedSecretKeys : undefined,
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
return {
|
|
483
|
-
hasBundle: true,
|
|
484
|
-
scope,
|
|
485
|
-
bundle,
|
|
486
|
-
bundleHash,
|
|
487
|
-
bundlePath: bundleDir,
|
|
488
|
-
bundleSource,
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Detect if bundle has changed for the current workspace scope.
|
|
494
|
-
*/
|
|
495
|
-
export function detectBundleChanges(projectName: string, workspacePath?: string): BundleChangeResult {
|
|
496
|
-
const result: BundleChangeResult = {
|
|
497
|
-
hasBundle: false,
|
|
498
|
-
hasChanged: false,
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
const bundleLocation = resolveBundleLocation(projectName, workspacePath);
|
|
502
|
-
if (!bundleLocation) {
|
|
503
|
-
return result;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
const { bundleDir, bundleSource } = bundleLocation;
|
|
507
|
-
|
|
508
|
-
const { bundle, error } = loadBundle(bundleDir);
|
|
509
|
-
if (!bundle) {
|
|
510
|
-
if (error) {
|
|
511
|
-
result.parseError = error;
|
|
512
|
-
}
|
|
513
|
-
return result;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
const scope = getBundleScopeKey(projectName, workspacePath);
|
|
517
|
-
const currentHash = hashBundle(bundle);
|
|
518
|
-
const currentRequirements = getBundleRequirements(bundle);
|
|
519
|
-
const config = readProjectConfig(projectName);
|
|
520
|
-
const state = config.bundleWorkspaceState || {};
|
|
521
|
-
|
|
522
|
-
const scopeState = state[scope];
|
|
523
|
-
const baseState = state[BASE_SCOPE];
|
|
524
|
-
const inferredState = Object.values(state).find((entry) => entry.bundleHash === currentHash);
|
|
525
|
-
|
|
526
|
-
const baselineState = scopeState || baseState || inferredState;
|
|
527
|
-
const baselineSource: 'scope' | 'base' | 'inferred' | 'none' = scopeState
|
|
528
|
-
? 'scope'
|
|
529
|
-
: baseState
|
|
530
|
-
? 'base'
|
|
531
|
-
: inferredState
|
|
532
|
-
? 'inferred'
|
|
533
|
-
: 'none';
|
|
534
|
-
|
|
535
|
-
const previousHash = baselineState?.bundleHash;
|
|
536
|
-
const previousRequirements: BundleRequirements = {
|
|
537
|
-
requiredInputKeys: baselineState?.requiredInputKeys || [],
|
|
538
|
-
requiredSecretKeys: baselineState?.requiredSecretKeys || [],
|
|
539
|
-
confirmFingerprints: baselineState?.confirmFingerprints || [],
|
|
540
|
-
};
|
|
541
|
-
const requirementsDiff = buildRequirementsDiff(previousRequirements, currentRequirements);
|
|
542
|
-
|
|
543
|
-
result.hasBundle = true;
|
|
544
|
-
result.currentBundle = bundle;
|
|
545
|
-
result.currentHash = currentHash;
|
|
546
|
-
result.previousHash = previousHash;
|
|
547
|
-
result.bundlePath = bundleDir;
|
|
548
|
-
result.bundleSource = bundleSource;
|
|
549
|
-
result.scope = scope;
|
|
550
|
-
result.baselineSource = baselineSource;
|
|
551
|
-
result.requirementsDiff = requirementsDiff;
|
|
552
|
-
result.hasChanged = previousHash !== currentHash;
|
|
553
|
-
result.changeSummary = buildBundleChangeSummary(result);
|
|
554
|
-
|
|
555
|
-
return result;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function resolveWorkspaceNameFromPath(workspacePath: string): string {
|
|
559
|
-
return basename(resolve(workspacePath));
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function shouldIncludeInputStep(
|
|
563
|
-
key: string,
|
|
564
|
-
previousValues: Record<string, string>,
|
|
565
|
-
diff?: BundleRequirementsDiff,
|
|
566
|
-
baselineSource?: 'scope' | 'base' | 'inferred' | 'none'
|
|
567
|
-
): boolean {
|
|
568
|
-
if (baselineSource === 'none') {
|
|
569
|
-
return true;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (diff?.inputAdded.includes(key)) {
|
|
573
|
-
return true;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const existing = previousValues[key];
|
|
577
|
-
return !existing;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function shouldIncludeSecretStep(
|
|
581
|
-
key: string,
|
|
582
|
-
existingSecrets: Record<string, string>,
|
|
583
|
-
diff?: BundleRequirementsDiff,
|
|
584
|
-
baselineSource?: 'scope' | 'base' | 'inferred' | 'none'
|
|
585
|
-
): boolean {
|
|
586
|
-
if (baselineSource === 'none') {
|
|
587
|
-
return true;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
if (diff?.secretsAdded.includes(key)) {
|
|
591
|
-
return true;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
return !Object.prototype.hasOwnProperty.call(existingSecrets, key);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
function makeStepDescription(step: OnboardingStep): string {
|
|
598
|
-
if (step.required === false) {
|
|
599
|
-
return `${step.description}\n\n(Optional)`;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
return step.description;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
function buildPendingRequirementsSummary(steps: BundleRefreshStep[]): string[] {
|
|
606
|
-
const required = steps.filter((step) => step.required !== false);
|
|
607
|
-
if (required.length === 0) {
|
|
608
|
-
return [];
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
const inputKeys = required
|
|
612
|
-
.filter((step) => step.type === 'input' && step.configKey)
|
|
613
|
-
.map((step) => step.configKey as string);
|
|
614
|
-
const secretKeys = required
|
|
615
|
-
.filter((step) => step.type === 'secret' && step.configKey)
|
|
616
|
-
.map((step) => step.configKey as string);
|
|
617
|
-
const confirmTitles = required
|
|
618
|
-
.filter((step) => step.type === 'confirm')
|
|
619
|
-
.map((step) => step.title);
|
|
620
|
-
|
|
621
|
-
const lines: string[] = [];
|
|
622
|
-
if (inputKeys.length > 0) {
|
|
623
|
-
lines.push(`Missing required input values: ${inputKeys.join(', ')}.`);
|
|
624
|
-
}
|
|
625
|
-
if (secretKeys.length > 0) {
|
|
626
|
-
lines.push(`Missing required secrets: ${secretKeys.join(', ')}.`);
|
|
627
|
-
}
|
|
628
|
-
if (confirmTitles.length > 0) {
|
|
629
|
-
lines.push(`Pending required checks: ${confirmTitles.join(', ')}.`);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
return lines;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
export async function getBundleRefreshPlan(
|
|
636
|
-
projectName: string,
|
|
637
|
-
workspacePath: string,
|
|
638
|
-
workspaceId?: string
|
|
639
|
-
): Promise<BundleRefreshPlan> {
|
|
640
|
-
const workspaceName = resolveWorkspaceNameFromPath(workspacePath);
|
|
641
|
-
const resolvedWorkspaceId = workspaceId ?? `${projectName}:${workspaceName}`;
|
|
642
|
-
const changes = detectBundleChanges(projectName, workspacePath);
|
|
643
|
-
const details = formatBundleChangeDetails(changes);
|
|
644
|
-
|
|
645
|
-
const emptyPlan: BundleRefreshPlan = {
|
|
646
|
-
projectName,
|
|
647
|
-
workspaceId: resolvedWorkspaceId,
|
|
648
|
-
workspaceName,
|
|
649
|
-
workspacePath: resolve(workspacePath),
|
|
650
|
-
hasBundle: changes.hasBundle,
|
|
651
|
-
hasChanged: changes.hasChanged,
|
|
652
|
-
scope: changes.scope,
|
|
653
|
-
bundleSource: changes.bundleSource,
|
|
654
|
-
baselineSource: changes.baselineSource,
|
|
655
|
-
details,
|
|
656
|
-
currentHash: changes.currentHash,
|
|
657
|
-
previousHash: changes.previousHash,
|
|
658
|
-
steps: [],
|
|
659
|
-
autoConfirmResults: {},
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
if (!changes.hasBundle || !changes.currentBundle) {
|
|
663
|
-
return emptyPlan;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
const config = readProjectConfig(projectName);
|
|
667
|
-
const previousValues = config.bundleValues || {};
|
|
668
|
-
const confirmHistory = config.bundleConfirmHistory || {};
|
|
669
|
-
const secretStepKeys = (changes.currentBundle.onboarding || [])
|
|
670
|
-
.filter((step): step is SecretStep => step.type === 'secret')
|
|
671
|
-
.map((step) => step.configKey);
|
|
672
|
-
const existingSecrets = await getProjectSecrets(projectName, secretStepKeys);
|
|
673
|
-
const diff = changes.requirementsDiff;
|
|
674
|
-
const steps: BundleRefreshStep[] = [];
|
|
675
|
-
const autoConfirmResults: Record<string, ConfirmStepResult> = {};
|
|
676
|
-
|
|
677
|
-
for (const onboardingStep of changes.currentBundle.onboarding || []) {
|
|
678
|
-
if (onboardingStep.type === 'input') {
|
|
679
|
-
const include = shouldIncludeInputStep(
|
|
680
|
-
onboardingStep.configKey,
|
|
681
|
-
previousValues,
|
|
682
|
-
diff,
|
|
683
|
-
changes.baselineSource
|
|
684
|
-
);
|
|
685
|
-
if (!include) {
|
|
686
|
-
continue;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
steps.push({
|
|
690
|
-
id: onboardingStep.id,
|
|
691
|
-
type: onboardingStep.type,
|
|
692
|
-
title: onboardingStep.title,
|
|
693
|
-
description: makeStepDescription(onboardingStep),
|
|
694
|
-
required: onboardingStep.required,
|
|
695
|
-
configKey: onboardingStep.configKey,
|
|
696
|
-
defaultValue: previousValues[onboardingStep.configKey] ?? onboardingStep.defaultValue,
|
|
697
|
-
validationPattern: onboardingStep.validationPattern,
|
|
698
|
-
validationMessage: onboardingStep.validationMessage,
|
|
699
|
-
});
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
if (onboardingStep.type === 'secret') {
|
|
704
|
-
const include = shouldIncludeSecretStep(
|
|
705
|
-
onboardingStep.configKey,
|
|
706
|
-
existingSecrets,
|
|
707
|
-
diff,
|
|
708
|
-
changes.baselineSource
|
|
709
|
-
);
|
|
710
|
-
if (!include) {
|
|
711
|
-
continue;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
steps.push({
|
|
715
|
-
id: onboardingStep.id,
|
|
716
|
-
type: onboardingStep.type,
|
|
717
|
-
title: onboardingStep.title,
|
|
718
|
-
description: makeStepDescription(onboardingStep),
|
|
719
|
-
required: onboardingStep.required,
|
|
720
|
-
configKey: onboardingStep.configKey,
|
|
721
|
-
validationPattern: onboardingStep.validationPattern,
|
|
722
|
-
validationMessage: onboardingStep.validationMessage,
|
|
723
|
-
hasExistingSecret: Object.prototype.hasOwnProperty.call(existingSecrets, onboardingStep.configKey),
|
|
724
|
-
});
|
|
725
|
-
continue;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
if (onboardingStep.type === 'confirm') {
|
|
729
|
-
const fingerprint = getConfirmStepFingerprint(onboardingStep);
|
|
730
|
-
const history = confirmHistory[fingerprint];
|
|
731
|
-
if (history?.status === 'passed') {
|
|
732
|
-
autoConfirmResults[onboardingStep.id] = {
|
|
733
|
-
status: 'passed',
|
|
734
|
-
checkCommand: onboardingStep.checkCommand,
|
|
735
|
-
};
|
|
736
|
-
continue;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
if (onboardingStep.checkCommand) {
|
|
740
|
-
const exists = await checkCommandExists(onboardingStep.checkCommand);
|
|
741
|
-
if (exists) {
|
|
742
|
-
autoConfirmResults[onboardingStep.id] = {
|
|
743
|
-
status: 'passed',
|
|
744
|
-
checkCommand: onboardingStep.checkCommand,
|
|
745
|
-
};
|
|
746
|
-
continue;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
steps.push({
|
|
751
|
-
id: onboardingStep.id,
|
|
752
|
-
type: onboardingStep.type,
|
|
753
|
-
title: onboardingStep.title,
|
|
754
|
-
description: makeStepDescription(onboardingStep),
|
|
755
|
-
required: onboardingStep.required,
|
|
756
|
-
checkCommand: onboardingStep.checkCommand,
|
|
757
|
-
installUrl: onboardingStep.installUrl,
|
|
758
|
-
confirmPrompt: onboardingStep.confirmPrompt,
|
|
759
|
-
});
|
|
760
|
-
continue;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
if (onboardingStep.type === 'info' && changes.baselineSource === 'none') {
|
|
764
|
-
steps.push({
|
|
765
|
-
id: onboardingStep.id,
|
|
766
|
-
type: onboardingStep.type,
|
|
767
|
-
title: onboardingStep.title,
|
|
768
|
-
description: makeStepDescription(onboardingStep),
|
|
769
|
-
required: onboardingStep.required,
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
const pendingSummary = buildPendingRequirementsSummary(steps);
|
|
775
|
-
const detailsWithPending = pendingSummary.length > 0
|
|
776
|
-
? [details, ...pendingSummary].join('\n')
|
|
777
|
-
: details;
|
|
778
|
-
|
|
779
|
-
return {
|
|
780
|
-
...emptyPlan,
|
|
781
|
-
details: detailsWithPending,
|
|
782
|
-
steps,
|
|
783
|
-
autoConfirmResults,
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
export async function applyBundleRefreshSubmission(
|
|
788
|
-
projectName: string,
|
|
789
|
-
workspacePath: string,
|
|
790
|
-
submission: BundleRefreshSubmission
|
|
791
|
-
): Promise<void> {
|
|
792
|
-
const changes = detectBundleChanges(projectName, workspacePath);
|
|
793
|
-
if (!changes.hasBundle || !changes.currentBundle || !changes.currentHash) {
|
|
794
|
-
throw new SpacesError(changes.parseError || 'No bundle found for workspace', 'USER_ERROR', 1);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
const scope = changes.scope || BASE_SCOPE;
|
|
798
|
-
const bundle = changes.currentBundle;
|
|
799
|
-
const bundleHash = changes.currentHash;
|
|
800
|
-
const config = readProjectConfig(projectName);
|
|
801
|
-
const confirmHistory = config.bundleConfirmHistory || {};
|
|
802
|
-
const stateApplied = applyWorkspaceState(config, scope, bundleHash, bundle);
|
|
803
|
-
|
|
804
|
-
const newValues: Record<string, string> = {
|
|
805
|
-
...(config.bundleValues || {}),
|
|
806
|
-
...submission.inputValues,
|
|
807
|
-
};
|
|
808
|
-
|
|
809
|
-
const secretKeys = new Set<string>(stateApplied.mergedSecretKeys);
|
|
810
|
-
for (const key of Object.keys(submission.secretValues)) {
|
|
811
|
-
secretKeys.add(key);
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
for (const [key, value] of Object.entries(submission.secretValues)) {
|
|
815
|
-
if (!value || value === KEEP_EXISTING_SECRET) {
|
|
816
|
-
continue;
|
|
817
|
-
}
|
|
818
|
-
await setProjectSecret(projectName, key, value);
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
const historyNext: Record<string, BundleConfirmHistoryEntry> = {
|
|
822
|
-
...confirmHistory,
|
|
823
|
-
};
|
|
824
|
-
|
|
825
|
-
const allConfirmResults: Record<string, ConfirmStepResult> = {
|
|
826
|
-
...submission.confirmResults,
|
|
827
|
-
};
|
|
828
|
-
|
|
829
|
-
for (const step of bundle.onboarding || []) {
|
|
830
|
-
if (step.type !== 'confirm') {
|
|
831
|
-
continue;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
if (!allConfirmResults[step.id] && step.checkCommand) {
|
|
835
|
-
const exists = await checkCommandExists(step.checkCommand);
|
|
836
|
-
if (exists) {
|
|
837
|
-
allConfirmResults[step.id] = {
|
|
838
|
-
status: 'passed',
|
|
839
|
-
checkCommand: step.checkCommand,
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
const result = allConfirmResults[step.id];
|
|
845
|
-
if (!result) {
|
|
846
|
-
continue;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
const fingerprint = getConfirmStepFingerprint(step);
|
|
850
|
-
historyNext[fingerprint] = {
|
|
851
|
-
fingerprint,
|
|
852
|
-
stepId: step.id,
|
|
853
|
-
checkCommand: step.checkCommand,
|
|
854
|
-
status: result.status,
|
|
855
|
-
scope,
|
|
856
|
-
bundleHash,
|
|
857
|
-
checkedAt: new Date().toISOString(),
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
updateProjectConfig(projectName, {
|
|
862
|
-
bundleValues: Object.keys(newValues).length > 0 ? newValues : undefined,
|
|
863
|
-
bundleSecretKeys: secretKeys.size > 0 ? uniqueSorted([...secretKeys]) : undefined,
|
|
864
|
-
bundleWorkspaceState: stateApplied.bundleWorkspaceState,
|
|
865
|
-
bundleConfirmHistory: Object.keys(historyNext).length > 0 ? historyNext : undefined,
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
/**
|
|
870
|
-
* Refresh bundle onboarding.
|
|
871
|
-
*
|
|
872
|
-
* Re-runs onboarding for steps that require input, while skipping confirm/check
|
|
873
|
-
* steps whose fingerprint was already confirmed.
|
|
874
|
-
*/
|
|
875
|
-
export async function refreshBundle(
|
|
876
|
-
projectName: string,
|
|
877
|
-
workspacePath?: string,
|
|
878
|
-
options: BundleRefreshOptions = {}
|
|
879
|
-
): Promise<BundleRefreshResult> {
|
|
880
|
-
const result: BundleRefreshResult = {
|
|
881
|
-
refreshed: false,
|
|
882
|
-
completed: false,
|
|
883
|
-
};
|
|
884
|
-
|
|
885
|
-
const changes = detectBundleChanges(projectName, workspacePath);
|
|
886
|
-
if (!changes.hasBundle) {
|
|
887
|
-
result.error = changes.parseError || 'No bundle found';
|
|
888
|
-
return result;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
const scope = changes.scope || BASE_SCOPE;
|
|
892
|
-
const bundle = changes.currentBundle!;
|
|
893
|
-
const bundleHash = changes.currentHash!;
|
|
894
|
-
|
|
895
|
-
if (!changes.hasChanged && !options.force) {
|
|
896
|
-
// Keep per-scope state and merged key list fresh.
|
|
897
|
-
syncBundleWorkspaceState(projectName, workspacePath);
|
|
898
|
-
result.refreshed = false;
|
|
899
|
-
result.completed = true;
|
|
900
|
-
return result;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
if (options.nonInteractive) {
|
|
904
|
-
// Still sync metadata so required key lists merge across workspaces.
|
|
905
|
-
syncBundleWorkspaceState(projectName, workspacePath);
|
|
906
|
-
logger.info('Bundle has changed but running in non-interactive mode, skipping refresh');
|
|
907
|
-
result.refreshed = false;
|
|
908
|
-
result.completed = true;
|
|
909
|
-
return result;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
const config = readProjectConfig(projectName);
|
|
913
|
-
const previousValues = config.bundleValues || {};
|
|
914
|
-
const configuredSecretKeys = config.bundleSecretKeys || [];
|
|
915
|
-
const confirmHistory = config.bundleConfirmHistory || {};
|
|
916
|
-
const steps = bundle.onboarding || [];
|
|
917
|
-
|
|
918
|
-
// Only prompt for confirm/check steps when the fingerprint changed or is new.
|
|
919
|
-
const stepsToRun: OnboardingStep[] = [];
|
|
920
|
-
for (const step of steps) {
|
|
921
|
-
if (step.type !== 'confirm') {
|
|
922
|
-
stepsToRun.push(step);
|
|
923
|
-
continue;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
const fingerprint = getConfirmStepFingerprint(step);
|
|
927
|
-
const history = confirmHistory[fingerprint];
|
|
928
|
-
if (history?.status === 'passed') {
|
|
929
|
-
logger.dim(`Skipping confirm step "${step.title}" (already confirmed)`);
|
|
930
|
-
continue;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
stepsToRun.push(step);
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// Verify which secrets actually exist in keychain.
|
|
937
|
-
const existingSecretKeys: string[] = [];
|
|
938
|
-
for (const key of configuredSecretKeys) {
|
|
939
|
-
const exists = await getProjectSecret(projectName, key);
|
|
940
|
-
if (exists !== null) {
|
|
941
|
-
existingSecretKeys.push(key);
|
|
942
|
-
} else {
|
|
943
|
-
logger.debug(`Secret '${key}' not found in keychain, will prompt for new value`);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
const onboardingOptions: OnboardingOptions = {
|
|
948
|
-
previousValues,
|
|
949
|
-
previousSecretKeys: existingSecretKeys,
|
|
950
|
-
title: 'Bundle Refresh',
|
|
951
|
-
isRefresh: true,
|
|
952
|
-
};
|
|
953
|
-
|
|
954
|
-
const onboardingResult = await runOnboarding(stepsToRun, onboardingOptions);
|
|
955
|
-
if (!onboardingResult.completed) {
|
|
956
|
-
result.error = 'Onboarding cancelled';
|
|
957
|
-
return result;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
const stateApplied = applyWorkspaceState(config, scope, bundleHash, bundle);
|
|
961
|
-
|
|
962
|
-
// Merge input values.
|
|
963
|
-
const newValues: Record<string, string> = {
|
|
964
|
-
...previousValues,
|
|
965
|
-
...onboardingResult.inputValues,
|
|
966
|
-
};
|
|
967
|
-
|
|
968
|
-
// Merge secret keys from config + workspace requirements + newly entered secrets.
|
|
969
|
-
const newSecretKeysSet = new Set(stateApplied.mergedSecretKeys);
|
|
970
|
-
for (const key of Object.keys(onboardingResult.secretValues)) {
|
|
971
|
-
newSecretKeysSet.add(key);
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
// Persist new secret values to keychain.
|
|
975
|
-
for (const step of steps) {
|
|
976
|
-
if (step.type !== 'secret') {
|
|
977
|
-
continue;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
const secretStep = step as SecretStep;
|
|
981
|
-
const value = onboardingResult.secretValues[secretStep.configKey];
|
|
982
|
-
if (value && value !== KEEP_EXISTING_SECRET) {
|
|
983
|
-
await setProjectSecret(projectName, secretStep.configKey, value);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
const historyNext: Record<string, BundleConfirmHistoryEntry> = {
|
|
988
|
-
...confirmHistory,
|
|
989
|
-
};
|
|
990
|
-
|
|
991
|
-
for (const step of steps) {
|
|
992
|
-
if (step.type !== 'confirm') {
|
|
993
|
-
continue;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
const fingerprint = getConfirmStepFingerprint(step);
|
|
997
|
-
const stepResult = onboardingResult.confirmResults[step.id];
|
|
998
|
-
if (!stepResult) {
|
|
999
|
-
continue;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
historyNext[fingerprint] = {
|
|
1003
|
-
fingerprint,
|
|
1004
|
-
stepId: step.id,
|
|
1005
|
-
checkCommand: step.checkCommand,
|
|
1006
|
-
status: stepResult.status,
|
|
1007
|
-
scope,
|
|
1008
|
-
bundleHash,
|
|
1009
|
-
checkedAt: new Date().toISOString(),
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
updateProjectConfig(projectName, {
|
|
1014
|
-
bundleValues: Object.keys(newValues).length > 0 ? newValues : undefined,
|
|
1015
|
-
bundleSecretKeys: newSecretKeysSet.size > 0
|
|
1016
|
-
? uniqueSorted([...newSecretKeysSet])
|
|
1017
|
-
: undefined,
|
|
1018
|
-
bundleWorkspaceState: stateApplied.bundleWorkspaceState,
|
|
1019
|
-
bundleConfirmHistory: Object.keys(historyNext).length > 0 ? historyNext : undefined,
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
result.refreshed = true;
|
|
1023
|
-
result.completed = true;
|
|
1024
|
-
result.newValues = newValues;
|
|
1025
|
-
result.newSecretKeys = uniqueSorted([...newSecretKeysSet]);
|
|
1026
|
-
|
|
1027
|
-
return result;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
/**
|
|
1031
|
-
* Check if bundle refresh is needed and perform it.
|
|
1032
|
-
*
|
|
1033
|
-
* Returns true if refresh was performed or not needed,
|
|
1034
|
-
* false if user cancelled or an error occurred.
|
|
1035
|
-
*/
|
|
1036
|
-
export async function checkAndRefreshBundle(
|
|
1037
|
-
projectName: string,
|
|
1038
|
-
workspacePath: string
|
|
1039
|
-
): Promise<boolean> {
|
|
1040
|
-
const changes = detectBundleChanges(projectName, workspacePath);
|
|
1041
|
-
if (!changes.hasBundle) {
|
|
1042
|
-
if (changes.parseError) {
|
|
1043
|
-
logger.warning(`Bundle parse error: ${changes.parseError}`);
|
|
1044
|
-
return false;
|
|
1045
|
-
}
|
|
1046
|
-
return true;
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
if (!changes.hasChanged) {
|
|
1050
|
-
// Keep merged requirements fresh even when no prompts are needed.
|
|
1051
|
-
syncBundleWorkspaceState(projectName, workspacePath);
|
|
1052
|
-
return true;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
logger.info('Bundle configuration has changed for this workspace');
|
|
1056
|
-
|
|
1057
|
-
const result = await refreshBundle(projectName, workspacePath);
|
|
1058
|
-
if (result.error) {
|
|
1059
|
-
logger.warning(`Bundle refresh failed: ${result.error}`);
|
|
1060
|
-
return false;
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
return result.completed;
|
|
1064
|
-
}
|