@vantran-se/openchamber-web 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -24
- package/bin/cli.js +1 -3
- package/bin/cli.test.js +40 -16
- package/bin/lib/DOCUMENTATION.md +6 -5
- package/bin/lib/cli-args.js +6 -1
- package/bin/lib/cli-args.test.js +34 -0
- package/bin/lib/cli-lifecycle.js +0 -1
- package/bin/lib/cli-network.js +8 -2
- package/bin/lib/cli-process.js +1 -1
- package/bin/lib/cli-settings-accessors.test.js +21 -13
- package/bin/lib/cli-startup.js +17 -11
- package/bin/lib/cli-startup.test.js +92 -2
- package/bin/lib/commands-connect-url.js +8 -1
- package/bin/lib/commands-schedule.js +8 -2
- package/bin/lib/commands-serve.js +15 -21
- package/bin/lib/commands-update.js +12 -24
- package/bin/lib/commands-update.test.js +14 -46
- package/dist/assets/DiffView-Cv9MSXl1.js +4 -0
- package/dist/assets/FilePreviewCommentMenu-D4kLseHj.js +4 -0
- package/dist/assets/FilesView-BVoD8I2x.js +14 -0
- package/dist/assets/{GitView-DzuzhIPx.js → GitView-Bzb8p-ew.js} +4 -4
- package/dist/assets/GuestSurfacePane-Crdbvsxo.js +1 -0
- package/dist/assets/InProgressOperationBanner-CwMjNQgk.js +2 -0
- package/dist/assets/{JsonTreeView-B_EPRjzy.js → JsonTreeView-DRKHZfsn.js} +1 -1
- package/dist/assets/MarkdownImageGallery-DZtqT1Zn.js +1 -0
- package/dist/assets/MarkdownRendererImpl-CqG7HCNd.js +10 -0
- package/dist/assets/OnboardingScreen-DMIxwYK5.js +1 -0
- package/dist/assets/{PatchDiff-6yh1csza.js → PatchDiff-CUcCg1jk.js} +1 -1
- package/dist/assets/PierreDiffViewer-CnnR-dnD.js +228 -0
- package/dist/assets/PlanView-RCBadcY7.js +5 -0
- package/dist/assets/SettingsView-CjBjZFRO.js +30 -0
- package/dist/assets/SettingsWindow-DyM7QuhI.js +1 -0
- package/dist/assets/SubmoduleDiffSummary-BWIfgMED.js +1 -0
- package/dist/assets/ToolOutputDialog-Q0P3FuAi.js +15 -0
- package/dist/assets/ToolPartDiffPreview-p7qR9FpU.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-BTJVRkyo.js → VirtualizedFileDiff-CFpjL8Ns.js} +3 -3
- package/dist/assets/{Virtualizer-xCEYz34p.js → Virtualizer-C0IsclZn.js} +1 -1
- package/dist/assets/WalkthroughView-DI31zD02.js +4 -0
- package/dist/assets/appThemeRegistry-B9gkFq9i.js +1 -0
- package/dist/assets/{computeVirtualFileMetrics-CmPe29DM.js → computeVirtualFileMetrics-C2WM6JC2.js} +2 -2
- package/dist/assets/de-YKfk5MmC.js +14 -0
- package/dist/assets/es-_gwT0kr-.js +20 -0
- package/dist/assets/fileTreeStatus-D438yi8M.js +1 -0
- package/dist/assets/fr-DqzvXmIm.js +18 -0
- package/dist/assets/index-BLaHKdtx.js +1 -0
- package/dist/assets/index-BkMXRrVQ.css +1 -0
- package/dist/assets/index-DxNa7xYB.js +65 -0
- package/dist/assets/initializeSharedPreferences-Bck5EAWP.js +1 -0
- package/dist/assets/{isDiffPlainText-CQmDCnEv.js → isDiffPlainText-Dgbw8VKk.js} +1 -1
- package/dist/assets/{isFilePlainText-CYBUzW4I.js → isFilePlainText-_ZS5UYRj.js} +1 -1
- package/dist/assets/ja-B-GFYhQE.js +20 -0
- package/dist/assets/ko-mzksfqDC.js +20 -0
- package/dist/assets/main-C5Xx8dn_.js +2 -0
- package/dist/assets/main-CfBSQM1H.js +990 -0
- package/dist/assets/markdownCore-BmWwW1ll.js +7 -0
- package/dist/assets/miniChat-mnKwy4vQ.js +2 -0
- package/dist/assets/mobile-DeEQd0wZ.js +2 -0
- package/dist/assets/nl-CWQr6NSq.js +20 -0
- package/dist/assets/{parseDiffFromFile-Cy0PNxun.js → parseDiffFromFile-BUgn-T9p.js} +2 -2
- package/dist/assets/patchFileDiff-Dt8LhgBh.js +13 -0
- package/dist/assets/pl-UmRcDUX2.js +20 -0
- package/dist/assets/pt-BR-BSLybvVF.js +20 -0
- package/dist/assets/relocateRestoredSession-B5E9xb3T.js +1 -0
- package/dist/assets/renderElectronMiniChatApp-C0a9A5_J.js +2 -0
- package/dist/assets/renderMobileApp-BNncg3Pk.js +25 -0
- package/dist/assets/runtimeEndpointReset-CZ5aafpl.js +62 -0
- package/dist/assets/{selectionMarkdown-TkYk5h9l.js → selectionMarkdown-CUN3wUld.js} +9 -9
- package/dist/assets/{sessionOwnership-Bs1xbRfB.js → sessionOwnership-BSuxCEhb.js} +1 -1
- package/dist/assets/shikiHighlight-DHDx-6eM.js +1 -0
- package/dist/assets/space-apply-DM_2pRW7.js +1 -0
- package/dist/assets/space-close-Bpomt8z0.js +1 -0
- package/dist/assets/{surface-B6n7wJtt.js → surface-C-jQaGnL.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-CDlKffWz.js → textMateThemeFromAppTheme-DlsTphbY.js} +1 -1
- package/dist/assets/tr-gaTrX_HK.js +11 -0
- package/dist/assets/uk-CnJF6ykl.js +20 -0
- package/dist/assets/{useAppFontEffects-DCqCVGlT.js → useAppFontEffects-DGlvOqP_.js} +194 -233
- package/dist/assets/useInlineCommentController-kMN4Wg_D.js +2 -0
- package/dist/assets/{useRootScrollLock-BowwS8kq.js → useRootScrollLock-DMPpvpNP.js} +2 -2
- package/dist/assets/{vendor-base-ui-DX96QhTP.js → vendor-base-ui-Det7U81h.js} +1 -1
- package/dist/assets/{vendor-codemirror-autocomplete-DW1FQz9_.js → vendor-codemirror-autocomplete-BSHQHbKu.js} +1 -1
- package/dist/assets/{vendor-codemirror-commands-DpmhalwN.js → vendor-codemirror-commands-C4G_dPWe.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-angular-BqExbN1M.js → vendor-codemirror-lang-angular-B6LbBGHB.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-cpp-DZA36HfS.js → vendor-codemirror-lang-cpp-BWWXBx7W.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-css-B07blnnZ.js → vendor-codemirror-lang-css-Cayfb29q.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-go-BM8K9D1-.js → vendor-codemirror-lang-go-BoKlCNKw.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-html-DaH9adfE.js → vendor-codemirror-lang-html-BQWerHsJ.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-java-CsdC0viz.js → vendor-codemirror-lang-java-DWX7Fq_K.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-javascript-CpGYXGuo.js → vendor-codemirror-lang-javascript-DbpEcZM0.js} +2 -2
- package/dist/assets/{vendor-codemirror-lang-jinja-BBeGzXw5.js → vendor-codemirror-lang-jinja-D1JIJRA7.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-json-58T70Hqs.js → vendor-codemirror-lang-json-BzgckzmB.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-less-BI-xCA59.js → vendor-codemirror-lang-less-B9EpDXFi.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-liquid-zEcMa-zy.js → vendor-codemirror-lang-liquid-9U7kRbSf.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-markdown-BLKnlLP4.js → vendor-codemirror-lang-markdown-D2TEBIvZ.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-php-9f35M83-.js → vendor-codemirror-lang-php-BT6sDVzE.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-python-jAMtYiA9.js +3 -0
- package/dist/assets/{vendor-codemirror-lang-rust-MNDL_jZQ.js → vendor-codemirror-lang-rust-CVO56EiL.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-sass-p9epsSNm.js → vendor-codemirror-lang-sass-DZlOoUWu.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-sql-Bqzh0EXX.js → vendor-codemirror-lang-sql-fZ6t1DPw.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-vue-BnNUWV-p.js → vendor-codemirror-lang-vue-DxbqrLiE.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-wast-C2a-oLmk.js → vendor-codemirror-lang-wast-DyU1Gt-I.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-xml-C9a_K5_p.js +1 -0
- package/dist/assets/{vendor-codemirror-lang-yaml-C0sjswB0.js → vendor-codemirror-lang-yaml-Cptq8l0e.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-ByBn5zIW.js → vendor-codemirror-language-DufXoQvE.js} +2 -2
- package/dist/assets/vendor-codemirror-language-data-CtwSWKtX.js +2 -0
- package/dist/assets/{vendor-codemirror-search-ib-sHD5W.js → vendor-codemirror-search-CvXM_pZg.js} +1 -1
- package/dist/assets/{vendor-codemirror-state-G3UuR-be.js → vendor-codemirror-state-Dek9sMtw.js} +1 -1
- package/dist/assets/{vendor-codemirror-view-Bniqp7p5.js → vendor-codemirror-view-1e0FQ6Wn.js} +1 -1
- package/dist/assets/vendor-framer-motion-DxDVhOLc.js +9 -0
- package/dist/assets/vendor-katex-CuQvj9-L.js +257 -0
- package/dist/assets/vendor-katex-DdZNUVNG.css +1 -0
- package/dist/assets/{vendor-motion-dom-DcCgeG69.js → vendor-motion-dom-HXPOMxb7.js} +1 -1
- package/dist/assets/{vendor-opencode-client-DHzhmLwV.js → vendor-opencode-client-BmFcUkbQ.js} +1 -1
- package/dist/assets/{vendor-replit-codemirror-vim-DkzqxN2H.js → vendor-replit-codemirror-vim-BlJRn-cW.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-virtual-H_JmXOFV.js → vendor-tanstack-react-virtual-CvuLsk7D.js} +1 -1
- package/dist/assets/{vendor-tanstack-virtual-core-Dxc8hSTB.js → vendor-tanstack-virtual-core-DPaNcJcT.js} +1 -1
- package/dist/assets/zh-CN-t2vigSB1.js +20 -0
- package/dist/assets/zh-TW-jAgqBokB.js +20 -0
- package/dist/index.html +6 -6
- package/dist/mini-chat.html +6 -6
- package/dist/mobile.html +6 -6
- package/package.json +8 -5
- package/server/index.js +80 -36
- package/server/lib/agent-memory/actions.js +1 -6
- package/server/lib/agent-memory/routes.js +1 -2
- package/server/lib/agent-memory/runtime.js +1 -8
- package/server/lib/agent-tool/DOCUMENTATION.md +7 -3
- package/server/lib/agent-tool/runtime.js +3 -6
- package/server/lib/agent-tool/runtime.test.js +0 -2
- package/server/lib/browser-control/DOCUMENTATION.md +6 -0
- package/server/lib/changelog/update-notes.js +1 -1
- package/server/lib/context-obligatory/runtime.js +1 -2
- package/server/lib/dev-tunnel/DOCUMENTATION.md +8 -0
- package/server/lib/dev-tunnel/client.js +21 -10
- package/server/lib/dev-tunnel/tunnel.test.js +34 -0
- package/server/lib/dispatch-results/runtime.js +1 -1
- package/server/lib/enterprise-mode.d.ts +1 -0
- package/server/lib/enterprise-mode.js +13 -2
- package/server/lib/enterprise-mode.test.js +21 -0
- package/server/lib/event-stream/DOCUMENTATION.md +2 -2
- package/server/lib/event-stream/index.js +1 -3
- package/server/lib/event-stream/translate-v2.js +1 -9
- package/server/lib/event-stream/translate-v2.test.js +0 -11
- package/server/lib/fs/DOCUMENTATION.md +5 -2
- package/server/lib/fs/routes.js +62 -15
- package/server/lib/fs/routes.test.js +278 -48
- package/server/lib/fs/workspace-file-names.js +104 -0
- package/server/lib/fs/workspace-file-names.test.js +62 -0
- package/server/lib/git/DOCUMENTATION.md +17 -5
- package/server/lib/git/contributor-provenance-storage.js +9 -6
- package/server/lib/git/contributor-provenance-storage.test.js +29 -0
- package/server/lib/git/credential-broker.js +1 -1
- package/server/lib/git/credential-resolver.js +1 -4
- package/server/lib/git/credentials.js +1 -0
- package/server/lib/git/discovery-endpoint.js +1 -1
- package/server/lib/git/identity-provisioning.js +2 -1
- package/server/lib/git/identity-storage.js +3 -4
- package/server/lib/git/lfs-discovery.js +14 -14
- package/server/lib/git/lfs-discovery.test.js +16 -6
- package/server/lib/git/network-operation-plan.js +50 -62
- package/server/lib/git/network-operation-plan.test.js +103 -1
- package/server/lib/git/network-operation-registry.js +4 -6
- package/server/lib/git/network-operation-storage.js +4 -6
- package/server/lib/git/network-operations.js +12 -4
- package/server/lib/git/network-operations.test.js +19 -6
- package/server/lib/git/redaction.js +2 -1
- package/server/lib/git/repository-credential-runtime.js +5 -3
- package/server/lib/git/repository-credential-runtime.test.js +22 -0
- package/server/lib/git/routes.js +31 -137
- package/server/lib/git/routes.test.js +2 -2
- package/server/lib/git/service.js +143 -27
- package/server/lib/git/service.test.js +215 -1
- package/server/lib/git/ssh-credential-storage.js +1 -4
- package/server/lib/git/submodule-discovery.js +1 -1
- package/server/lib/git/worktree-bootstrap-storage.js +3 -4
- package/server/lib/git/worktree-bootstrap-storage.test.js +15 -0
- package/server/lib/git/worktree-creation.js +194 -0
- package/server/lib/git/worktree-creation.test.js +155 -0
- package/server/lib/github/DOCUMENTATION.md +11 -6
- package/server/lib/github/auth.js +2 -4
- package/server/lib/github/device-flow.js +2 -2
- package/server/lib/github/gh-cli-credential.js +1 -0
- package/server/lib/github/octokit.js +31 -0
- package/server/lib/github/pr-status.js +35 -18
- package/server/lib/github/pr-status.test.js +271 -3
- package/server/lib/github/reference-search.js +102 -13
- package/server/lib/github/reference-search.test.js +48 -7
- package/server/lib/github/routes.auth.test.js +18 -0
- package/server/lib/github/routes.js +360 -6
- package/server/lib/github/routes.mutations.test.js +239 -0
- package/server/lib/github/routes.test.js +134 -2
- package/server/lib/gitlab/DOCUMENTATION.md +5 -1
- package/server/lib/gitlab/auth-storage.test.js +1 -0
- package/server/lib/gitlab/mappers.js +30 -1
- package/server/lib/gitlab/resources.js +230 -6
- package/server/lib/gitlab/resources.test.js +179 -0
- package/server/lib/gitlab/routes.js +200 -6
- package/server/lib/gitlab/routes.test.js +186 -2
- package/server/lib/gitlab/validation.js +1 -5
- package/server/lib/guests/DOCUMENTATION.md +1 -1
- package/server/lib/guests/oauth.js +2 -2
- package/server/lib/guests/service.js +1 -1
- package/server/lib/guests/sockets.js +1 -1
- package/server/lib/injected-env.d.ts +8 -0
- package/server/lib/injected-env.js +50 -0
- package/server/lib/injected-env.test.js +56 -0
- package/server/lib/linear/DOCUMENTATION.md +5 -6
- package/server/lib/linear/issues.js +62 -4
- package/server/lib/linear/issues.test.js +68 -0
- package/server/lib/linear/oauth.js +5 -5
- package/server/lib/linear/parse.js +2 -9
- package/server/lib/message-queue/DOCUMENTATION.md +23 -1
- package/server/lib/message-queue/runtime.js +85 -12
- package/server/lib/message-queue/runtime.test.js +172 -2
- package/server/lib/notifications/apns-runtime.js +5 -6
- package/server/lib/notifications/apns-runtime.test.js +20 -4
- package/server/lib/openchamber-control/DOCUMENTATION.md +19 -1
- package/server/lib/openchamber-control/actions.js +2 -1
- package/server/lib/openchamber-control/service.js +119 -20
- package/server/lib/openchamber-control/service.test.js +166 -0
- package/server/lib/openchamber-sessions/archive-store.js +1 -6
- package/server/lib/openchamber-sessions/routes.js +47 -12
- package/server/lib/openchamber-sessions/routes.test.js +65 -2
- package/server/lib/openchamber-sessions/session-metadata-store.js +1 -6
- package/server/lib/opencode/DOCUMENTATION.md +22 -8
- package/server/lib/opencode/agents.js +62 -0
- package/server/lib/opencode/auth-state-runtime.js +3 -1
- package/server/lib/opencode/auth.d.ts +1 -1
- package/server/lib/opencode/auth.js +8 -0
- package/server/lib/opencode/auth.test.js +31 -0
- package/server/lib/opencode/config-entity-routes.js +16 -0
- package/server/lib/opencode/config-entity-v2.test.js +16 -1
- package/server/lib/opencode/config-paths.test.js +2 -2
- package/server/lib/opencode/config-v2.js +2 -4
- package/server/lib/opencode/feature-routes-runtime.js +89 -49
- package/server/lib/opencode/lifecycle.js +41 -21
- package/server/lib/opencode/lifecycle.test.js +50 -2
- package/server/lib/opencode/model-discovery.js +1 -1
- package/server/lib/opencode/model-discovery.test.js +17 -0
- package/server/lib/opencode/models-metadata.js +0 -2
- package/server/lib/opencode/openchamber-routes.test.js +3 -3
- package/server/lib/opencode/plugins.js +6 -9
- package/server/lib/opencode/provider-source-route.test.js +101 -3
- package/server/lib/opencode/providers-disabled.test.js +59 -0
- package/server/lib/opencode/providers.js +34 -0
- package/server/lib/opencode/proxy.js +11 -3
- package/server/lib/opencode/routes.js +40 -2
- package/server/lib/opencode/settings-helpers.js +27 -1
- package/server/lib/opencode/settings-helpers.test.js +19 -7
- package/server/lib/opencode/settings-registry.json +27 -16
- package/server/lib/opencode/settings-runtime.test.js +18 -0
- package/server/lib/opencode/shared-service-runtime.js +23 -104
- package/server/lib/opencode/shared.js +77 -46
- package/server/lib/opencode/shared.test.js +12 -12
- package/server/lib/opencode/shutdown-runtime.test.js +2 -3
- package/server/lib/opencode/skill-routes.js +123 -38
- package/server/lib/opencode/skill-routes.test.js +201 -0
- package/server/lib/opencode/skills.js +81 -34
- package/server/lib/opencode/skills.test.js +176 -21
- package/server/lib/opencode/startup-pipeline-runtime.js +6 -3
- package/server/lib/opencode/startup-pipeline-runtime.test.js +2 -1
- package/server/lib/opencode/watcher.test.js +0 -54
- package/server/lib/outbound-proxy.js +52 -0
- package/server/lib/outbound-proxy.test.js +55 -0
- package/server/lib/package-manager.js +23 -17
- package/server/lib/package-manager.test.js +5 -18
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +1 -1
- package/server/lib/permission-auto-accept/runtime.js +19 -47
- package/server/lib/permission-auto-accept/runtime.test.js +2 -2
- package/server/lib/project-context/DOCUMENTATION.md +7 -1
- package/server/lib/project-context/routes.js +1 -2
- package/server/lib/project-context/runtime.js +12 -14
- package/server/lib/project-context/runtime.test.js +78 -2
- package/server/lib/projects/project-config.js +30 -15
- package/server/lib/projects/project-config.test.js +30 -0
- package/server/lib/projects/project-setup.js +1 -2
- package/server/lib/projects/routes.js +1 -2
- package/server/lib/quota/DOCUMENTATION.md +35 -7
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/claude/index.js +1 -1
- package/server/lib/quota/providers/cline-pass.js +1 -1
- package/server/lib/quota/providers/exe-dev.js +1 -1
- package/server/lib/quota/providers/google/index.js +1 -1
- package/server/lib/quota/providers/hyper.js +1 -1
- package/server/lib/quota/providers/kimi.js +116 -36
- package/server/lib/quota/providers/kimi.test.js +128 -3
- package/server/lib/quota/providers/opencode-go.js +144 -21
- package/server/lib/quota/providers/opencode-go.test.js +229 -6
- package/server/lib/quota/providers/openrouter.js +1 -1
- package/server/lib/quota/providers/zenmux.js +1 -1
- package/server/lib/quota/utils/auth.js +1 -0
- package/server/lib/quota/utils/transformers.js +2 -6
- package/server/lib/relay/DOCUMENTATION.md +18 -3
- package/server/lib/relay/e2ee.js +7 -7
- package/server/lib/relay/host-client.js +84 -13
- package/server/lib/relay/host-client.test.js +201 -1
- package/server/lib/relay/identity.js +6 -29
- package/server/lib/relay/identity.test.js +18 -28
- package/server/lib/relay/key-store.js +141 -0
- package/server/lib/relay/key-store.test.js +138 -0
- package/server/lib/relay/service.js +8 -8
- package/server/lib/relay/service.test.js +10 -5
- package/server/lib/relay/signing-key.js +13 -36
- package/server/lib/relay/tunnel-codec.js +10 -10
- package/server/lib/route-module-exports.test.js +22 -0
- package/server/lib/routing/DOCUMENTATION.md +23 -3
- package/server/lib/routing/cache.js +87 -0
- package/server/lib/routing/defaults.js +11 -0
- package/server/lib/routing/history.js +12 -4
- package/server/lib/routing/runtime.js +39 -3
- package/server/lib/routing/runtime.test.js +81 -2
- package/server/lib/routing/store.js +12 -1
- package/server/lib/routing/store.test.js +13 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +59 -1
- package/server/lib/scheduled-tasks/existing-session.js +119 -0
- package/server/lib/scheduled-tasks/existing-session.test.js +350 -0
- package/server/lib/scheduled-tasks/loops.js +1 -8
- package/server/lib/scheduled-tasks/routes.js +8 -8
- package/server/lib/scheduled-tasks/runtime.js +103 -13
- package/server/lib/scheduled-tasks/runtime.test.js +48 -0
- package/server/lib/scheduled-tasks/service.js +33 -11
- package/server/lib/scheduled-tasks/service.test.js +128 -2
- package/server/lib/scheduled-tasks/session-defaults.js +27 -0
- package/server/lib/scheduled-tasks/session-defaults.test.js +19 -0
- package/server/lib/session-assist/context.js +1 -0
- package/server/lib/session-folders/routes.js +2 -2
- package/server/lib/session-goal/objectives.js +1 -1
- package/server/lib/session-knowledge/routes.js +2 -2
- package/server/lib/session-knowledge/runtime.js +1 -2
- package/server/lib/session-work/DOCUMENTATION.md +39 -14
- package/server/lib/session-work/questions.js +31 -2
- package/server/lib/session-work/runtime.js +22 -13
- package/server/lib/session-work/runtime.test.js +43 -26
- package/server/lib/session-work/state.js +10 -4
- package/server/lib/session-work/state.test.js +8 -1
- package/server/lib/shared/DOCUMENTATION.md +89 -0
- package/server/lib/shared/guards.js +53 -0
- package/server/lib/shared/guards.test.js +112 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +10 -8
- package/server/lib/skills-catalog/cache.js +3 -2
- package/server/lib/skills-catalog/curated-sources.js +5 -1
- package/server/lib/skills-catalog/git.js +107 -20
- package/server/lib/skills-catalog/git.test.js +78 -0
- package/server/lib/skills-catalog/install.js +21 -12
- package/server/lib/skills-catalog/scan.js +25 -15
- package/server/lib/skills-catalog/source.js +26 -1
- package/server/lib/skills-catalog/source.test.js +27 -0
- package/server/lib/source-control/DOCUMENTATION.md +2 -1
- package/server/lib/source-control/audit-storage.js +7 -6
- package/server/lib/source-control/audit-storage.test.js +11 -0
- package/server/lib/source-control/binding-service.js +2 -5
- package/server/lib/source-control/change-request-worktree.js +90 -0
- package/server/lib/source-control/change-request-worktree.test.js +121 -0
- package/server/lib/source-control/mutation-executor.js +1 -1
- package/server/lib/source-control/mutation-storage.js +11 -5
- package/server/lib/source-control/provider-instance.js +1 -2
- package/server/lib/source-control/repository-identity.js +1 -1
- package/server/lib/source-control/routes.js +14 -2
- package/server/lib/spaces/DOCUMENTATION.md +42 -17
- package/server/lib/spaces/command-stream.test.js +15 -0
- package/server/lib/spaces/dispatcher.js +7 -0
- package/server/lib/spaces/dispatcher.test.js +17 -0
- package/server/lib/spaces/hardening.js +2 -2
- package/server/lib/spaces/hardening.test.js +1 -1
- package/server/lib/spaces/host.js +2 -0
- package/server/lib/spaces/journey.js +66 -5
- package/server/lib/spaces/journey.test.js +95 -4
- package/server/lib/spaces/layout.js +1 -1
- package/server/lib/spaces/places/docker-disk.js +38 -14
- package/server/lib/spaces/places/docker-disk.test.js +80 -1
- package/server/lib/spaces/places/docker.js +35 -11
- package/server/lib/spaces/places/fake-docker.js +22 -3
- package/server/lib/spaces/places/memory-place.js +3 -0
- package/server/lib/spaces/places/packed.docker.live.test.js +1 -1
- package/server/lib/spaces/routes.js +5 -0
- package/server/lib/spaces/routes.test.js +4 -2
- package/server/lib/spaces/run-command.js +9 -2
- package/server/lib/spaces/tools.js +4 -5
- package/server/lib/spaces/tools.test.js +2 -2
- package/server/lib/terminal/runtime.js +18 -6
- package/server/lib/terminal/runtime.test.js +66 -0
- package/server/lib/tts/index.js +3 -0
- package/server/lib/tts/service.js +4 -2
- package/server/lib/walkthrough/DOCUMENTATION.md +3 -1
- package/server/lib/walkthrough/index.js +2 -3
- package/server/lib/walkthrough/pr-source.test.js +22 -0
- package/server/lib/walkthrough/sources.js +6 -1
- package/server/opencode-proxy.test.js +127 -0
- package/bin/lib/commands-startup.test.js +0 -15
- package/dist/assets/CodeMirrorCommentWidgets-CVDSjcrP.js +0 -1
- package/dist/assets/DiffView-Ct7vL3fD.js +0 -4
- package/dist/assets/FilesView-DT0uEYgT.js +0 -17
- package/dist/assets/GuestSurfacePane-DVvk1VPu.js +0 -1
- package/dist/assets/LinearIssuesView-C10MwA9h.js +0 -1
- package/dist/assets/MarkdownImageGallery-BbN_K_3d.js +0 -1
- package/dist/assets/MarkdownRendererImpl-BLt2Tkk9.js +0 -10
- package/dist/assets/OnboardingScreen-Bgp5A3WB.js +0 -1
- package/dist/assets/PierreDiffViewer-CWJLj-S-.js +0 -210
- package/dist/assets/PlanView-DsRWnsOI.js +0 -5
- package/dist/assets/PublishDialog-CCeKloRu.js +0 -2
- package/dist/assets/SettingsView-BuPPsNPp.js +0 -30
- package/dist/assets/SettingsWindow-CM5fL8Ot.js +0 -1
- package/dist/assets/SubmoduleDiffSummary-CfsMcfiQ.js +0 -1
- package/dist/assets/ToolOutputDialog-mZrfSofz.js +0 -15
- package/dist/assets/ToolPartDiffPreview-BUGCRYEL.js +0 -8
- package/dist/assets/WalkthroughView-ClBo7PPd.js +0 -4
- package/dist/assets/appThemeRegistry-CxzMxR-H.js +0 -1
- package/dist/assets/de-CBla4hjd.js +0 -14
- package/dist/assets/es-BmnZtxTD.js +0 -20
- package/dist/assets/fileTreeStatus-BSHFIGEg.js +0 -1
- package/dist/assets/fr-OVKIBUZO.js +0 -18
- package/dist/assets/index-CKCFKmjF.js +0 -1
- package/dist/assets/index-Dbx4V4GN.css +0 -1
- package/dist/assets/index-vQ1Pg1cr.js +0 -61
- package/dist/assets/ja-6v-yr-gE.js +0 -20
- package/dist/assets/ko-I_ZLvgQ9.js +0 -20
- package/dist/assets/main-CmO4dRtR.js +0 -2
- package/dist/assets/main-b9vqhlYM.js +0 -999
- package/dist/assets/markdownCore-BPEZRQfP.js +0 -7
- package/dist/assets/miniChat-B5Oi7B-6.js +0 -2
- package/dist/assets/mobile-CVtShif9.js +0 -2
- package/dist/assets/nl-DcvIJf46.js +0 -20
- package/dist/assets/patchFileDiff-CW3wCH1O.js +0 -13
- package/dist/assets/pl-JxCUl0sd.js +0 -20
- package/dist/assets/pt-BR-L89mjja2.js +0 -20
- package/dist/assets/relocateRestoredSession-DYh_9oBa.js +0 -1
- package/dist/assets/renderElectronMiniChatApp-BD-1JHSg.js +0 -2
- package/dist/assets/renderMobileApp-7s5InXt1.js +0 -25
- package/dist/assets/runtimeEndpointReset-DqLJC6L1.js +0 -53
- package/dist/assets/shikiHighlight-CXgZnvHh.js +0 -1
- package/dist/assets/space-apply-1trGVIl8.js +0 -1
- package/dist/assets/space-close-y7j-NuBv.js +0 -1
- package/dist/assets/tr-ka5J1flt.js +0 -11
- package/dist/assets/uk-PyGe6Q_u.js +0 -20
- package/dist/assets/useInlineCommentController-CrjSGCpi.js +0 -2
- package/dist/assets/vendor-codemirror-lang-python-B7ClrMIT.js +0 -3
- package/dist/assets/vendor-codemirror-lang-xml-C8tGnS64.js +0 -1
- package/dist/assets/vendor-codemirror-language-data-q2bKO1EM.js +0 -2
- package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +0 -1
- package/dist/assets/vendor-katex-CHD9BdDO.js +0 -257
- package/dist/assets/vendor-katex-Cp579UoJ.css +0 -1
- package/dist/assets/zh-CN-B0P2tybJ.js +0 -20
- package/dist/assets/zh-TW-Bs9C5cN8.js +0 -20
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, mock, test, vi } from 'bun:test';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
2
6
|
|
|
3
|
-
import { findBranchPrCandidates, invalidateRepoPullsCache, isHistoricalPrOfCheckout } from './pr-status.js';
|
|
7
|
+
import { findBranchPrCandidates, invalidateRepoPullsCache, isHistoricalPrOfCheckout, resolveGitHubPrStatus } from './pr-status.js';
|
|
4
8
|
import { createOctokit, getOctokitCacheIdentity } from './octokit.js';
|
|
5
9
|
|
|
6
10
|
const listMock = mock(async () => ({ data: [] }));
|
|
@@ -274,6 +278,110 @@ describe('findBranchPrCandidates', () => {
|
|
|
274
278
|
expect(listMock.mock.calls.some((entry) => entry[0]?.state === 'all')).toBe(true);
|
|
275
279
|
expect(listMock.mock.calls.length).toBeGreaterThan(callsAfterFirst + 1);
|
|
276
280
|
});
|
|
281
|
+
|
|
282
|
+
test('does not reuse a cached history miss for a different source owner', async () => {
|
|
283
|
+
const target = { repo: { owner: 'upstream-cache-scope', repo: 'app' }, remoteName: 'origin' };
|
|
284
|
+
const ownerA = { repo: { owner: 'fork-cache-a', repo: 'app' } };
|
|
285
|
+
const ownerB = { repo: { owner: 'fork-cache-b', repo: 'app' } };
|
|
286
|
+
const ownerBPr = {
|
|
287
|
+
...mergedPr,
|
|
288
|
+
number: 16,
|
|
289
|
+
head: { ...mergedPr.head, label: 'fork-cache-b:feature', user: { login: 'fork-cache-b' }, repo: { owner: { login: 'fork-cache-b' }, name: 'app' } },
|
|
290
|
+
};
|
|
291
|
+
listMock.mockImplementation(async ({ head }) => (
|
|
292
|
+
head === 'fork-cache-b:feature' ? { data: [ownerBPr] } : { data: [] }
|
|
293
|
+
));
|
|
294
|
+
const octokit = octokitFor('test-token', 'test-account');
|
|
295
|
+
|
|
296
|
+
await findBranchPrCandidates({
|
|
297
|
+
octokit,
|
|
298
|
+
target,
|
|
299
|
+
branch: 'feature',
|
|
300
|
+
sourceCandidates: [ownerA],
|
|
301
|
+
force: true,
|
|
302
|
+
includeHistory: true,
|
|
303
|
+
});
|
|
304
|
+
const { historical } = await findBranchPrCandidates({
|
|
305
|
+
octokit,
|
|
306
|
+
target,
|
|
307
|
+
branch: 'feature',
|
|
308
|
+
sourceCandidates: [ownerB],
|
|
309
|
+
includeHistory: true,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
expect(historical?.number).toBe(16);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe('resolveGitHubPrStatus for a contributor fork checkout', () => {
|
|
317
|
+
let directory;
|
|
318
|
+
|
|
319
|
+
beforeEach(async () => {
|
|
320
|
+
directory = await fs.mkdtemp(path.join(os.tmpdir(), 'pr-status-fork-'));
|
|
321
|
+
const git = (...args) => execFileSync('git', args, { cwd: directory, stdio: 'ignore' });
|
|
322
|
+
git('init', '-q', '-b', 'fix/thing');
|
|
323
|
+
git('remote', 'add', 'origin', 'https://github.com/acme/app.git');
|
|
324
|
+
git('remote', 'add', 'pr-contrib', 'https://github.com/contrib/app.git');
|
|
325
|
+
invalidateRepoPullsCache('acme', 'app');
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
afterEach(async () => {
|
|
329
|
+
await fs.rm(directory, { recursive: true, force: true });
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const forkPr = {
|
|
333
|
+
number: 44,
|
|
334
|
+
state: 'open',
|
|
335
|
+
head: { ref: 'fix/thing', label: 'contrib:fix/thing', user: { login: 'contrib' }, repo: { owner: { login: 'contrib' }, name: 'app' } },
|
|
336
|
+
};
|
|
337
|
+
const octokit = () => ({
|
|
338
|
+
openChamberCacheIdentity: getOctokitCacheIdentity(createOctokit('fork-token', 'fork-account')),
|
|
339
|
+
rest: {
|
|
340
|
+
repos: {
|
|
341
|
+
get: async ({ owner, repo }) => ({
|
|
342
|
+
data: owner === 'contrib'
|
|
343
|
+
? { default_branch: 'main', parent: { owner: { login: 'acme' }, name: repo } }
|
|
344
|
+
: { default_branch: 'main' },
|
|
345
|
+
}),
|
|
346
|
+
},
|
|
347
|
+
pulls: { list: async ({ owner }) => ({ data: owner === 'acme' ? [forkPr] : [] }) },
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test('finds the fork PR when the caller names the fork remote as the branch source', async () => {
|
|
352
|
+
const status = await resolveGitHubPrStatus({
|
|
353
|
+
octokit: octokit(), directory, branch: 'fix/thing', remoteName: 'origin', sourceRemoteName: 'pr-contrib', force: true,
|
|
354
|
+
});
|
|
355
|
+
expect(status.pr?.number).toBe(44);
|
|
356
|
+
expect(status.repo).toMatchObject({ owner: 'acme', repo: 'app' });
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test('finds the merged fork PR in the primary remote once it is merged', async () => {
|
|
360
|
+
git(directory, '-c', 'user.name=Test User', '-c', 'user.email=test@example.com', 'commit', '-q', '--allow-empty', '-m', 'fix');
|
|
361
|
+
const mergedForkPr = {
|
|
362
|
+
...forkPr,
|
|
363
|
+
state: 'closed',
|
|
364
|
+
merged_at: '2026-01-01T00:00:00Z',
|
|
365
|
+
head: { ...forkPr.head, sha: git(directory, 'rev-parse', 'HEAD').trim() },
|
|
366
|
+
};
|
|
367
|
+
const client = octokit();
|
|
368
|
+
client.rest.pulls.list = async ({ owner, state, head }) => ({
|
|
369
|
+
data: owner === 'acme' && state === 'all' && head === 'contrib:fix/thing' ? [mergedForkPr] : [],
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
const status = await resolveGitHubPrStatus({
|
|
373
|
+
octokit: client, directory, branch: 'fix/thing', remoteName: 'origin', sourceRemoteName: 'pr-contrib', force: true,
|
|
374
|
+
});
|
|
375
|
+
expect(status.pr?.number).toBe(44);
|
|
376
|
+
expect(status.repo).toMatchObject({ owner: 'acme', repo: 'app' });
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('without a source remote a same-named fork branch is not this branch', async () => {
|
|
380
|
+
const status = await resolveGitHubPrStatus({
|
|
381
|
+
octokit: octokit(), directory, branch: 'fix/thing', remoteName: 'origin', force: true,
|
|
382
|
+
});
|
|
383
|
+
expect(status.pr).toBeNull();
|
|
384
|
+
});
|
|
277
385
|
});
|
|
278
386
|
|
|
279
387
|
describe('isHistoricalPrOfCheckout', () => {
|
|
@@ -283,15 +391,30 @@ describe('isHistoricalPrOfCheckout', () => {
|
|
|
283
391
|
|
|
284
392
|
test('a merged PR whose head commit is in the checkout history belongs to it', async () => {
|
|
285
393
|
isAncestorMock.mockImplementation(async () => true);
|
|
394
|
+
const isPatchEquivalent = mock(async () => false);
|
|
286
395
|
const pr = { ...mergedPr, head: { ...mergedPr.head, sha: 'abc1234' } };
|
|
287
|
-
expect(await isHistoricalPrOfCheckout('/repo', pr, { isAncestor: isAncestorMock })).toBe(true);
|
|
396
|
+
expect(await isHistoricalPrOfCheckout('/repo', pr, { isAncestor: isAncestorMock, isPatchEquivalent })).toBe(true);
|
|
288
397
|
expect(isAncestorMock).toHaveBeenCalledWith('/repo', 'abc1234');
|
|
398
|
+
expect(isPatchEquivalent).not.toHaveBeenCalled();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test('a PR with a patch-equivalent head commit belongs to the checkout', async () => {
|
|
402
|
+
isAncestorMock.mockImplementation(async () => false);
|
|
403
|
+
const isPatchEquivalent = mock(async () => true);
|
|
404
|
+
const pr = { ...mergedPr, head: { ...mergedPr.head, sha: 'abc1234' } };
|
|
405
|
+
|
|
406
|
+
const belongsToCheckout = await isHistoricalPrOfCheckout('/repo', pr, { isAncestor: isAncestorMock, isPatchEquivalent });
|
|
407
|
+
|
|
408
|
+
expect(isPatchEquivalent).toHaveBeenCalledWith('/repo', 'abc1234');
|
|
409
|
+
expect(belongsToCheckout).toBe(true);
|
|
289
410
|
});
|
|
290
411
|
|
|
291
412
|
test('a reused branch name without the merged commits does not inherit the PR', async () => {
|
|
292
413
|
isAncestorMock.mockImplementation(async () => false);
|
|
414
|
+
const isPatchEquivalent = mock(async () => false);
|
|
293
415
|
const pr = { ...mergedPr, head: { ...mergedPr.head, sha: 'abc1234' } };
|
|
294
|
-
expect(await isHistoricalPrOfCheckout('/repo', pr, { isAncestor: isAncestorMock })).toBe(false);
|
|
416
|
+
expect(await isHistoricalPrOfCheckout('/repo', pr, { isAncestor: isAncestorMock, isPatchEquivalent })).toBe(false);
|
|
417
|
+
expect(isPatchEquivalent).toHaveBeenCalledWith('/repo', 'abc1234');
|
|
295
418
|
});
|
|
296
419
|
|
|
297
420
|
test('a PR without a head sha is never attributed', async () => {
|
|
@@ -299,3 +422,148 @@ describe('isHistoricalPrOfCheckout', () => {
|
|
|
299
422
|
expect(isAncestorMock).not.toHaveBeenCalled();
|
|
300
423
|
});
|
|
301
424
|
});
|
|
425
|
+
|
|
426
|
+
const git = (directory, ...args) => execFileSync('git', ['-C', directory, ...args], {
|
|
427
|
+
encoding: 'utf8',
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const createRebasedCheckout = async (suffix, branch = 'feature') => {
|
|
431
|
+
const directory = await fs.mkdtemp(path.join(os.tmpdir(), `oc-pr-history-${suffix}-`));
|
|
432
|
+
git(directory, 'init', `--initial-branch=${branch}`);
|
|
433
|
+
git(directory, 'config', 'user.name', 'Test User');
|
|
434
|
+
git(directory, 'config', 'user.email', 'test@example.com');
|
|
435
|
+
await fs.writeFile(path.join(directory, 'status.txt'), 'base\n');
|
|
436
|
+
git(directory, 'add', 'status.txt');
|
|
437
|
+
git(directory, 'commit', '-m', 'base');
|
|
438
|
+
await fs.writeFile(path.join(directory, 'status.txt'), 'rebased change\n');
|
|
439
|
+
git(directory, 'commit', '-am', 'original change');
|
|
440
|
+
const originalSha = git(directory, 'rev-parse', 'HEAD').trim();
|
|
441
|
+
git(directory, 'reset', '--hard', 'HEAD~1');
|
|
442
|
+
await fs.writeFile(path.join(directory, 'status.txt'), 'rebased change\n');
|
|
443
|
+
git(directory, 'commit', '-am', 'rebased change');
|
|
444
|
+
const baseSha = git(directory, 'rev-parse', 'HEAD~1').trim();
|
|
445
|
+
git(directory, 'remote', 'add', 'origin', `https://github.com/fork-${suffix}/app-${suffix}.git`);
|
|
446
|
+
git(directory, 'remote', 'add', 'upstream', `https://github.com/upstream-${suffix}/app-${suffix}.git`);
|
|
447
|
+
git(directory, 'remote', 'add', 'contributor', `https://github.com/contributor-${suffix}/app-${suffix}.git`);
|
|
448
|
+
return { directory, originalSha, baseSha };
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const createForkNetworkOctokit = ({ suffix, historicalPr, forkHistoricalPr = null, openParentPr = null, branch = 'feature' }) => {
|
|
452
|
+
const calls = [];
|
|
453
|
+
const fork = { owner: `fork-${suffix}`, repo: `app-${suffix}` };
|
|
454
|
+
const parent = { owner: `upstream-${suffix}`, repo: `app-${suffix}` };
|
|
455
|
+
const contributor = { owner: `contributor-${suffix}`, repo: `app-${suffix}` };
|
|
456
|
+
const octokit = {
|
|
457
|
+
openChamberCacheIdentity: getOctokitCacheIdentity(createOctokit(`token-${suffix}`, `account-${suffix}`)),
|
|
458
|
+
rest: {
|
|
459
|
+
repos: {
|
|
460
|
+
get: async ({ owner, repo }) => {
|
|
461
|
+
if (owner === fork.owner && repo === fork.repo) {
|
|
462
|
+
return { data: { default_branch: 'main', parent: { owner: { login: parent.owner }, name: parent.repo } } };
|
|
463
|
+
}
|
|
464
|
+
return { data: { default_branch: 'main' } };
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
pulls: {
|
|
468
|
+
list: async (options) => {
|
|
469
|
+
calls.push(options);
|
|
470
|
+
if (!options.head && options.owner === parent.owner && openParentPr) {
|
|
471
|
+
return { data: [openParentPr] };
|
|
472
|
+
}
|
|
473
|
+
if (options.owner === fork.owner && options.state === 'all' && options.head === `${fork.owner}:${branch}` && forkHistoricalPr) {
|
|
474
|
+
return { data: [forkHistoricalPr] };
|
|
475
|
+
}
|
|
476
|
+
if (options.owner === parent.owner && options.state === 'all' && options.head === `${fork.owner}:${branch}`) {
|
|
477
|
+
return { data: [historicalPr] };
|
|
478
|
+
}
|
|
479
|
+
return { data: [] };
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
return { octokit, calls, fork, parent, contributor };
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
describe('resolveGitHubPrStatus fork history', () => {
|
|
488
|
+
test.each([
|
|
489
|
+
['closed', null],
|
|
490
|
+
['merged', '2026-01-01T00:00:00Z'],
|
|
491
|
+
])('restores a patch-equivalent %s PR from the primary fork parent', async (state, mergedAt) => {
|
|
492
|
+
const suffix = `terminal-${state}`;
|
|
493
|
+
const { directory, originalSha } = await createRebasedCheckout(suffix);
|
|
494
|
+
const historicalPr = {
|
|
495
|
+
number: state === 'closed' ? 301 : 302,
|
|
496
|
+
state: 'closed',
|
|
497
|
+
merged_at: mergedAt,
|
|
498
|
+
head: {
|
|
499
|
+
sha: originalSha,
|
|
500
|
+
ref: 'feature',
|
|
501
|
+
label: `fork-${suffix}:feature`,
|
|
502
|
+
user: { login: `fork-${suffix}` },
|
|
503
|
+
repo: { owner: { login: `fork-${suffix}` }, name: `app-${suffix}` },
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
const { octokit, calls, fork, parent, contributor } = createForkNetworkOctokit({ suffix, historicalPr });
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
const resolved = await resolveGitHubPrStatus({ octokit, directory, branch: 'feature', remoteName: 'origin', force: true });
|
|
510
|
+
|
|
511
|
+
expect(resolved.pr?.number).toBe(historicalPr.number);
|
|
512
|
+
expect(resolved.repo).toMatchObject(parent);
|
|
513
|
+
const historyCalls = calls.filter((call) => call.state === 'all');
|
|
514
|
+
expect(historyCalls).toEqual([
|
|
515
|
+
expect.objectContaining({ owner: fork.owner, head: `${fork.owner}:feature` }),
|
|
516
|
+
expect.objectContaining({ owner: fork.owner, head: `${parent.owner}:feature` }),
|
|
517
|
+
expect.objectContaining({ owner: parent.owner, head: `${fork.owner}:feature` }),
|
|
518
|
+
expect.objectContaining({ owner: parent.owner, head: `${parent.owner}:feature` }),
|
|
519
|
+
]);
|
|
520
|
+
expect(historyCalls.some((call) => call.owner === contributor.owner)).toBe(false);
|
|
521
|
+
|
|
522
|
+
const callsAfterFirst = calls.length;
|
|
523
|
+
const repeated = await resolveGitHubPrStatus({ octokit, directory, branch: 'feature', remoteName: 'origin' });
|
|
524
|
+
expect(repeated.pr?.number).toBe(historicalPr.number);
|
|
525
|
+
expect(calls).toHaveLength(callsAfterFirst);
|
|
526
|
+
} finally {
|
|
527
|
+
await fs.rm(directory, { recursive: true, force: true });
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test('returns an open parent PR before an earlier terminal fork PR', async () => {
|
|
532
|
+
const suffix = 'open-priority';
|
|
533
|
+
const { directory, originalSha } = await createRebasedCheckout(suffix);
|
|
534
|
+
const terminalPr = {
|
|
535
|
+
number: 303,
|
|
536
|
+
state: 'closed',
|
|
537
|
+
head: { sha: originalSha, ref: 'feature', label: `fork-${suffix}:feature`, user: { login: `fork-${suffix}` }, repo: { owner: { login: `fork-${suffix}` }, name: `app-${suffix}` } },
|
|
538
|
+
};
|
|
539
|
+
const openParentPr = { ...terminalPr, number: 304, state: 'open', merged_at: null };
|
|
540
|
+
const { octokit } = createForkNetworkOctokit({ suffix, historicalPr: terminalPr, forkHistoricalPr: terminalPr, openParentPr });
|
|
541
|
+
|
|
542
|
+
try {
|
|
543
|
+
const resolved = await resolveGitHubPrStatus({ octokit, directory, branch: 'feature', remoteName: 'origin', force: true });
|
|
544
|
+
expect(resolved.pr?.number).toBe(openParentPr.number);
|
|
545
|
+
} finally {
|
|
546
|
+
await fs.rm(directory, { recursive: true, force: true });
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test('does not inherit terminal fork history on the default branch', async () => {
|
|
551
|
+
const suffix = 'default-main-history';
|
|
552
|
+
const { directory, baseSha } = await createRebasedCheckout(suffix, 'main');
|
|
553
|
+
const terminalPr = {
|
|
554
|
+
number: 305,
|
|
555
|
+
state: 'closed',
|
|
556
|
+
merged_at: '2026-01-01T00:00:00Z',
|
|
557
|
+
head: { sha: baseSha, ref: 'main', label: `fork-${suffix}:main`, user: { login: `fork-${suffix}` }, repo: { owner: { login: `fork-${suffix}` }, name: `app-${suffix}` } },
|
|
558
|
+
};
|
|
559
|
+
const { octokit, calls } = createForkNetworkOctokit({ suffix, historicalPr: terminalPr, branch: 'main' });
|
|
560
|
+
|
|
561
|
+
try {
|
|
562
|
+
const resolved = await resolveGitHubPrStatus({ octokit, directory, branch: 'main', remoteName: 'origin', force: true });
|
|
563
|
+
expect(resolved.pr).toBeNull();
|
|
564
|
+
expect(calls.some((call) => call.state === 'all')).toBe(false);
|
|
565
|
+
} finally {
|
|
566
|
+
await fs.rm(directory, { recursive: true, force: true });
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
});
|
|
@@ -22,14 +22,21 @@
|
|
|
22
22
|
|
|
23
23
|
import { z } from 'zod';
|
|
24
24
|
|
|
25
|
-
import { isGraphqlRateLimitError, parseSummaryRefs } from './pr-summaries.js';
|
|
25
|
+
import { checkContextSchema, isGraphqlRateLimitError, parseSummaryRefs, summarizeCheckContexts } from './pr-summaries.js';
|
|
26
26
|
|
|
27
27
|
const REFERENCE_PAGE_SIZE = 30;
|
|
28
28
|
// The preview shows the description; the attach path reads the full one.
|
|
29
29
|
const BODY_PREVIEW_MAX = 20_000;
|
|
30
30
|
// Keys match the filter schema below.
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// The list's state and whose items it shows combine, one qualifier each.
|
|
32
|
+
const SEARCH_STATES = {
|
|
33
|
+
open: 'is:open',
|
|
34
|
+
closed: 'is:closed',
|
|
35
|
+
merged: 'is:merged',
|
|
36
|
+
all: '',
|
|
37
|
+
};
|
|
38
|
+
const SEARCH_PEOPLE = {
|
|
39
|
+
any: '',
|
|
33
40
|
assigned: 'assignee:@me',
|
|
34
41
|
created: 'author:@me',
|
|
35
42
|
reviewRequested: 'review-requested:@me',
|
|
@@ -67,6 +74,8 @@ fragment PullReference on PullRequest {
|
|
|
67
74
|
|
|
68
75
|
// The newest comments and reviews; `totalCount` says how many there are.
|
|
69
76
|
const DETAIL_COMMENT_LIMIT = 50;
|
|
77
|
+
// The newest commits, so the preview can show what each review answered.
|
|
78
|
+
const DETAIL_COMMIT_LIMIT = 50;
|
|
70
79
|
|
|
71
80
|
const DETAIL_QUERY = `
|
|
72
81
|
query ReferenceDetail($owner: String!, $repo: String!, $number: Int!) {
|
|
@@ -79,11 +88,33 @@ query ReferenceDetail($owner: String!, $repo: String!, $number: Int!) {
|
|
|
79
88
|
}
|
|
80
89
|
... on PullRequest {
|
|
81
90
|
number
|
|
91
|
+
state
|
|
82
92
|
reviewDecision
|
|
93
|
+
# The preview's checks, from the PR itself rather than the list's batch.
|
|
94
|
+
headCommit: commits(last: 1) {
|
|
95
|
+
nodes {
|
|
96
|
+
commit {
|
|
97
|
+
statusCheckRollup {
|
|
98
|
+
contexts(first: 100) {
|
|
99
|
+
nodes {
|
|
100
|
+
__typename
|
|
101
|
+
... on CheckRun { databaseId name status conclusion startedAt checkSuite { app { databaseId } } }
|
|
102
|
+
... on StatusContext { context state }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
reviewRequests(first: 30) { nodes { requestedReviewer { __typename ... on User { login avatarUrl } } } }
|
|
83
110
|
additions
|
|
84
111
|
deletions
|
|
85
112
|
changedFiles
|
|
86
113
|
comments(last: ${DETAIL_COMMENT_LIMIT}) { totalCount nodes { ...DetailComment } }
|
|
114
|
+
commits(last: ${DETAIL_COMMIT_LIMIT}) {
|
|
115
|
+
totalCount
|
|
116
|
+
nodes { commit { oid messageHeadline committedDate url author { name user { login avatarUrl } } } }
|
|
117
|
+
}
|
|
87
118
|
reviews(last: 30) {
|
|
88
119
|
nodes {
|
|
89
120
|
author { login avatarUrl }
|
|
@@ -175,11 +206,40 @@ const detailSchema = z.object({
|
|
|
175
206
|
z.object({
|
|
176
207
|
__typename: z.literal('PullRequest'),
|
|
177
208
|
number: z.number().int(),
|
|
209
|
+
state: z.enum(['OPEN', 'CLOSED', 'MERGED']),
|
|
178
210
|
reviewDecision: z.enum(['APPROVED', 'CHANGES_REQUESTED', 'REVIEW_REQUIRED']).nullable(),
|
|
211
|
+
headCommit: z.object({
|
|
212
|
+
nodes: z.array(z.object({
|
|
213
|
+
commit: z.object({
|
|
214
|
+
statusCheckRollup: z.object({ contexts: z.object({ nodes: z.array(checkContextSchema) }) }).nullable(),
|
|
215
|
+
}),
|
|
216
|
+
})),
|
|
217
|
+
}),
|
|
218
|
+
reviewRequests: z.object({
|
|
219
|
+
nodes: z.array(z.object({
|
|
220
|
+
requestedReviewer: z.object({
|
|
221
|
+
__typename: z.string(),
|
|
222
|
+
login: z.string().optional(),
|
|
223
|
+
avatarUrl: z.string().nullable().optional(),
|
|
224
|
+
}).nullable(),
|
|
225
|
+
})),
|
|
226
|
+
}),
|
|
179
227
|
additions: z.number().int(),
|
|
180
228
|
deletions: z.number().int(),
|
|
181
229
|
changedFiles: z.number().int(),
|
|
182
230
|
comments: z.object({ totalCount: z.number().int(), nodes: z.array(detailCommentSchema) }),
|
|
231
|
+
commits: z.object({
|
|
232
|
+
totalCount: z.number().int(),
|
|
233
|
+
nodes: z.array(z.object({
|
|
234
|
+
commit: z.object({
|
|
235
|
+
oid: z.string(),
|
|
236
|
+
messageHeadline: z.string(),
|
|
237
|
+
committedDate: z.string().nullable(),
|
|
238
|
+
url: z.string().nullable(),
|
|
239
|
+
author: z.object({ name: z.string().nullable(), user: userSchema }).nullable(),
|
|
240
|
+
}),
|
|
241
|
+
})),
|
|
242
|
+
}),
|
|
183
243
|
reviews: z.object({
|
|
184
244
|
nodes: z.array(z.object({
|
|
185
245
|
author: userSchema,
|
|
@@ -322,9 +382,29 @@ export async function fetchReferenceDetail({ octokit, owner, repo, number }) {
|
|
|
322
382
|
commentTotal: item.comments.totalCount,
|
|
323
383
|
pull: {
|
|
324
384
|
reviewDecision: item.reviewDecision ? REVIEW_DECISIONS[item.reviewDecision] : null,
|
|
385
|
+
// People asked to review and not done yet; a team request is left out
|
|
386
|
+
// since the board only picks people.
|
|
387
|
+
reviewers: item.reviewRequests.nodes.flatMap(({ requestedReviewer: reviewer }) => (
|
|
388
|
+
reviewer?.__typename === 'User' && reviewer.login
|
|
389
|
+
? [{ id: reviewer.login, ...toAuthor({ login: reviewer.login, avatarUrl: reviewer.avatarUrl }) }]
|
|
390
|
+
: []
|
|
391
|
+
)),
|
|
392
|
+
// Like the list's statuses: a closed or merged PR's checks are not actionable.
|
|
393
|
+
checks: item.state === 'OPEN'
|
|
394
|
+
? summarizeCheckContexts(item.headCommit.nodes[0]?.commit.statusCheckRollup?.contexts.nodes ?? [])
|
|
395
|
+
: null,
|
|
325
396
|
additions: item.additions,
|
|
326
397
|
deletions: item.deletions,
|
|
327
398
|
changedFiles: item.changedFiles,
|
|
399
|
+
commits: item.commits.nodes.map(({ commit }) => ({
|
|
400
|
+
sha: commit.oid,
|
|
401
|
+
headline: commit.messageHeadline,
|
|
402
|
+
author: toAuthor(commit.author?.user ?? null),
|
|
403
|
+
authorName: commit.author?.name ?? null,
|
|
404
|
+
committedAt: commit.committedDate,
|
|
405
|
+
url: commit.url,
|
|
406
|
+
})),
|
|
407
|
+
commitTotal: item.commits.totalCount,
|
|
328
408
|
},
|
|
329
409
|
};
|
|
330
410
|
}
|
|
@@ -348,12 +428,18 @@ export function readPullStatusRefs(value) {
|
|
|
348
428
|
return parseSummaryRefs(refs);
|
|
349
429
|
}
|
|
350
430
|
|
|
351
|
-
const
|
|
431
|
+
const stateSchema = z.enum(['open', 'closed', 'merged', 'all']).catch('open');
|
|
432
|
+
const peopleSchema = z.enum(['any', 'assigned', 'created', 'reviewRequested']).catch('any');
|
|
352
433
|
const kindSchema = z.enum(['issue', 'pull']).nullable().catch(null);
|
|
353
434
|
|
|
354
|
-
/**
|
|
355
|
-
export function
|
|
356
|
-
return
|
|
435
|
+
/** A list's state; anything else reads as `open`. */
|
|
436
|
+
export function readReferenceState(value) {
|
|
437
|
+
return stateSchema.parse(value);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Whose items a list shows; anything else reads as `any`. */
|
|
441
|
+
export function readReferencePeople(value) {
|
|
442
|
+
return peopleSchema.parse(value);
|
|
357
443
|
}
|
|
358
444
|
|
|
359
445
|
/** `issue` or `pull`; anything else is null. */
|
|
@@ -391,18 +477,21 @@ export function parseReferenceLookup(text, repos) {
|
|
|
391
477
|
* The GitHub search string for one picker page. The user's text may carry
|
|
392
478
|
* its own qualifiers; state and sort are added only when it does not.
|
|
393
479
|
*/
|
|
394
|
-
export function buildReferenceSearchQuery({ repos, kind,
|
|
480
|
+
export function buildReferenceSearchQuery({ repos, kind, state, people, text }) {
|
|
395
481
|
const userText = text.trim();
|
|
396
482
|
const parts = repos.map((entry) => `repo:${entry.owner}/${entry.repo}`);
|
|
397
483
|
parts.push(kind === 'pull' ? 'is:pr' : 'is:issue');
|
|
398
484
|
if (!/(^|\s)(is:(open|closed|merged|unmerged)|state:\S+)/i.test(userText)) {
|
|
399
|
-
|
|
485
|
+
// An issue is never merged; the nearest list is its closed one.
|
|
486
|
+
const stateQualifier = SEARCH_STATES[kind === 'issue' && state === 'merged' ? 'closed' : state];
|
|
487
|
+
if (stateQualifier) parts.push(stateQualifier);
|
|
400
488
|
}
|
|
401
489
|
if (!/(^|\s)sort:\S+/i.test(userText)) {
|
|
402
490
|
parts.push('sort:updated-desc');
|
|
403
491
|
}
|
|
404
|
-
|
|
405
|
-
|
|
492
|
+
// Review requests exist for pull requests only.
|
|
493
|
+
const peopleQualifier = kind === 'issue' && people === 'reviewRequested' ? '' : SEARCH_PEOPLE[people];
|
|
494
|
+
if (peopleQualifier) parts.push(peopleQualifier);
|
|
406
495
|
if (userText) parts.push(userText);
|
|
407
496
|
return parts.join(' ');
|
|
408
497
|
}
|
|
@@ -442,7 +531,7 @@ async function runPartialQuery(octokit, query, variables) {
|
|
|
442
531
|
* first. A lookup answers every repo that has the number, both kinds; a
|
|
443
532
|
* search answers one kind, newest activity first.
|
|
444
533
|
*/
|
|
445
|
-
export async function searchGitHubReferences({ octokit, repos, kind,
|
|
534
|
+
export async function searchGitHubReferences({ octokit, repos, kind, state, people, text, cursor }) {
|
|
446
535
|
const lookup = parseReferenceLookup(text, repos);
|
|
447
536
|
if (lookup) {
|
|
448
537
|
if (lookup.repos.length === 0) {
|
|
@@ -457,7 +546,7 @@ export async function searchGitHubReferences({ octokit, repos, kind, filter, tex
|
|
|
457
546
|
}
|
|
458
547
|
|
|
459
548
|
const { search } = searchResultSchema.parse(await octokit.graphql(SEARCH_QUERY, {
|
|
460
|
-
q: buildReferenceSearchQuery({ repos, kind,
|
|
549
|
+
q: buildReferenceSearchQuery({ repos, kind, state, people, text }),
|
|
461
550
|
first: REFERENCE_PAGE_SIZE,
|
|
462
551
|
after: cursor || null,
|
|
463
552
|
}));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import { buildReferenceSearchQuery, fetchReferenceDetail, parseReferenceLookup,
|
|
3
|
+
import { buildReferenceSearchQuery, fetchReferenceDetail, parseReferenceLookup, readReferencePeople, readReferenceState, toReference } from './reference-search.js';
|
|
4
4
|
|
|
5
5
|
const origin = { owner: 'acme', repo: 'app', source: 'origin' };
|
|
6
6
|
const upstream = { owner: 'Upstream', repo: 'App', source: 'upstream' };
|
|
@@ -21,20 +21,36 @@ const common = (number, repository = { name: 'app', owner: { login: 'acme' } })
|
|
|
21
21
|
|
|
22
22
|
describe('buildReferenceSearchQuery', () => {
|
|
23
23
|
it('lists open items of one kind across the network, newest activity first', () => {
|
|
24
|
-
expect(buildReferenceSearchQuery({ repos, kind: 'issue',
|
|
24
|
+
expect(buildReferenceSearchQuery({ repos, kind: 'issue', state: 'open', people: 'any', text: '' }))
|
|
25
25
|
.toBe('repo:acme/app repo:Upstream/App is:issue is:open sort:updated-desc');
|
|
26
|
-
expect(buildReferenceSearchQuery({ repos: [origin], kind: 'pull',
|
|
26
|
+
expect(buildReferenceSearchQuery({ repos: [origin], kind: 'pull', state: 'open', people: 'reviewRequested', text: ' crash ' }))
|
|
27
27
|
.toBe('repo:acme/app is:pr is:open sort:updated-desc review-requested:@me crash');
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
it('combines a state with whose items they are', () => {
|
|
31
|
+
expect(buildReferenceSearchQuery({ repos: [origin], kind: 'pull', state: 'closed', people: 'created', text: '' }))
|
|
32
|
+
.toBe('repo:acme/app is:pr is:closed sort:updated-desc author:@me');
|
|
33
|
+
expect(buildReferenceSearchQuery({ repos: [origin], kind: 'pull', state: 'merged', people: 'assigned', text: '' }))
|
|
34
|
+
.toBe('repo:acme/app is:pr is:merged sort:updated-desc assignee:@me');
|
|
35
|
+
expect(buildReferenceSearchQuery({ repos: [origin], kind: 'issue', state: 'all', people: 'any', text: '' }))
|
|
36
|
+
.toBe('repo:acme/app is:issue sort:updated-desc');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('reads issue-only nonsense as the nearest list: merged as closed, review requests as anyone', () => {
|
|
40
|
+
expect(buildReferenceSearchQuery({ repos: [origin], kind: 'issue', state: 'merged', people: 'reviewRequested', text: '' }))
|
|
41
|
+
.toBe('repo:acme/app is:issue is:closed sort:updated-desc');
|
|
42
|
+
});
|
|
43
|
+
|
|
30
44
|
it('leaves state and sort to the user when their text sets them', () => {
|
|
31
|
-
expect(buildReferenceSearchQuery({ repos: [origin], kind: 'issue',
|
|
45
|
+
expect(buildReferenceSearchQuery({ repos: [origin], kind: 'issue', state: 'open', people: 'assigned', text: 'is:closed sort:created-asc' }))
|
|
32
46
|
.toBe('repo:acme/app is:issue assignee:@me is:closed sort:created-asc');
|
|
33
47
|
});
|
|
34
48
|
|
|
35
|
-
it('reads
|
|
36
|
-
expect(
|
|
37
|
-
expect(
|
|
49
|
+
it('reads unknown values as open and anyone', () => {
|
|
50
|
+
expect(readReferenceState('everything')).toBe('open');
|
|
51
|
+
expect(readReferenceState('merged')).toBe('merged');
|
|
52
|
+
expect(readReferencePeople('everyone')).toBe('any');
|
|
53
|
+
expect(readReferencePeople('created')).toBe('created');
|
|
38
54
|
});
|
|
39
55
|
});
|
|
40
56
|
|
|
@@ -130,11 +146,25 @@ describe('fetchReferenceDetail', () => {
|
|
|
130
146
|
const pull = (reviews = []) => ({
|
|
131
147
|
__typename: 'PullRequest',
|
|
132
148
|
number: 9,
|
|
149
|
+
state: 'OPEN',
|
|
133
150
|
reviewDecision: 'CHANGES_REQUESTED',
|
|
151
|
+
headCommit: { nodes: [{ commit: { statusCheckRollup: { contexts: { nodes: [
|
|
152
|
+
{ __typename: 'CheckRun', databaseId: 1, name: 'test', status: 'COMPLETED', conclusion: 'FAILURE', startedAt: null, checkSuite: { app: { databaseId: 15368 } } },
|
|
153
|
+
{ __typename: 'CheckRun', databaseId: 2, name: 'lint', status: 'COMPLETED', conclusion: 'SUCCESS', startedAt: null, checkSuite: { app: { databaseId: 15368 } } },
|
|
154
|
+
] } } } }] },
|
|
155
|
+
reviewRequests: { nodes: [
|
|
156
|
+
{ requestedReviewer: { __typename: 'User', login: 'hubot', avatarUrl: null } },
|
|
157
|
+
{ requestedReviewer: { __typename: 'Team' } },
|
|
158
|
+
{ requestedReviewer: null },
|
|
159
|
+
] },
|
|
134
160
|
additions: 12,
|
|
135
161
|
deletions: 3,
|
|
136
162
|
changedFiles: 2,
|
|
137
163
|
comments: { totalCount: 1, nodes: [{ author, body: 'first', createdAt: '2026-10-01T10:00:00Z', url: 'u1' }] },
|
|
164
|
+
commits: { totalCount: 1, nodes: [{ commit: {
|
|
165
|
+
oid: 'abc1234', messageHeadline: 'fix: the thing', committedDate: '2026-10-01T08:00:00Z', url: 'https://github.com/acme/app/commit/abc1234',
|
|
166
|
+
author: { name: 'Octo Cat', user: author },
|
|
167
|
+
} }] },
|
|
138
168
|
reviews: { nodes: reviews },
|
|
139
169
|
});
|
|
140
170
|
|
|
@@ -166,9 +196,20 @@ describe('fetchReferenceDetail', () => {
|
|
|
166
196
|
]);
|
|
167
197
|
expect(detail?.pull).toEqual({
|
|
168
198
|
reviewDecision: 'changes_requested',
|
|
199
|
+
reviewers: [{ id: 'hubot', login: 'hubot', avatarUrl: undefined }],
|
|
200
|
+
checks: expect.objectContaining({ state: 'failure', total: 2, success: 1, failure: 1 }),
|
|
169
201
|
additions: 12,
|
|
170
202
|
deletions: 3,
|
|
171
203
|
changedFiles: 2,
|
|
204
|
+
commits: [{
|
|
205
|
+
sha: 'abc1234',
|
|
206
|
+
headline: 'fix: the thing',
|
|
207
|
+
author: { login: 'octo', avatarUrl: 'https://avatars/octo' },
|
|
208
|
+
authorName: 'Octo Cat',
|
|
209
|
+
committedAt: '2026-10-01T08:00:00Z',
|
|
210
|
+
url: 'https://github.com/acme/app/commit/abc1234',
|
|
211
|
+
}],
|
|
212
|
+
commitTotal: 1,
|
|
172
213
|
});
|
|
173
214
|
});
|
|
174
215
|
|
|
@@ -1187,6 +1187,24 @@ describe('gh CLI account consent', () => {
|
|
|
1187
1187
|
await expect(getOctokitForAccountId('github.com#cli:9')).resolves.toMatchObject({ accountId: 'github.com#cli:9', source: 'cli' });
|
|
1188
1188
|
});
|
|
1189
1189
|
|
|
1190
|
+
it('offers the gh account as a Git transport credential only while the user has switched to it', async () => {
|
|
1191
|
+
const fetch = stubGitHubUsers();
|
|
1192
|
+
const { getGitHubCliCredential } = await import('./octokit.js');
|
|
1193
|
+
|
|
1194
|
+
await expect(getGitHubCliCredential('github.com#cli:9')).resolves.toBeNull();
|
|
1195
|
+
expect(usedCliToken(fetch)).toBe(false);
|
|
1196
|
+
|
|
1197
|
+
auth.setGhCliActive(true);
|
|
1198
|
+
await expect(getGitHubCliCredential('github.com#cli:9')).resolves.toMatchObject({
|
|
1199
|
+
credentialId: 'github.com#cli:9', credentialRevision: 1, providerUserId: 'github.com#9',
|
|
1200
|
+
accessToken: 'cli-token', status: 'valid',
|
|
1201
|
+
});
|
|
1202
|
+
await expect(getGitHubCliCredential('github.com#cli:9', 2)).resolves.toBeNull();
|
|
1203
|
+
// gh signed in as somebody else is not the account the request named.
|
|
1204
|
+
await expect(getGitHubCliCredential('github.com#cli:8')).resolves.toBeNull();
|
|
1205
|
+
await expect(getGitHubCliCredential('github.com#9')).resolves.toBeNull();
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1190
1208
|
it('stops using the gh token after switching back to a saved account', async () => {
|
|
1191
1209
|
const fetch = stubGitHubUsers();
|
|
1192
1210
|
const saved = await auth.setGitHubAuth({ accessToken: 'token-a', user: { id: 7, login: 'saved-user' } });
|