@hienlh/ppm 0.9.85 → 0.9.87
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/260415-0932-git-graph-stash-rebase-conflicts/reports/code-reviewer-260415-1020-stash-rebase-conflicts.md +288 -0
- package/260415-0932-git-graph-stash-rebase-conflicts/reports/tester-260415-1020-build-check.md +117 -0
- package/260415-1150-ext-silent-failure-debugging/reports/code-reviewer-260415-1159-ext-error-reporting-review.md +205 -0
- package/260415-1150-ext-silent-failure-debugging/reports/docs-manager-260415-1206-ext-error-reporting.md +99 -0
- package/260415-1150-ext-silent-failure-debugging/reports/tester-260415-1159-extension-error-reporting.md +174 -0
- package/CHANGELOG.md +24 -0
- package/dist/web/assets/{_basePickBy-D-bUmjma.js → _basePickBy-Bj0dI1ei.js} +1 -1
- package/dist/web/assets/{_baseUniq-BnXXIfRB.js → _baseUniq-CyzdZeQH.js} +1 -1
- package/dist/web/assets/ai-settings-section-Bo9lCaTd.js +1 -0
- package/dist/web/assets/{api-settings-Qi2xRiHa.js → api-settings-CUxg9RE5.js} +1 -1
- package/dist/web/assets/{arc-DB9vXGzd.js → arc-CxgHJ7Z4.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-DDFO_NKq.js +1 -0
- package/dist/web/assets/{architectureDiagram-2XIMDMQ5-BBV25747.js → architectureDiagram-2XIMDMQ5-D16OotsC.js} +1 -1
- package/dist/web/assets/arrow-up-I9-21gkR.js +1 -0
- package/dist/web/assets/{blockDiagram-WCTKOSBZ-BOTnY2Lq.js → blockDiagram-WCTKOSBZ-Ct57Wtfk.js} +1 -1
- package/dist/web/assets/{c4Diagram-IC4MRINW-D7QAUdHD.js → c4Diagram-IC4MRINW-BIymcNsg.js} +1 -1
- package/dist/web/assets/channel-wumTB1if.js +1 -0
- package/dist/web/assets/chat-tab-R4gKsnxD.js +10 -0
- package/dist/web/assets/chevron-right-DY_wImxB.js +1 -0
- package/dist/web/assets/{chunk-4BX2VUAB-BnOVw77D.js → chunk-4BX2VUAB-CENmY7Kw.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-BftA8DxR.js → chunk-55IACEB6-DhZGI1l3.js} +1 -1
- package/dist/web/assets/{chunk-7E7YKBS2-B0vnP8v3.js → chunk-7E7YKBS2-DZcnC7Ow.js} +1 -1
- package/dist/web/assets/{chunk-7R4GIKGN-Czlaj26D.js → chunk-7R4GIKGN-y8bfHEy-.js} +2 -2
- package/dist/web/assets/{chunk-C72U2L5F-DpEbDtMo.js → chunk-C72U2L5F-BHPkfQj2.js} +1 -1
- package/dist/web/assets/{chunk-EGIJ26TM-BWXe6lkx.js → chunk-EGIJ26TM-nant2LXl.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-DspqhPfk.js → chunk-FMBD7UC4-Bog4cpN-.js} +1 -1
- package/dist/web/assets/{chunk-GEFDOKGD-D6HHRbYk.js → chunk-GEFDOKGD-86LFbsAC.js} +1 -1
- package/dist/web/assets/chunk-GLR3WWYH-Re-5eSlQ.js +2 -0
- package/dist/web/assets/chunk-HHEYEP7N-C45i5G_3.js +1 -0
- package/dist/web/assets/{chunk-JSJVCQXG-BC8wnMwf.js → chunk-JSJVCQXG-23eG9mgt.js} +1 -1
- package/dist/web/assets/{chunk-KX2RTZJC-D3VDtyvX.js → chunk-KX2RTZJC-CHj8TnTB.js} +1 -1
- package/dist/web/assets/{chunk-KYZI473N-Z-NBw_HS.js → chunk-KYZI473N-gqRLpJ4w.js} +1 -1
- package/dist/web/assets/{chunk-L3YUKLVL--RGkEh__.js → chunk-L3YUKLVL-DnSMmNFC.js} +1 -1
- package/dist/web/assets/{chunk-MX3YWQON-2B76t_Kx.js → chunk-MX3YWQON-B6g1ZH9X.js} +1 -1
- package/dist/web/assets/{chunk-NQ4KR5QH-BekY3tEi.js → chunk-NQ4KR5QH-DX32345Y.js} +1 -1
- package/dist/web/assets/{chunk-O4XLMI2P-2CJLfx_1.js → chunk-O4XLMI2P-Vp_V4P-b.js} +1 -1
- package/dist/web/assets/{chunk-OZEHJAEY-sug_L09P.js → chunk-OZEHJAEY-lKq2SWjA.js} +1 -1
- package/dist/web/assets/{chunk-PQ6SQG4A-_fwPRLQy.js → chunk-PQ6SQG4A-Bik13fTV.js} +1 -1
- package/dist/web/assets/{chunk-PU5JKC2W-BUaTFJVQ.js → chunk-PU5JKC2W-DD95Rx35.js} +1 -1
- package/dist/web/assets/chunk-QZHKN3VN-N3VXx1VH.js +1 -0
- package/dist/web/assets/{chunk-R5LLSJPH-C37xW0vj.js → chunk-R5LLSJPH-dRhXRnrb.js} +1 -1
- package/dist/web/assets/{chunk-WL4C6EOR-CCkt_MT6.js → chunk-WL4C6EOR-B1iIvLOG.js} +1 -1
- package/dist/web/assets/{chunk-XIRO2GV7-Dz2LBq7Y.js → chunk-XIRO2GV7-DZBoNl1_.js} +1 -1
- package/dist/web/assets/{chunk-XPW4576I-DenTbBuj.js → chunk-XPW4576I-CgLyyW03.js} +1 -1
- package/dist/web/assets/{chunk-XZSTWKYB-Dbp1nUSQ.js → chunk-XZSTWKYB-DjV8xl5A.js} +1 -1
- package/dist/web/assets/{chunk-YBOYWFTD-3OTKowjE.js → chunk-YBOYWFTD-D_ILLe6_.js} +1 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-mr-Cb1me.js +1 -0
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-BKe8_uda.js +1 -0
- package/dist/web/assets/clone--z5KLAuR.js +1 -0
- package/dist/web/assets/code-editor-Br0vzTOy.js +8 -0
- package/dist/web/assets/columns-2-IeETSfON.js +1 -0
- package/dist/web/assets/conflict-editor-BPgCjnNz.js +19 -0
- package/dist/web/assets/{cose-bilkent-S5V4N54A-MbmGZnt0.js → cose-bilkent-S5V4N54A-BGNPFv3x.js} +1 -1
- package/dist/web/assets/{csv-preview-uZ_7b8I7.js → csv-preview-BZRICDP0.js} +2 -2
- package/dist/web/assets/{dagre-CPhI6v-K.js → dagre-CkhlMHnx.js} +1 -1
- package/dist/web/assets/{dagre-KLK3FWXG-CmSE-oNj.js → dagre-KLK3FWXG-Cnp996VG.js} +1 -1
- package/dist/web/assets/database-CgTomMxt.js +1 -0
- package/dist/web/assets/{database-viewer-5xljX0JI.js → database-viewer-DaUoQ-oR.js} +2 -2
- package/dist/web/assets/{diagram-E7M64L7V-B5XG3ZT7.js → diagram-E7M64L7V-BZF0tSOr.js} +1 -1
- package/dist/web/assets/{diagram-IFDJBPK2-BsP248aX.js → diagram-IFDJBPK2-nUcO8sN8.js} +1 -1
- package/dist/web/assets/{diagram-P4PSJMXO-Cna3408N.js → diagram-P4PSJMXO-CW0eCkwC.js} +1 -1
- package/dist/web/assets/diff-viewer-BzvK3gAE.js +4 -0
- package/dist/web/assets/dist-CM0oD8tQ.js +1 -0
- package/dist/web/assets/{erDiagram-INFDFZHY-B7SgktiR.js → erDiagram-INFDFZHY-DSkriYZ9.js} +1 -1
- package/dist/web/assets/extension-webview-CGepEw-b.js +3 -0
- package/dist/web/assets/{flowDiagram-PKNHOUZH-FOYZZ1OB.js → flowDiagram-PKNHOUZH-CFYAfZBx.js} +1 -1
- package/dist/web/assets/{ganttDiagram-A5KZAMGK-CnHVYh9v.js → ganttDiagram-A5KZAMGK-KSn4XAU4.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-OkvBPi6H.js +1 -0
- package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-0G9XxZay.js → gitGraphDiagram-K3NZZRJ6-BMgjjVys.js} +1 -1
- package/dist/web/assets/{graphlib-CNiBwlg_.js → graphlib-BWe1iK_s.js} +1 -1
- package/dist/web/assets/index-CKsEzQ4f.js +26 -0
- package/dist/web/assets/index-Chf0otez.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-BDU2_bYD.js +1 -0
- package/dist/web/assets/infoDiagram-LFFYTUFH-Diq4Cyc3.js +2 -0
- package/dist/web/assets/input-BHj0veau.js +45 -0
- package/dist/web/assets/{isEmpty-CcCb5n2-.js → isEmpty-BfLnxq-B.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-PHBUUO56-D4QCzh5J.js → ishikawaDiagram-PHBUUO56-CiVEvp8o.js} +1 -1
- package/dist/web/assets/{journeyDiagram-4ABVD52K-CnHYNfKW.js → journeyDiagram-4ABVD52K-CG_v5Aho.js} +1 -1
- package/dist/web/assets/jsx-runtime-BRW_vwa9.js +1 -0
- package/dist/web/assets/{kanban-definition-K7BYSVSG-Bh_g3EVu.js → kanban-definition-K7BYSVSG-miB0-_Zq.js} +1 -1
- package/dist/web/assets/keybindings-store-D5zgHod8.js +1 -0
- package/dist/web/assets/{line-6d3eBADm.js → line-CSuSrJ9J.js} +1 -1
- package/dist/web/assets/{linear-cA_2lQy7.js → linear-DFN_MPsw.js} +1 -1
- package/dist/web/assets/{markdown-renderer-CZ07F7T6.js → markdown-renderer-DSYnGywb.js} +6 -6
- package/dist/web/assets/{mermaid-parser.core-C3kd7JXM.js → mermaid-parser.core-CFdP1Z5_.js} +2 -2
- package/dist/web/assets/{mindmap-definition-YRQLILUH-CYiUwhr_.js → mindmap-definition-YRQLILUH-pYPWwASE.js} +1 -1
- package/dist/web/assets/{ordinal-XHK5vIzZ.js → ordinal-DpFn432U.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-BwpIpYB3.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-BPgAfmes.js +1 -0
- package/dist/web/assets/{pieDiagram-SKSYHLDU-D0S7jeZA.js → pieDiagram-SKSYHLDU-Dovdlvhu.js} +1 -1
- package/dist/web/assets/plus-DQGIb4mQ.js +1 -0
- package/dist/web/assets/port-forwarding-tab-vmqDKmk2.js +1 -0
- package/dist/web/assets/{postgres-viewer-RldlAO_m.js → postgres-viewer-0lIAosrr.js} +3 -3
- package/dist/web/assets/{quadrantDiagram-337W2JSQ-0hNP63hW.js → quadrantDiagram-337W2JSQ-TXe6cU_F.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-TqxBkWx-.js +1 -0
- package/dist/web/assets/refresh-cw-Clk8fdUD.js +1 -0
- package/dist/web/assets/{requirementDiagram-Z7DCOOCP-BVnmqFbL.js → requirementDiagram-Z7DCOOCP-CuiiuGS9.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DVkYdCJb.js → sankeyDiagram-WA2Y5GQK-BbRmhv0t.js} +1 -1
- package/dist/web/assets/scroll-area-BpXCNme3.js +1 -0
- package/dist/web/assets/{sequenceDiagram-2WXFIKYE-B80s7sOg.js → sequenceDiagram-2WXFIKYE-B2D8IQDb.js} +1 -1
- package/dist/web/assets/settings-tab-CMnv1fce.js +1 -0
- package/dist/web/assets/{sql-query-editor-CjZ7Z6XL.js → sql-query-editor-Bc2hAwqT.js} +1 -1
- package/dist/web/assets/sqlite-viewer-B60MS2Dy.js +1 -0
- package/dist/web/assets/square-vBdqj0bF.js +1 -0
- package/dist/web/assets/{stateDiagram-RAJIS63D-BPLXgXRR.js → stateDiagram-RAJIS63D-ylr4HxPu.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D6zvxf3M.js +1 -0
- package/dist/web/assets/table-Bi27fEaN.js +1 -0
- package/dist/web/assets/{terminal-tab-DjzD8GLn.js → terminal-tab-CCJoLstH.js} +2 -2
- package/dist/web/assets/text-wrap-D_OmSzhp.js +1 -0
- package/dist/web/assets/{timeline-definition-YZTLITO2-fa_51u1X.js → timeline-definition-YZTLITO2-pMv1grvM.js} +1 -1
- package/dist/web/assets/trash-2-CNuB-htI.js +1 -0
- package/dist/web/assets/treemap-KZPCXAKY-Kck06FKU.js +1 -0
- package/dist/web/assets/{use-monaco-theme-D9XFxQuU.js → use-monaco-theme-BJK48EmK.js} +1 -1
- package/dist/web/assets/{vennDiagram-LZ73GAT5-kX4jJn6W.js → vennDiagram-LZ73GAT5-C-rkIUbo.js} +1 -1
- package/dist/web/assets/x-Dw3TjeY_.js +1 -0
- package/dist/web/assets/{xychartDiagram-JWTSCODW-Bzm5lZBs.js → xychartDiagram-JWTSCODW-CtpjAakO.js} +1 -1
- package/dist/web/index.html +18 -22
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +169 -13
- package/docs/extension-development-guide.md +98 -1
- package/docs/journals/260414-1400-ext-git-graph-port-complete.md +147 -0
- package/docs/journals/260414-1452-git-graph-faithful-port.md +144 -0
- package/docs/journals/260414-1810-git-graph-ui-improvements-complete.md +261 -0
- package/docs/journals/260414-2001-bundled-extensions.md +219 -0
- package/docs/project-changelog.md +123 -21
- package/docs/project-roadmap.md +4 -2
- package/docs/system-architecture.md +77 -6
- package/package.json +1 -1
- package/packages/ext-git-graph/package.json +30 -0
- package/packages/ext-git-graph/src/extension-integration.test.ts +230 -0
- package/packages/ext-git-graph/src/extension-parsers.test.ts +193 -0
- package/packages/ext-git-graph/src/extension.ts +921 -0
- package/packages/ext-git-graph/src/git-log-parser.test.ts +271 -0
- package/packages/ext-git-graph/src/git-log-parser.ts +38 -0
- package/packages/ext-git-graph/src/types.ts +192 -0
- package/packages/ext-git-graph/src/webview-html.test.ts +142 -0
- package/packages/ext-git-graph/src/webview-html.ts +2417 -0
- package/packages/vscode-compat/src/index.ts +4 -0
- package/packages/vscode-compat/src/process.ts +25 -0
- package/packages/vscode-compat/src/window.ts +10 -0
- package/src/cli/commands/ext-cmd.ts +3 -1
- package/src/server/ws/extensions.ts +34 -4
- package/src/services/contribution-registry.ts +14 -1
- package/src/services/extension-host-worker.ts +12 -3
- package/src/services/extension-manifest.ts +18 -1
- package/src/services/extension-rpc-handlers.ts +68 -2
- package/src/services/extension.service.ts +63 -9
- package/src/types/extension-messages.ts +3 -1
- package/src/types/extension.ts +8 -0
- package/src/web/components/editor/code-editor.tsx +16 -4
- package/src/web/components/editor/conflict-editor.tsx +368 -0
- package/src/web/components/extensions/extension-webview.tsx +153 -12
- package/src/web/components/layout/command-palette.tsx +41 -17
- package/src/web/components/layout/editor-panel.tsx +16 -4
- package/src/web/components/layout/mobile-nav.tsx +6 -5
- package/src/web/components/layout/tab-bar.tsx +3 -3
- package/src/web/components/layout/tab-content.tsx +17 -5
- package/src/web/components/settings/keyboard-shortcuts-section.tsx +46 -1
- package/src/web/hooks/use-extension-ws.ts +30 -4
- package/src/web/hooks/use-global-keybindings.ts +24 -2
- package/src/web/hooks/use-url-sync.ts +8 -3
- package/src/web/stores/extension-store.ts +8 -0
- package/src/web/stores/keybindings-store.ts +2 -3
- package/src/web/stores/panel-store.ts +2 -2
- package/src/web/stores/panel-utils.ts +6 -2
- package/src/web/stores/tab-store.ts +3 -2
- package/dist/web/assets/ai-settings-section-D6d-RmR6.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-DpVzOETR.js +0 -1
- package/dist/web/assets/arrow-up-BigIMx-e.js +0 -1
- package/dist/web/assets/channel-Cgy1thYT.js +0 -1
- package/dist/web/assets/chat-tab-DXBb9Y3U.js +0 -10
- package/dist/web/assets/check-ePA3ZvK4.js +0 -1
- package/dist/web/assets/chevron-down-EQA06nR-.js +0 -1
- package/dist/web/assets/chevron-right-CXzzT44u.js +0 -1
- package/dist/web/assets/chunk-GLR3WWYH-CxUl1sdz.js +0 -2
- package/dist/web/assets/chunk-HHEYEP7N-DN7ebS2Y.js +0 -1
- package/dist/web/assets/chunk-QZHKN3VN-C4La7oLj.js +0 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-C3IyfqG-.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-Dcvhz2pb.js +0 -1
- package/dist/web/assets/clone--C7Tby8z.js +0 -1
- package/dist/web/assets/code-editor-Cr7JrBKC.js +0 -8
- package/dist/web/assets/columns-2-BZ9uqssV.js +0 -1
- package/dist/web/assets/createLucideIcon-PuMiQgHl.js +0 -1
- package/dist/web/assets/database-D1ToEV9d.js +0 -1
- package/dist/web/assets/diff-viewer-BBr6e_gb.js +0 -4
- package/dist/web/assets/dist-KUoHa6tg.js +0 -1
- package/dist/web/assets/extension-webview-B0klBip8.js +0 -3
- package/dist/web/assets/eye-CNcBU6Tx.js +0 -1
- package/dist/web/assets/git-graph-CDiwGa0g.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-DcPyMEIJ.js +0 -1
- package/dist/web/assets/index-CkaCzNgO.css +0 -2
- package/dist/web/assets/index-Ic5uTu20.js +0 -26
- package/dist/web/assets/info-3K5VOQVL-Dw4O15cw.js +0 -1
- package/dist/web/assets/infoDiagram-LFFYTUFH-DFhmsucr.js +0 -2
- package/dist/web/assets/input-CcbTF6ih.js +0 -45
- package/dist/web/assets/jsx-runtime-R_NjdZtX.js +0 -1
- package/dist/web/assets/keybindings-store-CxE6BlG2.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-o3LmdL8H.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-BjNP0M3B.js +0 -1
- package/dist/web/assets/plus-Iso5r9vD.js +0 -1
- package/dist/web/assets/port-forwarding-tab-BPuSc6pI.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-gDgOiaME.js +0 -1
- package/dist/web/assets/refresh-cw-BgQzFNaG.js +0 -1
- package/dist/web/assets/scroll-area-i4EZlOl_.js +0 -1
- package/dist/web/assets/settings-tab-BzSSN2BQ.js +0 -1
- package/dist/web/assets/sqlite-viewer-CoyZOM_Y.js +0 -1
- package/dist/web/assets/square-pfn_LYYy.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-DksQJ7es.js +0 -1
- package/dist/web/assets/table-CHv2x_qg.js +0 -1
- package/dist/web/assets/tag-Bb_UFXt0.js +0 -1
- package/dist/web/assets/text-wrap-D8BbQYTx.js +0 -1
- package/dist/web/assets/trash-2-DYCa06CV.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-DwFqAvnj.js +0 -1
- package/dist/web/assets/x-BXecj-16.js +0 -1
- package/src/web/components/git/git-graph-branch-label.tsx +0 -124
- package/src/web/components/git/git-graph-constants.ts +0 -185
- package/src/web/components/git/git-graph-detail.tsx +0 -107
- package/src/web/components/git/git-graph-dialog.tsx +0 -72
- package/src/web/components/git/git-graph-row.tsx +0 -167
- package/src/web/components/git/git-graph-settings-dialog.tsx +0 -104
- package/src/web/components/git/git-graph-svg.tsx +0 -54
- package/src/web/components/git/git-graph-toolbar.tsx +0 -195
- package/src/web/components/git/git-graph.tsx +0 -193
- package/src/web/components/git/use-column-resize.ts +0 -33
- package/src/web/components/git/use-git-graph.ts +0 -201
- /package/dist/web/assets/{api-client-wQbeUyeh.js → api-client-BvxmRZUi.js} +0 -0
- /package/dist/web/assets/{array-X0JlPOfd.js → array-BFDiaBgf.js} +0 -0
- /package/dist/web/assets/{csv-parser-CElqio6o.js → csv-parser-i7fjqP2H.js} +0 -0
- /package/dist/web/assets/{cytoscape.esm-BfIOPvwt.js → cytoscape.esm-C8i2jUzT.js} +0 -0
- /package/dist/web/assets/{defaultLocale-B6RGN4id.js → defaultLocale-ZeknFqNe.js} +0 -0
- /package/dist/web/assets/{dist-CK1enexV.js → dist-DZmJeHOA.js} +0 -0
- /package/dist/web/assets/{init-BmUWJJHz.js → init-0VJVrkRJ.js} +0 -0
- /package/dist/web/assets/{isArrayLikeObject-BrCM-iA1.js → isArrayLikeObject-ClzWCpcm.js} +0 -0
- /package/dist/web/assets/{katex-xQS_6bNb.js → katex-DR0kdMDv.js} +0 -0
- /package/dist/web/assets/{lib-CfWBrYll.js → lib-DSLzfeW0.js} +0 -0
- /package/dist/web/assets/{math-CpLFzrfV.js → math-CRc16Nj6.js} +0 -0
- /package/dist/web/assets/{path-CoPyR7c2.js → path-INs8XTPH.js} +0 -0
- /package/dist/web/assets/{preload-helper-CH6UZRzu.js → preload-helper-mr3rCizq.js} +0 -0
- /package/dist/web/assets/{react-j5zqhEum.js → react-0tkk-ztn.js} +0 -0
- /package/dist/web/assets/{rough.esm-D5NinLFK.js → rough.esm-eLccZ4OJ.js} +0 -0
- /package/dist/web/assets/{sql-completion-provider-D0xutVaK.js → sql-completion-provider-B8uUWWej.js} +0 -0
- /package/dist/web/assets/{src-j04igtQ5.js → src-CqyWLlNZ.js} +0 -0
- /package/dist/web/assets/{utils-CSCvNZxE.js → utils-DX8jb5qv.js} +0 -0
package/docs/project-roadmap.md
CHANGED
|
@@ -70,6 +70,8 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
|
|
|
70
70
|
**v0.9.x polish (post-release):**
|
|
71
71
|
- File download feature (v0.9.2) — Single-file + folder-as-zip downloads with short-lived tokens, context menu + toolbar UI
|
|
72
72
|
- Agent Team UI (v0.9.9) — Real-time team monitoring dashboard: REST API + fs.watch inbox events, team activity button with unread pulse, popover/drawer with members + messages, team management in Settings
|
|
73
|
+
- Git-Graph UI Improvements (v0.9.85+) — ✅ Faithful SVG graph rendering (vscode-git-graph port), interactive git workflow (stage/unstage/commit/stash), branch filters, auto-fetch, mobile support, tab system safety guards, UX refinements (branch context menu, dblclick checkout, toast notifications, SVG icons)
|
|
74
|
+
- Git Workflow Enhancements (v0.9.86+) — ✅ Stash management (popover with Apply/Pop/Drop), rebase from context menu, conflict detection (merge/rebase/cherry-pick states), inline conflict resolution editor (Monaco with visual highlighting + Accept buttons), worktree full CRUD (create/remove/prune with project integration)
|
|
73
75
|
|
|
74
76
|
**Multi-provider — v0.9 scope (reduced):**
|
|
75
77
|
- Tier 1 (full agentic): Claude Agent SDK — file edit, terminal, git, full autonomy
|
|
@@ -99,12 +101,12 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
|
|
|
99
101
|
|
|
100
102
|
### v0.10.0 — "Enhanced Workflow" (Q3 2026)
|
|
101
103
|
|
|
102
|
-
**Theme:** Agent collaboration + git workflow. High-impact, independent features that ship fast.
|
|
104
|
+
**Theme:** Agent collaboration + advanced git workflow. High-impact, independent features that ship fast.
|
|
103
105
|
|
|
104
106
|
| Feature | Priority | Description |
|
|
105
107
|
|---------|----------|-------------|
|
|
106
108
|
| **Agent Team** | High | Multi-agent collaboration within PPM. Spawn agent teams for parallel task execution — lead agent delegates to specialist agents (coder, tester, reviewer). Task coordination, file ownership, progress tracking. |
|
|
107
|
-
| **
|
|
109
|
+
| **Advanced Git Operations** | Medium | Interactive rebase UI, cherry-pick workflow, merge strategy selection. (Worktree management completed in v0.9.86) |
|
|
108
110
|
|
|
109
111
|
---
|
|
110
112
|
|
|
@@ -151,13 +151,14 @@ WS /ws/project/:name/terminal/:id → Terminal I/O
|
|
|
151
151
|
```
|
|
152
152
|
/project/{name} → Project root (project switcher)
|
|
153
153
|
/project/{name}/editor/{filePath} → Open editor tab (e.g., src/index.ts)
|
|
154
|
+
/project/{name}/conflict-editor/{filePath} → Open conflict resolution editor (during merge/rebase)
|
|
154
155
|
/project/{name}/chat/{provider}/{sessionId} → Open chat tab
|
|
155
156
|
/project/{name}/terminal/{index} → Open terminal tab
|
|
156
157
|
/project/{name}/database/{connId}/{table} → Open database browser
|
|
157
158
|
/project/{name}/git-graph → Git history graph (singleton)
|
|
158
159
|
/project/{name}/settings → Settings panel (singleton)
|
|
159
160
|
```
|
|
160
|
-
Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `chat:claude/abc123`). Deep links auto-create missing tabs.
|
|
161
|
+
Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `conflict-editor:src/file.ts`, `chat:claude/abc123`). Deep links auto-create missing tabs.
|
|
161
162
|
|
|
162
163
|
---
|
|
163
164
|
|
|
@@ -1295,16 +1296,21 @@ Browser (React) ← Zustand store + React components
|
|
|
1295
1296
|
```
|
|
1296
1297
|
|
|
1297
1298
|
**Key components:**
|
|
1298
|
-
- **Package Format:** npm packages (`@ppm/ext-database`, `@ppm/ext-docker`, etc.)
|
|
1299
|
+
- **Package Format:** npm packages (`@ppm/ext-database`, `@ppm/ext-git-graph`, `@ppm/ext-docker`, etc.)
|
|
1299
1300
|
- **Installation:** `~/.ppm/extensions/node_modules/{id}/`
|
|
1300
1301
|
- **Lifecycle:** Install → Enable → Activate → Deactivate → Remove
|
|
1301
1302
|
- **Worker Isolation:** Each activated extension runs in a Bun Worker (crash-safe, 10s activation timeout)
|
|
1302
1303
|
- **Communication:** RPC (Worker↔Main) + WebSocket (Main↔Browser)
|
|
1303
1304
|
- **API Shim:** `@ppm/vscode-compat` — VSCode-compatible API (commands, window, workspace)
|
|
1305
|
+
- **Subprocess Access:** RPC `process:spawn` handler for extensions needing CLI commands (git, docker, npm, python, etc.)
|
|
1304
1306
|
- **State Storage:** globalState + workspaceState in SQLite via Memento
|
|
1305
1307
|
- **UI Bridge:** StatusBar, TreeView, WebviewPanel, QuickPick, InputBox, Notifications
|
|
1306
1308
|
- **Contributions:** Commands, views, configuration contributed via manifest
|
|
1307
1309
|
|
|
1310
|
+
**Official Extensions:**
|
|
1311
|
+
- `@ppm/ext-database` — Database browser with SQLite/PostgreSQL support (tree view + query panel)
|
|
1312
|
+
- `@ppm/ext-git-graph` — Git commit graph visualization (faithful vscode-git-graph SVG algorithm with Bézier curves, uses process:spawn for git CLI across registered projects)
|
|
1313
|
+
|
|
1308
1314
|
### Manifest Format
|
|
1309
1315
|
|
|
1310
1316
|
Extension metadata defined in `package.json` under `ppm` key:
|
|
@@ -1417,10 +1423,33 @@ Extension metadata defined in `package.json` under `ppm` key:
|
|
|
1417
1423
|
}
|
|
1418
1424
|
```
|
|
1419
1425
|
|
|
1420
|
-
**Built-in Methods:**
|
|
1421
|
-
- `
|
|
1422
|
-
- `
|
|
1423
|
-
- `
|
|
1426
|
+
**Built-in Methods (vscode-compat API):**
|
|
1427
|
+
- `commands:execute(command, ...args)` — Execute command
|
|
1428
|
+
- `commands:list(filterInternal)` — List available commands
|
|
1429
|
+
- `window:showMessage(level, message, items[])` — Show dialog with buttons
|
|
1430
|
+
- `window:showQuickPick(items[], options)` — Quick pick menu
|
|
1431
|
+
- `window:showInputBox(options)` — Text input dialog
|
|
1432
|
+
- `window:webview:create(panelId, extensionId, viewType, title)` — Create webview panel
|
|
1433
|
+
- `window:webview:html(panelId, html)` — Set webview content
|
|
1434
|
+
- `window:webview:postMessage(panelId, message)` — Send message to webview
|
|
1435
|
+
- `window:tree:update(viewId, items[])` — Update tree view items
|
|
1436
|
+
- `window:tree:refresh(viewId)` — Refresh tree view
|
|
1437
|
+
- `window:statusbar:update(item)` — Update/create status bar item
|
|
1438
|
+
- `window:statusbar:remove(itemId)` — Remove status bar item
|
|
1439
|
+
- `workspace:config:get(key)` — Read config value
|
|
1440
|
+
- `workspace:config:update(key, value, target)` — Write config value
|
|
1441
|
+
- `workspace:fs:readFile(filePath)` — Read file (base64 encoded)
|
|
1442
|
+
- `workspace:fs:writeFile(filePath, base64Content)` — Write file
|
|
1443
|
+
- `workspace:fs:stat(filePath)` — Get file metadata
|
|
1444
|
+
- `workspace:fs:readDirectory(dirPath)` — List directory contents
|
|
1445
|
+
|
|
1446
|
+
**Subprocess Execution (extensions needing CLI access):**
|
|
1447
|
+
- `process:spawn(command, args[], options)` — Execute external command
|
|
1448
|
+
- **Allowed commands:** git, node, bun, npm, yarn, pnpm, docker, psql, sqlite3, python3, python
|
|
1449
|
+
- **Options:** `{ cwd?: string, timeout?: number }` (default: 30s timeout, CWD must be within registered project paths, ~/.ppm/extensions/, or current process directory)
|
|
1450
|
+
- **Returns:** `{ code: number, stdout: string, stderr: string, error?: string }`
|
|
1451
|
+
- **Example:** See ext-git-graph for real-world usage (runs `git log --all` across any registered project via path-based CWD)
|
|
1452
|
+
|
|
1424
1453
|
- Extension can define custom RPC methods via `rpc.onRequest(method, handler)`
|
|
1425
1454
|
|
|
1426
1455
|
### State Storage
|
|
@@ -1648,6 +1677,48 @@ Main Process Worker
|
|
|
1648
1677
|
|
|
1649
1678
|
5. Extension auto-activates based on `activationEvents`, state persists
|
|
1650
1679
|
|
|
1680
|
+
### Error Handling & Debugging
|
|
1681
|
+
|
|
1682
|
+
**Activation Error Tracking:**
|
|
1683
|
+
- `ExtensionService.activationErrors` Map tracks `extId → error message` for all failed activations
|
|
1684
|
+
- Errors set during `activate()` if worker response indicates failure (`!result.ok`)
|
|
1685
|
+
- Errors cleared on successful activation or worker termination
|
|
1686
|
+
- Errors included in `contributions:update` message sent via WS to browser on client connect
|
|
1687
|
+
|
|
1688
|
+
**User Feedback (UI):**
|
|
1689
|
+
- **Command Errors:** When extension command fails, toast shows "Extension command failed: {error}" with error details
|
|
1690
|
+
- **Timeout Handling:** If webview panel doesn't load within 10s, fallback UI displays activation error (if available) + "Retry" button
|
|
1691
|
+
- **Retry Button:** User can click to re-trigger the command without page reload (re-dispatches `ext:command:execute`)
|
|
1692
|
+
|
|
1693
|
+
**Breadcrumb Logging (Console):**
|
|
1694
|
+
- **`[ExtService]`** — Main process lifecycle: activation start/success, worker lifecycle, contributions broadcast
|
|
1695
|
+
- **`[ExtHost]`** — Worker-side execution: command routing, handler invocation, error context
|
|
1696
|
+
- **`[ExtWS]`** — WebSocket bridge: client connect, message handling, error responses
|
|
1697
|
+
- **Extension-specific tags** — e.g., `[ext-git-graph]` for extension-specific log context
|
|
1698
|
+
|
|
1699
|
+
**Example Log Flow (normal):**
|
|
1700
|
+
```
|
|
1701
|
+
[ExtService] startup: activating ext-git-graph...
|
|
1702
|
+
[ExtWS] Client connected (1 total)
|
|
1703
|
+
[ExtHost] activating ext-git-graph from dist/extension.js
|
|
1704
|
+
[ExtHost] activated ext-git-graph (1 total)
|
|
1705
|
+
[ExtService] activated ext-git-graph successfully
|
|
1706
|
+
[ExtWS] command:execute "git-graph.view"
|
|
1707
|
+
[ExtHost] command:execute "git-graph.view" (1 extensions active)
|
|
1708
|
+
[ExtHost] routing "git-graph.view" → ext-git-graph
|
|
1709
|
+
```
|
|
1710
|
+
|
|
1711
|
+
**Example Log Flow (error):**
|
|
1712
|
+
```
|
|
1713
|
+
[ExtService] startup: activating ext-git-graph...
|
|
1714
|
+
[ExtHost] activating ext-git-graph from dist/extension.ts
|
|
1715
|
+
[ExtHost] ERROR: Cannot find module 'missing-dep'
|
|
1716
|
+
[ExtService] Failed to activate ext-git-graph on startup: Cannot find module 'missing-dep'
|
|
1717
|
+
→ activationErrors["ext-git-graph"] = "Cannot find module 'missing-dep'"
|
|
1718
|
+
→ browser receives { type: "contributions:update", activationErrors: {"ext-git-graph": "..."} }
|
|
1719
|
+
→ user sees toast: "Extension "ext-git-graph" failed to activate: Cannot find module..."
|
|
1720
|
+
```
|
|
1721
|
+
|
|
1651
1722
|
### Crash Safety
|
|
1652
1723
|
|
|
1653
1724
|
**Worker Isolation:**
|
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ppm/ext-git-graph",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "src/extension.ts",
|
|
5
|
+
"engines": { "ppm": ">=0.9.0" },
|
|
6
|
+
"activationEvents": ["onCommand:git-graph.view"],
|
|
7
|
+
"contributes": {
|
|
8
|
+
"commands": [
|
|
9
|
+
{ "command": "git-graph.view", "title": "Git Graph: View Repository", "icon": "git-branch" }
|
|
10
|
+
],
|
|
11
|
+
"keybindings": [
|
|
12
|
+
{ "command": "git-graph.view", "key": "Mod+G" }
|
|
13
|
+
],
|
|
14
|
+
"menus": {
|
|
15
|
+
"commandPalette": [
|
|
16
|
+
{ "command": "git-graph.view" }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"configuration": {
|
|
20
|
+
"properties": {
|
|
21
|
+
"git-graph.maxCommits": { "type": "number", "default": 300, "description": "Maximum commits to load" },
|
|
22
|
+
"git-graph.showRemoteBranches": { "type": "boolean", "default": true, "description": "Show remote branches" }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"ppm": {
|
|
27
|
+
"displayName": "Git Graph",
|
|
28
|
+
"icon": "git-branch"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Integration tests for git-graph extension.
|
|
8
|
+
* Uses real git repositories to test git data fetching and parsing.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
let testRepoDir: string;
|
|
12
|
+
|
|
13
|
+
async function spawnGit(args: string[], cwd: string) {
|
|
14
|
+
const env = {
|
|
15
|
+
GIT_AUTHOR_NAME: "Test Author",
|
|
16
|
+
GIT_AUTHOR_EMAIL: "test@example.com",
|
|
17
|
+
GIT_COMMITTER_NAME: "Test Committer",
|
|
18
|
+
GIT_COMMITTER_EMAIL: "committer@example.com",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const proc = Bun.spawn(["git", ...args], {
|
|
22
|
+
cwd,
|
|
23
|
+
env,
|
|
24
|
+
stdout: "pipe",
|
|
25
|
+
stderr: "pipe",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const stdout = await new Response(proc.stdout).text();
|
|
29
|
+
const exitCode = await proc.exited;
|
|
30
|
+
return { stdout, exitCode };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function initGitRepo(repoPath: string, withRemote = false) {
|
|
34
|
+
const env = {
|
|
35
|
+
GIT_AUTHOR_NAME: "Test Author",
|
|
36
|
+
GIT_AUTHOR_EMAIL: "test@example.com",
|
|
37
|
+
GIT_COMMITTER_NAME: "Test Committer",
|
|
38
|
+
GIT_COMMITTER_EMAIL: "committer@example.com",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Initialize repo
|
|
42
|
+
await Bun.spawn(["git", "init"], { cwd: repoPath, env, stdout: "pipe" }).exited;
|
|
43
|
+
|
|
44
|
+
// Create initial commit
|
|
45
|
+
writeFileSync(join(repoPath, "README.md"), "# Test Repo\n");
|
|
46
|
+
await Bun.spawn(["git", "add", "README.md"], { cwd: repoPath, env }).exited;
|
|
47
|
+
await Bun.spawn(["git", "commit", "-m", "Initial commit"], { cwd: repoPath, env }).exited;
|
|
48
|
+
|
|
49
|
+
// Create a branch
|
|
50
|
+
await Bun.spawn(["git", "checkout", "-b", "develop"], { cwd: repoPath, env }).exited;
|
|
51
|
+
writeFileSync(join(repoPath, "feature.txt"), "Feature content\n");
|
|
52
|
+
await Bun.spawn(["git", "add", "feature.txt"], { cwd: repoPath, env }).exited;
|
|
53
|
+
await Bun.spawn(["git", "commit", "-m", "Add feature"], { cwd: repoPath, env }).exited;
|
|
54
|
+
|
|
55
|
+
// Create a tag
|
|
56
|
+
await Bun.spawn(["git", "tag", "v1.0.0"], { cwd: repoPath, env }).exited;
|
|
57
|
+
|
|
58
|
+
// Back to main
|
|
59
|
+
await Bun.spawn(["git", "checkout", "main"], { cwd: repoPath, env }).exited;
|
|
60
|
+
|
|
61
|
+
if (withRemote) {
|
|
62
|
+
// Create a bare repo to act as remote
|
|
63
|
+
const remoteDir = join(tmpdir(), `remote-${Date.now()}`);
|
|
64
|
+
await Bun.spawn(["git", "init", "--bare"], { cwd: remoteDir, env }).exited;
|
|
65
|
+
await Bun.spawn(["git", "remote", "add", "origin", remoteDir], { cwd: repoPath, env }).exited;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe("git-graph extension: integration tests", () => {
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
testRepoDir = mkdtempSync(resolve(tmpdir(), "ppm-git-graph-"));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
try {
|
|
76
|
+
rmSync(testRepoDir, { recursive: true, force: true });
|
|
77
|
+
} catch {}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("fetches branches from real git repo", async () => {
|
|
81
|
+
await initGitRepo(testRepoDir);
|
|
82
|
+
|
|
83
|
+
const result = await spawnGit(
|
|
84
|
+
["branch", "-a", "--format=%(refname:short)|%(objectname:short)|%(HEAD)"],
|
|
85
|
+
testRepoDir
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const lines = result.stdout.trim().split("\n").filter(Boolean);
|
|
89
|
+
expect(lines.length).toBeGreaterThan(0);
|
|
90
|
+
// Should have at least main and develop
|
|
91
|
+
const branches = lines.map((line) => line.split("|")[0]);
|
|
92
|
+
expect(branches).toContain("main");
|
|
93
|
+
expect(branches).toContain("develop");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("fetches tags from real git repo", async () => {
|
|
97
|
+
await initGitRepo(testRepoDir);
|
|
98
|
+
|
|
99
|
+
const result = await spawnGit(
|
|
100
|
+
["tag", "-l", "--format=%(refname:short)|%(objectname:short)"],
|
|
101
|
+
testRepoDir
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const lines = result.stdout.trim().split("\n").filter(Boolean);
|
|
105
|
+
const tags = lines.map((line) => line.split("|")[0]);
|
|
106
|
+
expect(tags).toContain("v1.0.0");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("fetches git log with custom format", async () => {
|
|
110
|
+
await initGitRepo(testRepoDir);
|
|
111
|
+
|
|
112
|
+
const result = await spawnGit(
|
|
113
|
+
[
|
|
114
|
+
"log",
|
|
115
|
+
`--format=%H%n%P%n%an%n%ae%n%at%n%cn%n%ce%n%ct%n%D%n%s%n<END_COMMIT>`,
|
|
116
|
+
"--topo-order",
|
|
117
|
+
"--all",
|
|
118
|
+
],
|
|
119
|
+
testRepoDir
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(result.stdout).toContain("<END_COMMIT>");
|
|
123
|
+
const commits = result.stdout.split("<END_COMMIT>").filter((b) => b.trim());
|
|
124
|
+
expect(commits.length).toBeGreaterThanOrEqual(2); // At least 2 commits
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("gets current branch name", async () => {
|
|
128
|
+
await initGitRepo(testRepoDir);
|
|
129
|
+
await spawnGit(["checkout", "main"], testRepoDir);
|
|
130
|
+
|
|
131
|
+
const result = await spawnGit(["rev-parse", "--abbrev-ref", "HEAD"], testRepoDir);
|
|
132
|
+
|
|
133
|
+
expect(result.stdout.trim()).toBe("main");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("gets HEAD commit hash", async () => {
|
|
137
|
+
await initGitRepo(testRepoDir);
|
|
138
|
+
|
|
139
|
+
const result = await spawnGit(["rev-parse", "HEAD"], testRepoDir);
|
|
140
|
+
|
|
141
|
+
const hash = result.stdout.trim();
|
|
142
|
+
expect(hash).toMatch(/^[0-9a-f]{40}$/); // Valid SHA-1
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("handles repo with no commits gracefully", async () => {
|
|
146
|
+
// Initialize but don't create commits
|
|
147
|
+
await Bun.spawn(["git", "init"], { cwd: testRepoDir, stdout: "pipe" }).exited;
|
|
148
|
+
|
|
149
|
+
const result = await spawnGit(["log", "--format=%H"], testRepoDir);
|
|
150
|
+
expect(result.exitCode).not.toBe(0); // Will fail with no commits
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("parses commit details with show --stat", async () => {
|
|
154
|
+
await initGitRepo(testRepoDir);
|
|
155
|
+
|
|
156
|
+
// Get latest commit hash
|
|
157
|
+
const hashResult = await spawnGit(["rev-parse", "HEAD"], testRepoDir);
|
|
158
|
+
const hash = hashResult.stdout.trim();
|
|
159
|
+
|
|
160
|
+
const result = await spawnGit(
|
|
161
|
+
[
|
|
162
|
+
"show",
|
|
163
|
+
"--stat",
|
|
164
|
+
`--format=%H%n%P%n%an%n%ae%n%at%n%cn%n%ce%n%ct%n%B%n<END_MSG>`,
|
|
165
|
+
hash,
|
|
166
|
+
],
|
|
167
|
+
testRepoDir
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
expect(result.stdout).toContain("<END_MSG>");
|
|
171
|
+
expect(result.stdout).toContain(hash);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("fetches stash list from repo", async () => {
|
|
175
|
+
await initGitRepo(testRepoDir);
|
|
176
|
+
|
|
177
|
+
// Create an unstaged change
|
|
178
|
+
writeFileSync(join(testRepoDir, "test.txt"), "test content");
|
|
179
|
+
await spawnGit(["add", "test.txt"], testRepoDir);
|
|
180
|
+
await spawnGit(["stash", "push", "-m", "Test stash"], testRepoDir);
|
|
181
|
+
|
|
182
|
+
const result = await spawnGit(
|
|
183
|
+
["stash", "list", "--format=%gd|%H|%s"],
|
|
184
|
+
testRepoDir
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(result.stdout).toContain("stash@{0}");
|
|
188
|
+
expect(result.stdout).toContain("Test stash");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("handles git operations with special characters in message", async () => {
|
|
192
|
+
await initGitRepo(testRepoDir);
|
|
193
|
+
|
|
194
|
+
writeFileSync(join(testRepoDir, "special.txt"), "content with special chars");
|
|
195
|
+
await spawnGit(["add", "special.txt"], testRepoDir);
|
|
196
|
+
await spawnGit(
|
|
197
|
+
["commit", "-m", "Fix: issue #123 & test 'quotes' \"double\""],
|
|
198
|
+
testRepoDir
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const result = await spawnGit(["log", "--oneline", "-1"], testRepoDir);
|
|
202
|
+
|
|
203
|
+
expect(result.stdout).toContain("issue #123");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("respects skip parameter in git log", async () => {
|
|
207
|
+
await initGitRepo(testRepoDir);
|
|
208
|
+
|
|
209
|
+
// Create 3 additional commits
|
|
210
|
+
for (let i = 0; i < 3; i++) {
|
|
211
|
+
writeFileSync(join(testRepoDir, `file${i}.txt`), `content ${i}`);
|
|
212
|
+
await spawnGit(["add", "."], testRepoDir);
|
|
213
|
+
await spawnGit(["commit", "-m", `Commit ${i}`], testRepoDir);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const resultAll = await spawnGit(
|
|
217
|
+
["log", "--format=%H", "--all"],
|
|
218
|
+
testRepoDir
|
|
219
|
+
);
|
|
220
|
+
const allCommits = resultAll.stdout.trim().split("\n").length;
|
|
221
|
+
|
|
222
|
+
const resultSkip2 = await spawnGit(
|
|
223
|
+
["log", "--format=%H", "--skip=2", "--all"],
|
|
224
|
+
testRepoDir
|
|
225
|
+
);
|
|
226
|
+
const skippedCommits = resultSkip2.stdout.trim().split("\n").filter(Boolean).length;
|
|
227
|
+
|
|
228
|
+
expect(skippedCommits).toBeLessThan(allCommits);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// Import the parser functions from extension.ts
|
|
4
|
+
// We'll need to export them for testing purposes
|
|
5
|
+
function parseBranches(stdout: string) {
|
|
6
|
+
return stdout.trim().split("\n").filter(Boolean).map((line) => {
|
|
7
|
+
const [name, hash, head] = line.split("|");
|
|
8
|
+
const remote = name.includes("/") ? name.split("/")[0] : undefined;
|
|
9
|
+
return { name, hash, current: head === "*", remote };
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function parseTags(stdout: string) {
|
|
14
|
+
return stdout.trim().split("\n").filter(Boolean).map((line) => {
|
|
15
|
+
const [name, hash] = line.split("|");
|
|
16
|
+
return { name, hash };
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseRemotes(stdout: string) {
|
|
21
|
+
const map = new Map<string, { fetchUrl: string; pushUrl: string }>();
|
|
22
|
+
for (const line of stdout.trim().split("\n").filter(Boolean)) {
|
|
23
|
+
const match = line.match(/^(\S+)\s+(\S+)\s+\((\w+)\)$/);
|
|
24
|
+
if (!match) continue;
|
|
25
|
+
const [, name, url, type] = match;
|
|
26
|
+
if (!map.has(name)) map.set(name, { fetchUrl: "", pushUrl: "" });
|
|
27
|
+
const entry = map.get(name)!;
|
|
28
|
+
if (type === "fetch") entry.fetchUrl = url;
|
|
29
|
+
else entry.pushUrl = url;
|
|
30
|
+
}
|
|
31
|
+
return [...map.entries()].map(([name, urls]) => ({ name, ...urls }));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseStashes(stdout: string) {
|
|
35
|
+
return stdout.trim().split("\n").filter(Boolean).map((line, i) => {
|
|
36
|
+
const [, hash, message] = line.split("|");
|
|
37
|
+
return { index: i, hash, message };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("extension.ts: parseBranches", () => {
|
|
42
|
+
it("parses current branch with asterisk", () => {
|
|
43
|
+
const output = "main|abc1234|*\n";
|
|
44
|
+
const result = parseBranches(output);
|
|
45
|
+
expect(result).toHaveLength(1);
|
|
46
|
+
expect(result[0]).toEqual({ name: "main", hash: "abc1234", current: true, remote: undefined });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("parses non-current branches", () => {
|
|
50
|
+
const output = "develop|def5678|\nfeature|ghi9012|\n";
|
|
51
|
+
const result = parseBranches(output);
|
|
52
|
+
expect(result).toHaveLength(2);
|
|
53
|
+
expect(result[0]?.current).toBe(false);
|
|
54
|
+
expect(result[1]?.current).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("parses remote branches with remote name", () => {
|
|
58
|
+
const output = "origin/main|abc1234|\nupstream/develop|def5678|\n";
|
|
59
|
+
const result = parseBranches(output);
|
|
60
|
+
expect(result[0]).toEqual({ name: "origin/main", hash: "abc1234", current: false, remote: "origin" });
|
|
61
|
+
expect(result[1]).toEqual({ name: "upstream/develop", hash: "def5678", current: false, remote: "upstream" });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("parses mixed local and remote branches", () => {
|
|
65
|
+
const output = "main|abc1234|*\norigin/main|abc1234|\ndevelop|def5678|\norigin/develop|def5678|\n";
|
|
66
|
+
const result = parseBranches(output);
|
|
67
|
+
expect(result).toHaveLength(4);
|
|
68
|
+
expect(result[0]?.remote).toBeUndefined();
|
|
69
|
+
expect(result[1]?.remote).toBe("origin");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("handles empty input", () => {
|
|
73
|
+
const result = parseBranches("");
|
|
74
|
+
expect(result).toEqual([]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("ignores lines with missing fields", () => {
|
|
78
|
+
const output = "incomplete|\nmain|abc1234|*\n";
|
|
79
|
+
const result = parseBranches(output);
|
|
80
|
+
// Both lines are parsed, but the first has undefined hash
|
|
81
|
+
expect(result).toHaveLength(2);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("extension.ts: parseTags", () => {
|
|
86
|
+
it("parses single tag", () => {
|
|
87
|
+
const output = "v1.0.0|abc1234\n";
|
|
88
|
+
const result = parseTags(output);
|
|
89
|
+
expect(result).toHaveLength(1);
|
|
90
|
+
expect(result[0]).toEqual({ name: "v1.0.0", hash: "abc1234" });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("parses multiple tags", () => {
|
|
94
|
+
const output = "v1.0.0|abc1234\nv1.0.1|def5678\nv2.0.0|ghi9012\n";
|
|
95
|
+
const result = parseTags(output);
|
|
96
|
+
expect(result).toHaveLength(3);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("handles empty input", () => {
|
|
100
|
+
const result = parseTags("");
|
|
101
|
+
expect(result).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("handles whitespace", () => {
|
|
105
|
+
const output = " \nv1.0.0|abc1234\n \n";
|
|
106
|
+
const result = parseTags(output);
|
|
107
|
+
expect(result).toHaveLength(1);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("extension.ts: parseRemotes", () => {
|
|
112
|
+
it("parses single remote with fetch and push URLs", () => {
|
|
113
|
+
const output = `origin https://github.com/user/repo.git (fetch)
|
|
114
|
+
origin https://github.com/user/repo.git (push)
|
|
115
|
+
`;
|
|
116
|
+
const result = parseRemotes(output);
|
|
117
|
+
expect(result).toHaveLength(1);
|
|
118
|
+
expect(result[0]).toEqual({
|
|
119
|
+
name: "origin",
|
|
120
|
+
fetchUrl: "https://github.com/user/repo.git",
|
|
121
|
+
pushUrl: "https://github.com/user/repo.git",
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("parses multiple remotes", () => {
|
|
126
|
+
const output = `origin https://github.com/user/repo.git (fetch)
|
|
127
|
+
origin https://github.com/user/repo.git (push)
|
|
128
|
+
upstream https://github.com/upstream/repo.git (fetch)
|
|
129
|
+
upstream https://github.com/upstream/repo.git (push)
|
|
130
|
+
`;
|
|
131
|
+
const result = parseRemotes(output);
|
|
132
|
+
expect(result).toHaveLength(2);
|
|
133
|
+
expect(result[0]?.name).toBe("origin");
|
|
134
|
+
expect(result[1]?.name).toBe("upstream");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("handles remotes with different fetch/push URLs", () => {
|
|
138
|
+
const output = `origin git@github.com:user/repo.git (fetch)
|
|
139
|
+
origin https://github.com/user/repo.git (push)
|
|
140
|
+
`;
|
|
141
|
+
const result = parseRemotes(output);
|
|
142
|
+
expect(result[0]?.fetchUrl).toBe("git@github.com:user/repo.git");
|
|
143
|
+
expect(result[0]?.pushUrl).toBe("https://github.com/user/repo.git");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("handles empty input", () => {
|
|
147
|
+
const result = parseRemotes("");
|
|
148
|
+
expect(result).toEqual([]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("ignores malformed lines", () => {
|
|
152
|
+
const output = `origin https://github.com/user/repo.git (fetch)
|
|
153
|
+
origin https://github.com/user/repo.git (push)
|
|
154
|
+
invalid line without brackets
|
|
155
|
+
`;
|
|
156
|
+
const result = parseRemotes(output);
|
|
157
|
+
expect(result).toHaveLength(1);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("extension.ts: parseStashes", () => {
|
|
162
|
+
it("parses single stash", () => {
|
|
163
|
+
const output = "stash@{0}|abc1234|WIP on main\n";
|
|
164
|
+
const result = parseStashes(output);
|
|
165
|
+
expect(result).toHaveLength(1);
|
|
166
|
+
expect(result[0]).toEqual({ index: 0, hash: "abc1234", message: "WIP on main" });
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("parses multiple stashes with correct indices", () => {
|
|
170
|
+
const output = `stash@{0}|abc1234|WIP on main: first
|
|
171
|
+
stash@{1}|def5678|WIP on develop: second
|
|
172
|
+
stash@{2}|ghi9012|Untracked files
|
|
173
|
+
`;
|
|
174
|
+
const result = parseStashes(output);
|
|
175
|
+
expect(result).toHaveLength(3);
|
|
176
|
+
expect(result[0]?.index).toBe(0);
|
|
177
|
+
expect(result[1]?.index).toBe(1);
|
|
178
|
+
expect(result[2]?.index).toBe(2);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("handles empty input", () => {
|
|
182
|
+
const result = parseStashes("");
|
|
183
|
+
expect(result).toEqual([]);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("handles stash messages with pipes in them", () => {
|
|
187
|
+
// This test verifies current behavior; note that parsing breaks with pipes in message
|
|
188
|
+
const output = "stash@{0}|abc1234|Message with more data\n";
|
|
189
|
+
const result = parseStashes(output);
|
|
190
|
+
expect(result).toHaveLength(1);
|
|
191
|
+
expect(result[0]?.hash).toBe("abc1234");
|
|
192
|
+
});
|
|
193
|
+
});
|