@rexvane/piweb 0.3.25 → 0.3.26
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +6 -5
- package/.next/build-manifest.json +4 -4
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +4 -4
- package/.next/react-loadable-manifest.json +14 -26
- package/.next/required-server-files.js +26 -26
- package/.next/required-server-files.json +26 -26
- package/.next/routes-manifest.json +6 -0
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/events/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/tree/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/new/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent/resources/route.js +1 -0
- package/.next/server/app/api/agent/resources/route.js.nft.json +1 -0
- package/.next/server/app/api/agent/resources/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/config/route.js +1 -1
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/files/route.js +1 -1
- package/.next/server/app/api/files/route.js.nft.json +1 -1
- package/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/route.js +1 -1
- package/.next/server/app/api/fs/route.js.nft.json +1 -1
- package/.next/server/app/api/fs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/git/route.js +4 -4
- package/.next/server/app/api/git/route.js.nft.json +1 -1
- package/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/github/route.js +4 -4
- package/.next/server/app/api/github/route.js.nft.json +1 -1
- package/.next/server/app/api/github/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/growth/route.js +1 -1
- package/.next/server/app/api/growth/route.js.nft.json +1 -1
- package/.next/server/app/api/growth/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/health/route.js +1 -1
- package/.next/server/app/api/health/route.js.nft.json +1 -1
- package/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/models/defaults/route.js +1 -1
- package/.next/server/app/api/models/defaults/route.js.nft.json +1 -1
- package/.next/server/app/api/models/defaults/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models/route.js.nft.json +1 -1
- package/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/pi-settings/route.js +1 -1
- package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
- package/.next/server/app/api/pi-settings/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/plugins/route.js.nft.json +1 -1
- package/.next/server/app/api/plugins/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/prompts/route.js +1 -1
- package/.next/server/app/api/prompts/route.js.nft.json +1 -1
- package/.next/server/app/api/prompts/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/remote/route.js +2 -2
- package/.next/server/app/api/remote/route.js.nft.json +1 -1
- package/.next/server/app/api/remote/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/security/route.js +1 -1
- package/.next/server/app/api/security/route.js.nft.json +1 -1
- package/.next/server/app/api/security/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/export/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/import/route.js +2 -2
- package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/import/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/route.js +2 -2
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/update/route.js +2 -2
- package/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/version/route.js.nft.json +1 -1
- package/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/web-auth/route.js +2 -2
- package/.next/server/app/api/web-auth/route.js.nft.json +1 -1
- package/.next/server/app/api/web-auth/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspaces/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/server/app/api/workspaces/route_client-reference-manifest.js +1 -1
- package/.next/server/app/icon.svg/route.js +1 -1
- package/.next/server/app/icon.svg/route.js.nft.json +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +5 -5
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page.js +1 -1
- package/.next/server/app/login/page.js.nft.json +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +4 -4
- package/.next/server/app/login.segments/_full.segment.rsc +4 -4
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +6 -5
- package/.next/server/chunks/{813.js → 1813.js} +1 -1
- package/.next/server/chunks/{430.js → 2430.js} +5 -5
- package/.next/server/chunks/{970.js → 2970.js} +1 -1
- package/.next/server/chunks/3924.js +6 -0
- package/.next/server/chunks/4807.js +1 -0
- package/.next/server/chunks/5393.js +1 -0
- package/.next/server/chunks/5802.js +2 -0
- package/.next/server/chunks/620.js +1 -1
- package/.next/server/chunks/6229.js +1 -0
- package/.next/server/chunks/7496.js +1 -0
- package/.next/server/chunks/7662.js +2 -0
- package/.next/server/chunks/8686.js +1 -0
- package/.next/server/chunks/{593.js → 974.js} +1 -1
- package/.next/server/chunks/{920.js → 9920.js} +1 -1
- package/.next/server/instrumentation.js +1 -1
- package/.next/server/instrumentation.js.nft.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/middleware.js +5 -5
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/preview-props.json +3 -3
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/server/webpack-runtime.js +1 -1
- package/.next/static/QJCtqgDKzGS2BdKABd9ch/_buildManifest.js +1 -0
- package/.next/static/chunks/2577.86c8538fbf05a8b2.js +1 -0
- package/.next/static/chunks/2716.fa561519675a3ff1.js +1 -0
- package/.next/static/chunks/2906.8a0e09bb47605cf1.js +1 -0
- package/.next/static/chunks/4673.ea36e55005c0f82f.js +1 -0
- package/.next/static/chunks/4686-4dc565817a160351.js +1 -0
- package/.next/static/chunks/4778.0d2c5d75b791d352.js +1 -0
- package/.next/static/chunks/app/_global-error/page-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/_not-found/page-2d40d1d7d916666a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/resources/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/config/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/files/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/fs/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/git/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/github/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/growth/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/health/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/models/defaults/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/models/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/remote/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/security/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/skills/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/update/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/version/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/{layout-7f64fa67ea6ce980.js → layout-50d06e2078d6ce76.js} +1 -1
- package/.next/static/chunks/app/login/{page-00dbd2f395b364d5.js → page-5e276a1eeb8867df.js} +1 -1
- package/.next/static/chunks/app/page-71f2d3b3ed864b3d.js +1 -0
- package/.next/static/chunks/main-5403f0764579e5fe.js +5 -0
- package/.next/static/chunks/{main-app-56863ace24eb152f.js → main-app-e9b53f0e1c6591b2.js} +1 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-a7e260f8cc2582d7.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/webpack-ffc09b5fcc8aaa6d.js +1 -0
- package/.next/static/css/7e3caf797e4cb19a.css +1 -0
- package/.next/types/app/api/agent/[id]/events/route.ts +1 -1
- package/.next/types/app/api/agent/[id]/route.ts +1 -1
- package/.next/types/app/api/agent/[id]/tree/route.ts +1 -1
- package/.next/types/app/api/agent/new/route.ts +1 -1
- package/.next/types/app/api/agent/resources/route.ts +355 -0
- package/.next/types/app/api/config/route.ts +1 -1
- package/.next/types/app/api/files/route.ts +1 -1
- package/.next/types/app/api/fs/route.ts +1 -1
- package/.next/types/app/api/git/route.ts +1 -1
- package/.next/types/app/api/github/route.ts +1 -1
- package/.next/types/app/api/growth/route.ts +1 -1
- package/.next/types/app/api/health/route.ts +1 -1
- package/.next/types/app/api/models/defaults/route.ts +1 -1
- package/.next/types/app/api/models/route.ts +1 -1
- package/.next/types/app/api/pi-settings/route.ts +1 -1
- package/.next/types/app/api/plugins/route.ts +1 -1
- package/.next/types/app/api/prompts/route.ts +1 -1
- package/.next/types/app/api/remote/route.ts +1 -1
- package/.next/types/app/api/security/route.ts +1 -1
- package/.next/types/app/api/sessions/[id]/export/route.ts +1 -1
- package/.next/types/app/api/sessions/[id]/route.ts +1 -1
- package/.next/types/app/api/sessions/import/route.ts +1 -1
- package/.next/types/app/api/sessions/route.ts +1 -1
- package/.next/types/app/api/skills/route.ts +1 -1
- package/.next/types/app/api/update/route.ts +1 -1
- package/.next/types/app/api/version/route.ts +1 -1
- package/.next/types/app/api/web-auth/route.ts +1 -1
- package/.next/types/app/api/workspaces/route.ts +1 -1
- package/.next/types/app/layout.ts +1 -1
- package/.next/types/app/login/page.ts +1 -1
- package/.next/types/app/page.ts +1 -1
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +10 -0
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/bin/pi.js +0 -0
- package/bin/piweb.js +0 -0
- package/package.json +1 -1
- package/scripts/build-output.mjs +0 -0
- package/scripts/dev.mjs +0 -0
- package/scripts/entrypoint.mjs +0 -0
- package/scripts/install-build.mjs +0 -0
- package/scripts/launcher.mjs +0 -0
- package/scripts/pick-folder.ps1 +0 -0
- package/scripts/postinstall.mjs +0 -0
- package/scripts/process-runner.mjs +0 -0
- package/scripts/release.mjs +0 -0
- package/scripts/sync-file-icons.mjs +0 -0
- package/scripts/sync-provider-icons.mjs +0 -0
- package/src/app/api/agent/resources/route.ts +18 -0
- package/src/app/api/remote/route.ts +5 -7
- package/src/app/api/sessions/[id]/export/route.ts +3 -1
- package/src/app/globals.css +16 -0
- package/src/components/AppShell.tsx +68 -21
- package/src/components/ChatInput.tsx +98 -27
- package/src/components/ChatWindow.tsx +68 -11
- package/src/components/ContextMeter.tsx +24 -4
- package/src/components/ExtensionUI.tsx +9 -3
- package/src/components/FileViewer.tsx +24 -21
- package/src/components/GitPanel.tsx +449 -449
- package/src/components/GrowthTree.tsx +3 -1
- package/src/components/ImageViewer.tsx +72 -0
- package/src/components/MarkdownBlock.tsx +6 -3
- package/src/components/PreviewImage.tsx +33 -0
- package/src/components/ProjectPanel.tsx +5 -3
- package/src/components/PromptPanel.tsx +322 -313
- package/src/components/RemoteSection.tsx +2 -1
- package/src/components/SessionSidebar.tsx +22 -4
- package/src/components/SettingsPanel.tsx +2654 -2639
- package/src/components/composer/ComposerImages.tsx +1 -79
- package/src/hooks/usePiWeb.ts +47 -2
- package/src/i18n.tsx +24 -0
- package/src/lib/agent-manager.ts +275 -18
- package/src/lib/chat/skill-expansion.ts +10 -0
- package/src/lib/extension-ui.ts +25 -1
- package/src/lib/git-service.ts +291 -289
- package/src/lib/github-identity.ts +245 -245
- package/src/lib/growth-turns.ts +3 -1
- package/src/lib/session-reader.ts +25 -13
- package/src/lib/session-retention.ts +11 -8
- package/src/lib/slash-command-token.ts +14 -0
- package/src/lib/trajectory.ts +17 -6
- package/src/lib/types.ts +24 -2
- package/src/lib/workspace-store.ts +36 -1
- package/.next/diagnostics/build-diagnostics.json +0 -6
- package/.next/diagnostics/framework.json +0 -1
- package/.next/server/chunks/229.js +0 -1
- package/.next/server/chunks/393.js +0 -1
- package/.next/server/chunks/612.js +0 -1
- package/.next/server/chunks/662.js +0 -2
- package/.next/server/chunks/686.js +0 -1
- package/.next/server/chunks/802.js +0 -2
- package/.next/server/chunks/807.js +0 -1
- package/.next/server/chunks/924.js +0 -6
- package/.next/static/4snUelZTEIiQYCkB5nTW7/_buildManifest.js +0 -1
- package/.next/static/chunks/1966.1560d0f43ac9a41a.js +0 -1
- package/.next/static/chunks/2577.eebf2cf291a811ad.js +0 -1
- package/.next/static/chunks/2716.a5228b0eeedbe4c8.js +0 -1
- package/.next/static/chunks/2906.546a64377e12dfbc.js +0 -1
- package/.next/static/chunks/3899.55a0943bf5e7fec7.js +0 -1
- package/.next/static/chunks/4673.8eff281cb63e2799.js +0 -1
- package/.next/static/chunks/4686-f03275819a719fd8.js +0 -1
- package/.next/static/chunks/4778.2dae0c6a9e396326.js +0 -1
- package/.next/static/chunks/app/_global-error/page-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/_not-found/page-851dc3883a1937aa.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/config/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/files/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/fs/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/git/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/github/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/growth/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/health/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/models/defaults/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/models/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/remote/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/security/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/import/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/skills/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/update/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/version/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/page-0159987118376f42.js +0 -1
- package/.next/static/chunks/main-22ccebc2da4d291e.js +0 -5
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-c37b7707077f50ce.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/webpack-c1b43c269867b64f.js +0 -1
- package/.next/static/css/93ed3daa6f3fbc35.css +0 -1
- package/.next/trace +0 -58
- package/.next/trace-build +0 -1
- /package/.next/static/{4snUelZTEIiQYCkB5nTW7 → QJCtqgDKzGS2BdKABd9ch}/_ssgManifest.js +0 -0
package/src/lib/git-service.ts
CHANGED
|
@@ -1,289 +1,291 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* git-service:会话工作区的 Git 状态与差异(详情栏「Git」面板)。
|
|
3
|
-
* 只读:porcelain v2 状态、最近提交、单文件差异、单次提交内容;不做任何写操作。
|
|
4
|
-
* 所有 git 命令以仓库根为 cwd 运行,因为 porcelain 输出的路径都相对仓库根。
|
|
5
|
-
*/
|
|
6
|
-
import { execFile } from "node:child_process";
|
|
7
|
-
import fs from "node:fs/promises";
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import { promisify } from "node:util";
|
|
10
|
-
import { parseNumstat, resolveGitBin } from "./growth-service";
|
|
11
|
-
import { BoundaryError, isPathInside, resolveWorkspacePath } from "./path-security";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (letter === "
|
|
75
|
-
if (letter === "
|
|
76
|
-
return "
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
if (!
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
let
|
|
100
|
-
let
|
|
101
|
-
let
|
|
102
|
-
let
|
|
103
|
-
let
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (line
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (type === "
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.
|
|
169
|
-
.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
let
|
|
207
|
-
let
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (!
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
if (!
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
1
|
+
/**
|
|
2
|
+
* git-service:会话工作区的 Git 状态与差异(详情栏「Git」面板)。
|
|
3
|
+
* 只读:porcelain v2 状态、最近提交、单文件差异、单次提交内容;不做任何写操作。
|
|
4
|
+
* 所有 git 命令以仓库根为 cwd 运行,因为 porcelain 输出的路径都相对仓库根。
|
|
5
|
+
*/
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
import { parseNumstat, resolveGitBin } from "./growth-service";
|
|
11
|
+
import { BoundaryError, isPathInside, resolveWorkspacePath } from "./path-security";
|
|
12
|
+
import { utf8Prefix } from "./text-sanitize";
|
|
13
|
+
|
|
14
|
+
const exec = promisify(execFile);
|
|
15
|
+
|
|
16
|
+
export interface GitFileEntry {
|
|
17
|
+
path: string;
|
|
18
|
+
/** 暂存区状态字母(" " 表示无) */
|
|
19
|
+
indexStatus: string;
|
|
20
|
+
/** 工作区状态字母(" " 表示无) */
|
|
21
|
+
workStatus: string;
|
|
22
|
+
kind: "modified" | "added" | "deleted" | "renamed" | "untracked" | "conflict";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface GitCommit {
|
|
26
|
+
hash: string;
|
|
27
|
+
subject: string;
|
|
28
|
+
author: string;
|
|
29
|
+
/** git 的相对时间("2 hours ago") */
|
|
30
|
+
relative: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GitInfo {
|
|
34
|
+
/** 机器上找不到 git 命令 */
|
|
35
|
+
available: boolean;
|
|
36
|
+
isRepo: boolean;
|
|
37
|
+
/** 仓库根(用于把面板里的相对路径映射回文件面板) */
|
|
38
|
+
root: string | null;
|
|
39
|
+
branch: string | null;
|
|
40
|
+
upstream: string | null;
|
|
41
|
+
ahead: number;
|
|
42
|
+
behind: number;
|
|
43
|
+
detached: boolean;
|
|
44
|
+
files: GitFileEntry[];
|
|
45
|
+
/** 状态读取失败时文件变更未知;不能把空 files 解读为工作区干净。 */
|
|
46
|
+
statusError?: string;
|
|
47
|
+
commits: GitCommit[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const EMPTY: GitInfo = {
|
|
51
|
+
available: true,
|
|
52
|
+
isRepo: false,
|
|
53
|
+
root: null,
|
|
54
|
+
branch: null,
|
|
55
|
+
upstream: null,
|
|
56
|
+
ahead: 0,
|
|
57
|
+
behind: 0,
|
|
58
|
+
detached: false,
|
|
59
|
+
files: [],
|
|
60
|
+
commits: [],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** 单次差异输出上限:超过则截断并标记(详情栏不是完整的 diff 工具) */
|
|
64
|
+
const MAX_DIFF_BYTES = 400 * 1024;
|
|
65
|
+
|
|
66
|
+
/** 与生长记录共用解析出的 git(macOS 从 GUI / launchd 启动时 PATH 里常常没有 Homebrew 的 git) */
|
|
67
|
+
async function runGit(cwd: string, args: string[], maxBuffer = 4 * 1024 * 1024): Promise<string> {
|
|
68
|
+
return exec(await resolveGitBin(), args, { cwd, timeout: 15_000, maxBuffer, windowsHide: true, env: { ...process.env, GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" } }).then((r) => r.stdout);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function kindOf(x: string, y: string): GitFileEntry["kind"] {
|
|
72
|
+
if (x === "U" || y === "U" || (x === "A" && y === "A") || (x === "D" && y === "D")) return "conflict";
|
|
73
|
+
const letter = x !== "." ? x : y;
|
|
74
|
+
if (letter === "A") return "added";
|
|
75
|
+
if (letter === "D") return "deleted";
|
|
76
|
+
if (letter === "R" || letter === "C") return "renamed";
|
|
77
|
+
return "modified";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 解析仓库根(GitHub 身份解析也要用,所以导出) */
|
|
81
|
+
export async function repoRoot(cwd: string): Promise<{ available: boolean; root: string | null }> {
|
|
82
|
+
try {
|
|
83
|
+
const out = (await runGit(cwd, ["rev-parse", "--show-toplevel"])).trim();
|
|
84
|
+
return { available: true, root: out ? path.resolve(out) : null };
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
87
|
+
if (code === "ENOENT") return { available: false, root: null };
|
|
88
|
+
// exit 128:不是仓库
|
|
89
|
+
return { available: true, root: null };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function gitInfo(cwdValue: unknown): Promise<GitInfo> {
|
|
94
|
+
const cwd = await resolveWorkspacePath(cwdValue);
|
|
95
|
+
const { available, root } = await repoRoot(cwd);
|
|
96
|
+
if (!available) return { ...EMPTY, available: false };
|
|
97
|
+
if (!root) return EMPTY;
|
|
98
|
+
|
|
99
|
+
let branch: string | null = null;
|
|
100
|
+
let upstream: string | null = null;
|
|
101
|
+
let ahead = 0;
|
|
102
|
+
let behind = 0;
|
|
103
|
+
let detached = false;
|
|
104
|
+
let statusError: string | undefined;
|
|
105
|
+
const files: GitFileEntry[] = [];
|
|
106
|
+
try {
|
|
107
|
+
// porcelain v2 是结构化输出,不依赖 "No branch" 这类措辞;-z 让含空格/非 ASCII 的路径也稳定
|
|
108
|
+
const status = await runGit(root, ["status", "--porcelain=v2", "--branch", "-z"]);
|
|
109
|
+
const records = status.split("\0");
|
|
110
|
+
for (let i = 0; i < records.length; i += 1) {
|
|
111
|
+
const line = records[i];
|
|
112
|
+
if (!line) continue;
|
|
113
|
+
if (line.startsWith("# branch.head ")) {
|
|
114
|
+
const head = line.slice("# branch.head ".length);
|
|
115
|
+
if (head === "(detached)") detached = true;
|
|
116
|
+
else branch = head;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (line.startsWith("# branch.upstream ")) {
|
|
120
|
+
upstream = line.slice("# branch.upstream ".length);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (line.startsWith("# branch.ab ")) {
|
|
124
|
+
const m = line.match(/\+(\d+) -(\d+)/);
|
|
125
|
+
if (m) {
|
|
126
|
+
ahead = Number(m[1]);
|
|
127
|
+
behind = Number(m[2]);
|
|
128
|
+
}
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (line.startsWith("# ")) continue;
|
|
132
|
+
const type = line[0];
|
|
133
|
+
if (type === "?") {
|
|
134
|
+
files.push({ path: line.slice(2), indexStatus: " ", workStatus: "?", kind: "untracked" });
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (type === "!") continue;
|
|
138
|
+
if (type === "1" || type === "2" || type === "u") {
|
|
139
|
+
const parts = line.split(" ");
|
|
140
|
+
const xy = parts[1] ?? "..";
|
|
141
|
+
const x = xy[0] ?? ".";
|
|
142
|
+
const y = xy[1] ?? ".";
|
|
143
|
+
// 1: <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
|
|
144
|
+
// 2: <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path> (NUL 后跟原路径,占下一条记录)
|
|
145
|
+
// u: <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
|
|
146
|
+
const pathIndex = type === "1" ? 8 : type === "2" ? 9 : 10;
|
|
147
|
+
const filePath = parts.slice(pathIndex).join(" ");
|
|
148
|
+
if (type === "2") i += 1; // 跳过 rename 的原路径记录
|
|
149
|
+
if (!filePath) continue;
|
|
150
|
+
files.push({
|
|
151
|
+
path: filePath,
|
|
152
|
+
indexStatus: x === "." ? " " : x,
|
|
153
|
+
workStatus: y === "." ? " " : y,
|
|
154
|
+
kind: kindOf(x, y),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
// 分支与提交可继续展示,但文件状态未知,绝不能回退为「工作区干净」。
|
|
160
|
+
const stderr = (error as { stderr?: unknown })?.stderr;
|
|
161
|
+
statusError = (typeof stderr === "string" ? stderr.trim() : error instanceof Error ? error.message : String(error)) || "git status failed";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let commits: GitCommit[] = [];
|
|
165
|
+
try {
|
|
166
|
+
const log = await runGit(root, ["log", "-10", "--pretty=%h%x1f%s%x1f%an%x1f%cr"]);
|
|
167
|
+
commits = log
|
|
168
|
+
.split("\n")
|
|
169
|
+
.filter(Boolean)
|
|
170
|
+
.map((line) => {
|
|
171
|
+
const [hash, subject, author, relative] = line.split("\x1f");
|
|
172
|
+
return { hash, subject: subject ?? "", author: author ?? "", relative: relative ?? "" };
|
|
173
|
+
});
|
|
174
|
+
} catch {
|
|
175
|
+
// 空仓库没有提交
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (branch === null && !detached) {
|
|
179
|
+
try {
|
|
180
|
+
branch = (await runGit(root, ["rev-parse", "--abbrev-ref", "HEAD"])).trim() || null;
|
|
181
|
+
} catch {
|
|
182
|
+
/* keep null */
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { available: true, isRepo: true, root, branch, upstream, ahead, behind, detached, files, commits, ...(statusError ? { statusError } : {}) };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface GitDiffResult {
|
|
189
|
+
patch: string;
|
|
190
|
+
truncated: boolean;
|
|
191
|
+
binary: boolean;
|
|
192
|
+
/** 完整 diff 的 Git numstat,独立于用于展示的 patch 截断。二进制不计行数。 */
|
|
193
|
+
stats: { add: number; del: number };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function fullDiffStats(root: string, args: string[], noIndex = false): Promise<{ stats: GitDiffResult["stats"]; binary: boolean }> {
|
|
197
|
+
let raw: string;
|
|
198
|
+
try {
|
|
199
|
+
raw = await runGit(root, args);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
// --no-index 的退出码 1 表示有差异,stdout 仍是有效的 numstat。
|
|
202
|
+
const result = error as { code?: number; stdout?: string };
|
|
203
|
+
if (!noIndex || result.code !== 1 || typeof result.stdout !== "string" || !result.stdout) throw error;
|
|
204
|
+
raw = result.stdout;
|
|
205
|
+
}
|
|
206
|
+
let add = 0;
|
|
207
|
+
let del = 0;
|
|
208
|
+
let binary = false;
|
|
209
|
+
for (const entry of parseNumstat(raw).values()) {
|
|
210
|
+
add += entry.add;
|
|
211
|
+
del += entry.del;
|
|
212
|
+
binary ||= entry.binary;
|
|
213
|
+
}
|
|
214
|
+
return { stats: { add, del }, binary };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** 按字节截断时退到字符边界:直接 subarray 会把多字节字符切成半个,patch 末尾多出一个 U+FFFD */
|
|
218
|
+
function truncate(patch: string, stats: GitDiffResult["stats"]): GitDiffResult {
|
|
219
|
+
if (Buffer.byteLength(patch) <= MAX_DIFF_BYTES) return { patch, truncated: false, binary: false, stats };
|
|
220
|
+
return { patch: utf8Prefix(Buffer.from(patch), MAX_DIFF_BYTES).toString("utf8"), truncated: true, binary: false, stats };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function assertRepoRelative(root: string, relPath: unknown): string {
|
|
224
|
+
if (typeof relPath !== "string" || !relPath.trim()) throw new BoundaryError("missing path");
|
|
225
|
+
const abs = path.resolve(root, relPath);
|
|
226
|
+
if (!isPathInside(root, abs)) throw new BoundaryError("path escapes the repository");
|
|
227
|
+
return abs;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** 单文件差异:staged(索引 vs HEAD)、worktree(工作区 vs 索引)、untracked(整文件视为新增) */
|
|
231
|
+
export async function gitDiff(cwdValue: unknown, relPath: unknown, mode: unknown): Promise<GitDiffResult> {
|
|
232
|
+
const cwd = await resolveWorkspacePath(cwdValue);
|
|
233
|
+
const { available, root } = await repoRoot(cwd);
|
|
234
|
+
if (!available || !root) throw new BoundaryError("not a git repository");
|
|
235
|
+
const abs = assertRepoRelative(root, relPath);
|
|
236
|
+
const rel = path.relative(root, abs).replace(/\\/g, "/");
|
|
237
|
+
if (mode === "untracked") {
|
|
238
|
+
// 未跟踪文件:合成一个全新增的 patch,让前端用同一套 diff 视图展示
|
|
239
|
+
const realRoot = await fs.realpath(root);
|
|
240
|
+
const realFile = await fs.realpath(abs).catch(() => { throw new BoundaryError("file not found"); });
|
|
241
|
+
if (!isPathInside(realRoot, realFile)) throw new BoundaryError("path escapes the repository");
|
|
242
|
+
const stat = await fs.stat(realFile).catch(() => null);
|
|
243
|
+
if (!stat?.isFile()) throw new BoundaryError("file not found");
|
|
244
|
+
const { stats, binary } = await fullDiffStats(root, ["diff", "--no-index", "--no-ext-diff", "--no-textconv", "--numstat", "-z", "--", "/dev/null", realFile], true);
|
|
245
|
+
if (binary) return { patch: "", truncated: false, binary: true, stats };
|
|
246
|
+
const buffer = Buffer.alloc(Math.min(stat.size, MAX_DIFF_BYTES + 1));
|
|
247
|
+
const handle = await fs.open(realFile, "r");
|
|
248
|
+
let bytesRead = 0;
|
|
249
|
+
try {
|
|
250
|
+
while (bytesRead < buffer.length) {
|
|
251
|
+
const read = await handle.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead);
|
|
252
|
+
if (read.bytesRead === 0) break;
|
|
253
|
+
bytesRead += read.bytesRead;
|
|
254
|
+
}
|
|
255
|
+
} finally {
|
|
256
|
+
await handle.close();
|
|
257
|
+
}
|
|
258
|
+
const head = buffer.subarray(0, bytesRead);
|
|
259
|
+
const text = utf8Prefix(head, MAX_DIFF_BYTES).toString("utf8").replace(/\r\n/g, "\n");
|
|
260
|
+
const lines = text.split("\n");
|
|
261
|
+
if (lines[lines.length - 1] === "") lines.pop();
|
|
262
|
+
const body = lines.map((l) => `+${l}`).join("\n");
|
|
263
|
+
return {
|
|
264
|
+
patch: `--- /dev/null\n+++ b/${rel}\n@@ -0,0 +1,${lines.length} @@\n${body}\n`,
|
|
265
|
+
truncated: stat.size > MAX_DIFF_BYTES,
|
|
266
|
+
binary: false,
|
|
267
|
+
stats,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const scope = mode === "staged" ? ["--cached"] : [];
|
|
271
|
+
const [out, counts] = await Promise.all([
|
|
272
|
+
runGit(root, ["diff", ...scope, "--no-color", "--no-ext-diff", "--no-textconv", "--", rel], 8 * 1024 * 1024),
|
|
273
|
+
fullDiffStats(root, ["diff", ...scope, "--no-ext-diff", "--no-textconv", "--numstat", "-z", "--", rel]),
|
|
274
|
+
]);
|
|
275
|
+
if (counts.binary) return { patch: "", truncated: false, binary: true, stats: counts.stats };
|
|
276
|
+
return truncate(out, counts.stats);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** 单次提交的内容(含各文件 patch) */
|
|
280
|
+
export async function gitShow(cwdValue: unknown, hash: unknown): Promise<GitDiffResult & { title: string }> {
|
|
281
|
+
const cwd = await resolveWorkspacePath(cwdValue);
|
|
282
|
+
const { available, root } = await repoRoot(cwd);
|
|
283
|
+
if (!available || !root) throw new BoundaryError("not a git repository");
|
|
284
|
+
if (typeof hash !== "string" || !/^[0-9a-f]{4,40}$/i.test(hash)) throw new BoundaryError("invalid commit hash");
|
|
285
|
+
const title = (await runGit(root, ["log", "-1", "--pretty=%h %s%n%an · %cr", hash])).trim();
|
|
286
|
+
const [out, counts] = await Promise.all([
|
|
287
|
+
runGit(root, ["show", "--no-color", "--no-ext-diff", "--no-textconv", "--format=", "--patch", hash], 8 * 1024 * 1024),
|
|
288
|
+
fullDiffStats(root, ["show", "--no-ext-diff", "--no-textconv", "--format=", "--numstat", "-z", hash]),
|
|
289
|
+
]);
|
|
290
|
+
return { title, ...truncate(out, counts.stats), binary: counts.binary && !out.includes("@@ ") };
|
|
291
|
+
}
|