@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
|
@@ -21,11 +21,13 @@ export {
|
|
|
21
21
|
type RpcClient,
|
|
22
22
|
} from "./types.ts";
|
|
23
23
|
export { type ExtensionContext, Memento } from "./context.ts";
|
|
24
|
+
export { ProcessService, type SpawnResult, type SpawnOptions } from "./process.ts";
|
|
24
25
|
|
|
25
26
|
// Service imports
|
|
26
27
|
import { CommandService } from "./commands.ts";
|
|
27
28
|
import { WindowService } from "./window.ts";
|
|
28
29
|
import { WorkspaceService } from "./workspace.ts";
|
|
30
|
+
import { ProcessService } from "./process.ts";
|
|
29
31
|
import { createExtensionContext, type ExtensionContext } from "./context.ts";
|
|
30
32
|
import { createEnvNamespace } from "./env.ts";
|
|
31
33
|
import { createNotSupported } from "./not-supported.ts";
|
|
@@ -55,12 +57,14 @@ export function createVscodeCompat(options: CreateVscodeCompatOptions) {
|
|
|
55
57
|
const commands = new CommandService(rpc, extensionId);
|
|
56
58
|
const window = new WindowService(rpc, extensionId);
|
|
57
59
|
const workspace = new WorkspaceService(rpc, extensionId);
|
|
60
|
+
const process = new ProcessService(rpc);
|
|
58
61
|
|
|
59
62
|
return {
|
|
60
63
|
// Active API namespaces
|
|
61
64
|
commands,
|
|
62
65
|
window,
|
|
63
66
|
workspace,
|
|
67
|
+
process,
|
|
64
68
|
env: createEnvNamespace(options.appName ?? "PPM", options.machineId ?? "ppm-local"),
|
|
65
69
|
|
|
66
70
|
// Classes & utilities
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RpcClient } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
export interface SpawnResult {
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
exitCode: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SpawnOptions {
|
|
10
|
+
timeout?: number;
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Process namespace — spawn subprocesses via RPC to main process */
|
|
15
|
+
export class ProcessService {
|
|
16
|
+
private rpc: RpcClient;
|
|
17
|
+
|
|
18
|
+
constructor(rpc: RpcClient) {
|
|
19
|
+
this.rpc = rpc;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async spawn(cmd: string, args: string[], cwd: string, options?: SpawnOptions): Promise<SpawnResult> {
|
|
23
|
+
return this.rpc.request<SpawnResult>("process:spawn", cmd, args, cwd, options);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -184,6 +184,16 @@ export class WindowService {
|
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// --- Open PPM Tab ---
|
|
188
|
+
|
|
189
|
+
async openTab(tabType: string, title: string, projectId: string | null, metadata?: Record<string, unknown>): Promise<void> {
|
|
190
|
+
await this.rpc.request("window:openTab", tabType, title, projectId, metadata);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async switchProject(projectName: string): Promise<void> {
|
|
194
|
+
await this.rpc.request("window:switchProject", projectName);
|
|
195
|
+
}
|
|
196
|
+
|
|
187
197
|
// --- Webview Panel ---
|
|
188
198
|
|
|
189
199
|
createWebviewPanel(viewType: string, title: string, showOptions: ViewColumn): unknown {
|
|
@@ -63,6 +63,7 @@ export function registerExtCommands(program: Command): void {
|
|
|
63
63
|
.description("List installed extensions")
|
|
64
64
|
.action(async () => {
|
|
65
65
|
const { extensionService } = await import("../../services/extension.service.ts");
|
|
66
|
+
await extensionService.discover(); // populate bundledIds for source column
|
|
66
67
|
const extensions = extensionService.list();
|
|
67
68
|
if (extensions.length === 0) {
|
|
68
69
|
console.log(`${C.dim}No extensions installed.${C.reset}`);
|
|
@@ -71,10 +72,11 @@ export function registerExtCommands(program: Command): void {
|
|
|
71
72
|
const rows = extensions.map((e) => [
|
|
72
73
|
e.id,
|
|
73
74
|
e.version,
|
|
75
|
+
extensionService.isBundled(e.id) ? `${C.cyan}bundled${C.reset}` : "user",
|
|
74
76
|
e.enabled ? `${C.green}enabled${C.reset}` : `${C.dim}disabled${C.reset}`,
|
|
75
77
|
e.activated ? `${C.green}active${C.reset}` : `${C.dim}inactive${C.reset}`,
|
|
76
78
|
]);
|
|
77
|
-
printTable(["ID", "Version", "Enabled", "Status"], rows);
|
|
79
|
+
printTable(["ID", "Version", "Source", "Enabled", "Status"], rows);
|
|
78
80
|
});
|
|
79
81
|
|
|
80
82
|
ext
|
|
@@ -71,21 +71,51 @@ async function handleMessage(ws: ExtWsSocket, raw: string | Buffer): Promise<voi
|
|
|
71
71
|
|
|
72
72
|
switch (msg.type) {
|
|
73
73
|
case "ready": {
|
|
74
|
-
// Send current contributions on connect
|
|
74
|
+
// Send current contributions + any activation errors on connect
|
|
75
75
|
const contributions = contributionRegistry.getAll();
|
|
76
|
-
|
|
76
|
+
const { extensionService } = await import("../../services/extension.service.ts");
|
|
77
|
+
const activationErrors = Object.fromEntries(extensionService.getActivationErrors());
|
|
78
|
+
const readyMsg: ExtServerMsg = Object.keys(activationErrors).length > 0
|
|
79
|
+
? { type: "contributions:update", contributions, activationErrors }
|
|
80
|
+
: { type: "contributions:update", contributions };
|
|
81
|
+
ws.send(JSON.stringify(readyMsg));
|
|
77
82
|
break;
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
case "command:execute": {
|
|
81
86
|
try {
|
|
82
87
|
const { extensionService } = await import("../../services/extension.service.ts");
|
|
83
|
-
// Forward to extension host worker via RPC
|
|
84
88
|
if (extensionService["rpc"]) {
|
|
85
|
-
|
|
89
|
+
console.log(`[ExtWS] command:execute "${msg.command}"`);
|
|
90
|
+
const result = await extensionService["rpc"].sendRequest<{ ok: boolean; error?: string }>(
|
|
91
|
+
"ext:command:execute", msg.command, ...(msg.args ?? []),
|
|
92
|
+
);
|
|
93
|
+
if (!result?.ok) {
|
|
94
|
+
console.error(`[ExtWS] command:execute failed: ${result?.error ?? "unknown"}`);
|
|
95
|
+
broadcastExtMsg({
|
|
96
|
+
type: "notification",
|
|
97
|
+
id: `cmd-error-${Date.now()}`,
|
|
98
|
+
level: "error",
|
|
99
|
+
message: `Extension command failed: ${result?.error ?? "unknown error"}`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
console.error(`[ExtWS] command:execute: extension host not ready`);
|
|
104
|
+
broadcastExtMsg({
|
|
105
|
+
type: "notification",
|
|
106
|
+
id: `cmd-error-${Date.now()}`,
|
|
107
|
+
level: "error",
|
|
108
|
+
message: `Extension host not ready. Try reloading the page.`,
|
|
109
|
+
});
|
|
86
110
|
}
|
|
87
111
|
} catch (e) {
|
|
88
112
|
console.error(`[ExtWS] command:execute error:`, e);
|
|
113
|
+
broadcastExtMsg({
|
|
114
|
+
type: "notification",
|
|
115
|
+
id: `cmd-error-${Date.now()}`,
|
|
116
|
+
level: "error",
|
|
117
|
+
message: `Extension command error: ${e instanceof Error ? e.message : String(e)}`,
|
|
118
|
+
});
|
|
89
119
|
}
|
|
90
120
|
break;
|
|
91
121
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionContributes, ContributedCommand, ContributedView, ContributedMenu } from "../types/extension.ts";
|
|
1
|
+
import type { ExtensionContributes, ContributedCommand, ContributedView, ContributedMenu, ContributedKeybinding } from "../types/extension.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* In-memory registry of all contribution points from enabled extensions.
|
|
@@ -9,6 +9,7 @@ class ContributionRegistry {
|
|
|
9
9
|
private views = new Map<string, Map<string, ContributedView & { extId: string }>>();
|
|
10
10
|
private configs = new Map<string, Record<string, unknown>>();
|
|
11
11
|
private menus = new Map<string, Array<ContributedMenu & { extId: string }>>();
|
|
12
|
+
private keybindings: Array<ContributedKeybinding & { extId: string }> = [];
|
|
12
13
|
|
|
13
14
|
register(extId: string, contributes: ExtensionContributes): void {
|
|
14
15
|
if (contributes.commands) {
|
|
@@ -37,6 +38,11 @@ class ContributionRegistry {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
}
|
|
41
|
+
if (contributes.keybindings) {
|
|
42
|
+
for (const kb of contributes.keybindings) {
|
|
43
|
+
this.keybindings.push({ ...kb, extId });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
unregister(extId: string): void {
|
|
@@ -51,6 +57,7 @@ class ContributionRegistry {
|
|
|
51
57
|
for (const [location, items] of this.menus) {
|
|
52
58
|
this.menus.set(location, items.filter((m) => m.extId !== extId));
|
|
53
59
|
}
|
|
60
|
+
this.keybindings = this.keybindings.filter((kb) => kb.extId !== extId);
|
|
54
61
|
this.configs.delete(extId);
|
|
55
62
|
}
|
|
56
63
|
|
|
@@ -73,6 +80,10 @@ class ContributionRegistry {
|
|
|
73
80
|
return [...this.views.keys()];
|
|
74
81
|
}
|
|
75
82
|
|
|
83
|
+
getKeybindings(): Array<ContributedKeybinding & { extId: string }> {
|
|
84
|
+
return [...this.keybindings];
|
|
85
|
+
}
|
|
86
|
+
|
|
76
87
|
getConfiguration(extId?: string): Record<string, Record<string, unknown>> {
|
|
77
88
|
if (extId) {
|
|
78
89
|
const cfg = this.configs.get(extId);
|
|
@@ -95,6 +106,7 @@ class ContributionRegistry {
|
|
|
95
106
|
commands: this.getCommands(),
|
|
96
107
|
views: viewsByLocation,
|
|
97
108
|
menus: menusByLocation,
|
|
109
|
+
keybindings: this.getKeybindings(),
|
|
98
110
|
configuration: this.getConfiguration(),
|
|
99
111
|
};
|
|
100
112
|
}
|
|
@@ -104,6 +116,7 @@ class ContributionRegistry {
|
|
|
104
116
|
this.views.clear();
|
|
105
117
|
this.configs.clear();
|
|
106
118
|
this.menus.clear();
|
|
119
|
+
this.keybindings = [];
|
|
107
120
|
}
|
|
108
121
|
}
|
|
109
122
|
|
|
@@ -29,6 +29,7 @@ self.addEventListener("message", (event: MessageEvent<RpcMessage>) => {
|
|
|
29
29
|
|
|
30
30
|
rpc.onRequest("ext:activate", async (params) => {
|
|
31
31
|
const [extId, entryPath, extensionPath, storedState, baseUrl] = params as [string, string, string, Record<string, Record<string, string | null>>?, string?];
|
|
32
|
+
console.log(`[ExtHost] activating ${extId} from ${entryPath}`);
|
|
32
33
|
if (activeExtensions.has(extId)) return { ok: true, already: true };
|
|
33
34
|
|
|
34
35
|
// Expose server base URL so extensions can use fetch() with absolute URLs
|
|
@@ -68,6 +69,7 @@ rpc.onRequest("ext:activate", async (params) => {
|
|
|
68
69
|
window: api.window as WindowService,
|
|
69
70
|
commands: api.commands as CommandService,
|
|
70
71
|
});
|
|
72
|
+
console.log(`[ExtHost] activated ${extId} (${activeExtensions.size} total)`);
|
|
71
73
|
return { ok: true };
|
|
72
74
|
} catch (e) {
|
|
73
75
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -101,16 +103,23 @@ rpc.onRequest("ext:deactivate", async (params) => {
|
|
|
101
103
|
|
|
102
104
|
rpc.onRequest("ext:command:execute", async (params) => {
|
|
103
105
|
const [command, ...args] = params as [string, ...unknown[]];
|
|
104
|
-
|
|
106
|
+
console.log(`[ExtHost] command:execute "${command}" (${activeExtensions.size} extensions active)`);
|
|
107
|
+
for (const [extId, ext] of activeExtensions) {
|
|
105
108
|
if (ext.commands) {
|
|
109
|
+
const hasLocal = (ext.commands as any).localHandlers?.has(command);
|
|
110
|
+
if (!hasLocal) continue;
|
|
111
|
+
console.log(`[ExtHost] routing "${command}" → ${extId}`);
|
|
106
112
|
try {
|
|
107
113
|
const result = await (ext.commands as any).executeCommand(command, ...args);
|
|
108
114
|
return { ok: true, result };
|
|
109
|
-
} catch {
|
|
110
|
-
|
|
115
|
+
} catch (e) {
|
|
116
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
117
|
+
console.error(`[ExtHost] command "${command}" in ${extId} threw:`, msg);
|
|
118
|
+
return { ok: false, error: msg };
|
|
111
119
|
}
|
|
112
120
|
}
|
|
113
121
|
}
|
|
122
|
+
console.warn(`[ExtHost] command not found: "${command}"`);
|
|
114
123
|
return { ok: false, error: `Command not found: ${command}` };
|
|
115
124
|
});
|
|
116
125
|
|
|
@@ -38,7 +38,7 @@ export function readManifestAt(dir: string): ExtensionManifest | null {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
/** Scan extensions directory for all valid manifests */
|
|
41
|
+
/** Scan extensions directory (node_modules) for all valid manifests */
|
|
42
42
|
export async function discoverManifests(extensionsDir: string): Promise<ExtensionManifest[]> {
|
|
43
43
|
const manifests: ExtensionManifest[] = [];
|
|
44
44
|
if (!existsSync(extensionsDir)) return manifests;
|
|
@@ -63,3 +63,20 @@ export async function discoverManifests(extensionsDir: string): Promise<Extensio
|
|
|
63
63
|
}
|
|
64
64
|
return manifests;
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
export type BundledManifest = ExtensionManifest & { _dir: string };
|
|
68
|
+
|
|
69
|
+
/** Scan packages directory for bundled extensions (ext-* dirs) */
|
|
70
|
+
export async function discoverBundledManifests(packagesDir: string): Promise<BundledManifest[]> {
|
|
71
|
+
const manifests: BundledManifest[] = [];
|
|
72
|
+
if (!existsSync(packagesDir)) return manifests;
|
|
73
|
+
|
|
74
|
+
const entries = await readdir(packagesDir, { withFileTypes: true });
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
if (!entry.isDirectory() || !entry.name.startsWith("ext-")) continue;
|
|
77
|
+
const dir = resolve(packagesDir, entry.name);
|
|
78
|
+
const manifest = readManifestAt(dir);
|
|
79
|
+
if (manifest) manifests.push({ ...manifest, _dir: dir });
|
|
80
|
+
}
|
|
81
|
+
return manifests;
|
|
82
|
+
}
|
|
@@ -112,6 +112,22 @@ export function registerVscodeCompatHandlers(rpc: RpcChannel): void {
|
|
|
112
112
|
return { ok: true };
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
+
// --- open PPM tab (generic, any extension can use) ---
|
|
116
|
+
rpc.onRequest("window:openTab", async (params) => {
|
|
117
|
+
const [tabType, title, projectId, metadata] = params as [
|
|
118
|
+
string, string, string | null, Record<string, unknown> | undefined,
|
|
119
|
+
];
|
|
120
|
+
broadcastExtMsg({ type: "tab:open", tabType, title, projectId, closable: true, metadata });
|
|
121
|
+
return { ok: true };
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// --- switch PPM project ---
|
|
125
|
+
rpc.onRequest("window:switchProject", async (params) => {
|
|
126
|
+
const [projectName] = params as [string];
|
|
127
|
+
broadcastExtMsg({ type: "project:switch", projectName });
|
|
128
|
+
return { ok: true };
|
|
129
|
+
});
|
|
130
|
+
|
|
115
131
|
// --- tree views (forwarded to browser via WS bridge) ---
|
|
116
132
|
rpc.onRequest("window:tree:update", async (params) => {
|
|
117
133
|
const [viewId, items] = params as [string, unknown[]];
|
|
@@ -155,9 +171,11 @@ export function registerVscodeCompatHandlers(rpc: RpcChannel): void {
|
|
|
155
171
|
async function assertSafePath(filePath: string): Promise<string> {
|
|
156
172
|
const { resolve, relative } = await import("node:path");
|
|
157
173
|
const resolved = resolve(filePath);
|
|
158
|
-
// Allow: CWD
|
|
174
|
+
// Allow: CWD, ~/.ppm/extensions/, and all registered project paths
|
|
159
175
|
const { getPpmDir } = await import("./ppm-dir.ts");
|
|
160
|
-
const
|
|
176
|
+
const { configService } = await import("./config.service.ts");
|
|
177
|
+
const projectPaths = configService.get("projects").map((p: { path: string }) => resolve(p.path));
|
|
178
|
+
const allowedRoots = [resolve(process.cwd()), resolve(getPpmDir(), "extensions"), ...projectPaths];
|
|
161
179
|
const isSafe = allowedRoots.some((root) => {
|
|
162
180
|
const rel = relative(root, resolved);
|
|
163
181
|
return !rel.startsWith("..") && !rel.startsWith("/");
|
|
@@ -221,6 +239,54 @@ export function registerVscodeCompatHandlers(rpc: RpcChannel): void {
|
|
|
221
239
|
}
|
|
222
240
|
return results;
|
|
223
241
|
});
|
|
242
|
+
|
|
243
|
+
// --- process spawn (for extensions needing subprocess access) ---
|
|
244
|
+
|
|
245
|
+
const ALLOWED_SPAWN_COMMANDS = new Set(["git", "node", "bun", "npx", "sqlite3"]);
|
|
246
|
+
const BLOCKED_ENV_KEYS = new Set(["PATH", "HOME", "LD_PRELOAD", "DYLD_INSERT_LIBRARIES", "LD_LIBRARY_PATH"]);
|
|
247
|
+
|
|
248
|
+
rpc.onRequest("process:spawn", async (params) => {
|
|
249
|
+
const [cmd, args, cwd, options] = params as [string, string[], string, { timeout?: number; env?: Record<string, string> }?];
|
|
250
|
+
|
|
251
|
+
// Security: command allowlist
|
|
252
|
+
const baseName = cmd.split("/").pop() || cmd;
|
|
253
|
+
if (!ALLOWED_SPAWN_COMMANDS.has(baseName)) {
|
|
254
|
+
throw new Error(`process:spawn: command "${cmd}" not allowed. Allowed: ${[...ALLOWED_SPAWN_COMMANDS].join(", ")}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Security: CWD must be within allowed roots
|
|
258
|
+
const safeCwd = await assertSafePath(cwd);
|
|
259
|
+
|
|
260
|
+
// Security: block dangerous env overrides
|
|
261
|
+
const safeEnv = { ...process.env };
|
|
262
|
+
if (options?.env) {
|
|
263
|
+
for (const [key, val] of Object.entries(options.env)) {
|
|
264
|
+
if (!BLOCKED_ENV_KEYS.has(key)) safeEnv[key] = val;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const timeout = options?.timeout ?? 30_000;
|
|
269
|
+
const proc = Bun.spawn([cmd, ...args], {
|
|
270
|
+
cwd: safeCwd,
|
|
271
|
+
stdout: "pipe",
|
|
272
|
+
stderr: "pipe",
|
|
273
|
+
env: safeEnv,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const timer = setTimeout(() => { try { proc.kill(); } catch {} }, timeout);
|
|
277
|
+
try {
|
|
278
|
+
const [stdout, stderr] = await Promise.all([
|
|
279
|
+
new Response(proc.stdout).text(),
|
|
280
|
+
new Response(proc.stderr).text(),
|
|
281
|
+
]);
|
|
282
|
+
const exitCode = await proc.exited;
|
|
283
|
+
clearTimeout(timer);
|
|
284
|
+
return { stdout, stderr, exitCode };
|
|
285
|
+
} catch (e) {
|
|
286
|
+
clearTimeout(timer);
|
|
287
|
+
throw new Error(`process:spawn failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
224
290
|
}
|
|
225
291
|
|
|
226
292
|
/** Get a nested value from an object by dot-separated key */
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import type { ExtensionManifest, ExtensionInfo, RpcMessage } from "../types/extension.ts";
|
|
4
|
-
import { getExtensions, getExtensionById, insertExtension, getExtensionStorage, setExtensionStorageValue } from "./db.service.ts";
|
|
4
|
+
import { getExtensions, getExtensionById, insertExtension, updateExtension, getExtensionStorage, setExtensionStorageValue } from "./db.service.ts";
|
|
5
5
|
import { contributionRegistry } from "./contribution-registry.ts";
|
|
6
6
|
import { RpcChannel } from "./extension-rpc.ts";
|
|
7
|
-
import { parseManifest, discoverManifests } from "./extension-manifest.ts";
|
|
7
|
+
import { parseManifest, discoverManifests, discoverBundledManifests } from "./extension-manifest.ts";
|
|
8
8
|
import { installExtension, removeExtension, devLinkExtension, ensureExtensionsDir } from "./extension-installer.ts";
|
|
9
9
|
import { registerVscodeCompatHandlers } from "./extension-rpc-handlers.ts";
|
|
10
10
|
import { getPpmDir } from "./ppm-dir.ts";
|
|
@@ -13,8 +13,11 @@ class ExtensionService {
|
|
|
13
13
|
private worker: Worker | null = null;
|
|
14
14
|
private rpc: RpcChannel | null = null;
|
|
15
15
|
private activatedIds = new Set<string>();
|
|
16
|
+
private activationErrors = new Map<string, string>();
|
|
16
17
|
private workerReady = false;
|
|
17
18
|
private installing = new Set<string>();
|
|
19
|
+
private extensionPaths = new Map<string, string>();
|
|
20
|
+
private bundledIds = new Set<string>();
|
|
18
21
|
|
|
19
22
|
// --- Worker lifecycle ---
|
|
20
23
|
|
|
@@ -54,6 +57,9 @@ class ExtensionService {
|
|
|
54
57
|
if (this.worker) { this.worker.terminate(); this.worker = null; }
|
|
55
58
|
this.workerReady = false;
|
|
56
59
|
this.activatedIds.clear();
|
|
60
|
+
this.activationErrors.clear();
|
|
61
|
+
this.extensionPaths.clear();
|
|
62
|
+
this.bundledIds.clear();
|
|
57
63
|
contributionRegistry.clear();
|
|
58
64
|
}
|
|
59
65
|
|
|
@@ -65,7 +71,29 @@ class ExtensionService {
|
|
|
65
71
|
|
|
66
72
|
async discover(): Promise<ExtensionManifest[]> {
|
|
67
73
|
ensureExtensionsDir(resolve(getPpmDir(), "extensions"));
|
|
68
|
-
|
|
74
|
+
|
|
75
|
+
// Discover bundled extensions from packages/ext-*
|
|
76
|
+
const bundledDir = resolve(import.meta.dir, "../../packages");
|
|
77
|
+
const bundled = await discoverBundledManifests(bundledDir);
|
|
78
|
+
for (const m of bundled) {
|
|
79
|
+
this.extensionPaths.set(m.id, m._dir);
|
|
80
|
+
this.bundledIds.add(m.id);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Discover user-installed extensions
|
|
84
|
+
const userExtDir = resolve(getPpmDir(), "extensions");
|
|
85
|
+
const userManifests = await discoverManifests(userExtDir);
|
|
86
|
+
for (const m of userManifests) {
|
|
87
|
+
this.extensionPaths.set(m.id, resolve(userExtDir, "node_modules", m.id));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Merge: user overrides bundled if same id (strip _dir to avoid leaking paths)
|
|
91
|
+
const byId = new Map(bundled.map((m) => {
|
|
92
|
+
const { _dir, ...manifest } = m;
|
|
93
|
+
return [m.id, manifest as ExtensionManifest];
|
|
94
|
+
}));
|
|
95
|
+
for (const m of userManifests) byId.set(m.id, m);
|
|
96
|
+
return [...byId.values()];
|
|
69
97
|
}
|
|
70
98
|
|
|
71
99
|
async install(name: string): Promise<ExtensionManifest> {
|
|
@@ -79,6 +107,9 @@ class ExtensionService {
|
|
|
79
107
|
}
|
|
80
108
|
|
|
81
109
|
async remove(id: string): Promise<void> {
|
|
110
|
+
if (this.bundledIds.has(id)) {
|
|
111
|
+
throw new Error(`Cannot remove bundled extension "${id}". Use 'ppm ext disable ${id}' instead.`);
|
|
112
|
+
}
|
|
82
113
|
if (this.activatedIds.has(id)) await this.deactivate(id);
|
|
83
114
|
await removeExtension(id, resolve(getPpmDir(), "extensions"));
|
|
84
115
|
contributionRegistry.unregister(id);
|
|
@@ -92,7 +123,8 @@ class ExtensionService {
|
|
|
92
123
|
if (!row.enabled) throw new Error(`Extension ${id} is disabled`);
|
|
93
124
|
|
|
94
125
|
const manifest: ExtensionManifest = JSON.parse(row.manifest);
|
|
95
|
-
const extDir =
|
|
126
|
+
const extDir = this.extensionPaths.get(id)
|
|
127
|
+
?? resolve(resolve(getPpmDir(), "extensions"), "node_modules", id);
|
|
96
128
|
const entryPath = resolve(extDir, manifest.main);
|
|
97
129
|
if (!existsSync(entryPath)) throw new Error(`Entry point not found: ${entryPath}`);
|
|
98
130
|
|
|
@@ -111,15 +143,20 @@ class ExtensionService {
|
|
|
111
143
|
const port = cfg.get("port") ?? 8080;
|
|
112
144
|
const baseUrl = `http://localhost:${port}`;
|
|
113
145
|
|
|
146
|
+
console.log(`[ExtService] activating ${id} (entry: ${entryPath})`);
|
|
114
147
|
const result = await rpc.sendRequest<{ ok: boolean; error?: string }>(
|
|
115
148
|
"ext:activate", id, entryPath, extDir, storedState, baseUrl,
|
|
116
149
|
);
|
|
117
|
-
if (!result.ok)
|
|
150
|
+
if (!result.ok) {
|
|
151
|
+
this.activationErrors.set(id, result.error ?? "Unknown activation error");
|
|
152
|
+
throw new Error(`Failed to activate ${id}: ${result.error}`);
|
|
153
|
+
}
|
|
118
154
|
|
|
155
|
+
this.activationErrors.delete(id);
|
|
119
156
|
this.activatedIds.add(id);
|
|
120
157
|
if (manifest.contributes) contributionRegistry.register(id, manifest.contributes);
|
|
121
158
|
this.broadcastContributions();
|
|
122
|
-
console.log(`[ExtService]
|
|
159
|
+
console.log(`[ExtService] activated ${id} successfully`);
|
|
123
160
|
}
|
|
124
161
|
|
|
125
162
|
async deactivate(id: string): Promise<void> {
|
|
@@ -166,7 +203,6 @@ class ExtensionService {
|
|
|
166
203
|
async setEnabled(id: string, enabled: boolean): Promise<void> {
|
|
167
204
|
const row = getExtensionById(id);
|
|
168
205
|
if (!row) throw new Error(`Extension ${id} not found`);
|
|
169
|
-
const { updateExtension } = await import("./db.service.ts");
|
|
170
206
|
updateExtension(id, { enabled: enabled ? 1 : 0 });
|
|
171
207
|
if (enabled && !this.activatedIds.has(id)) await this.activate(id);
|
|
172
208
|
else if (!enabled && this.activatedIds.has(id)) await this.deactivate(id);
|
|
@@ -187,17 +223,29 @@ class ExtensionService {
|
|
|
187
223
|
ensureExtensionsDir(resolve(getPpmDir(), "extensions"));
|
|
188
224
|
const manifests = await this.discover();
|
|
189
225
|
for (const m of manifests) {
|
|
190
|
-
|
|
226
|
+
const existing = getExtensionById(m.id);
|
|
227
|
+
if (!existing) {
|
|
191
228
|
insertExtension({
|
|
192
229
|
id: m.id, version: m.version,
|
|
193
230
|
display_name: m.displayName ?? null, description: m.description ?? null,
|
|
194
231
|
icon: m.icon ?? null, enabled: 1, manifest: JSON.stringify(m),
|
|
195
232
|
});
|
|
233
|
+
} else {
|
|
234
|
+
// Always sync manifest from disk so new contributes (keybindings, etc.) are picked up
|
|
235
|
+
updateExtension(m.id, {
|
|
236
|
+
version: m.version,
|
|
237
|
+
display_name: m.displayName ?? null,
|
|
238
|
+
description: m.description ?? null,
|
|
239
|
+
icon: m.icon ?? null,
|
|
240
|
+
manifest: JSON.stringify(m),
|
|
241
|
+
});
|
|
196
242
|
}
|
|
197
243
|
}
|
|
198
244
|
for (const row of getExtensions()) {
|
|
199
245
|
if (row.enabled !== 1) continue;
|
|
246
|
+
console.log(`[ExtService] startup: activating ${row.id}...`);
|
|
200
247
|
try { await this.activate(row.id); } catch (e) {
|
|
248
|
+
this.activationErrors.set(row.id, e instanceof Error ? e.message : String(e));
|
|
201
249
|
console.error(`[ExtService] Failed to activate ${row.id} on startup:`, e);
|
|
202
250
|
}
|
|
203
251
|
}
|
|
@@ -211,13 +259,19 @@ class ExtensionService {
|
|
|
211
259
|
}
|
|
212
260
|
|
|
213
261
|
isActivated(id: string): boolean { return this.activatedIds.has(id); }
|
|
262
|
+
isBundled(id: string): boolean { return this.bundledIds.has(id); }
|
|
214
263
|
getExtensionsDir(): string { return resolve(getPpmDir(), "extensions"); }
|
|
264
|
+
getActivationErrors(): Map<string, string> { return new Map(this.activationErrors); }
|
|
215
265
|
|
|
216
266
|
/** Push current contributions to all connected browser clients */
|
|
217
267
|
private broadcastContributions(): void {
|
|
218
268
|
try {
|
|
219
269
|
const { broadcastExtMsg } = require("../server/ws/extensions.ts");
|
|
220
|
-
|
|
270
|
+
const contributions = contributionRegistry.getAll();
|
|
271
|
+
broadcastExtMsg(this.activationErrors.size > 0
|
|
272
|
+
? { type: "contributions:update", contributions, activationErrors: Object.fromEntries(this.activationErrors) }
|
|
273
|
+
: { type: "contributions:update", contributions },
|
|
274
|
+
);
|
|
221
275
|
} catch {}
|
|
222
276
|
}
|
|
223
277
|
}
|
|
@@ -49,7 +49,9 @@ export type ExtServerMsg =
|
|
|
49
49
|
| { type: "webview:html"; panelId: string; html: string }
|
|
50
50
|
| { type: "webview:dispose"; panelId: string }
|
|
51
51
|
| { type: "webview:postMessage"; panelId: string; message: unknown }
|
|
52
|
-
| { type: "
|
|
52
|
+
| { type: "tab:open"; tabType: string; title: string; projectId: string | null; closable?: boolean; metadata?: Record<string, unknown> }
|
|
53
|
+
| { type: "project:switch"; projectName: string }
|
|
54
|
+
| { type: "contributions:update"; contributions: ExtensionContributes; activationErrors?: Record<string, string> };
|
|
53
55
|
|
|
54
56
|
// --- Client → Server messages ---
|
|
55
57
|
|
package/src/types/extension.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface ExtensionContributes {
|
|
|
23
23
|
views?: Record<string, ContributedView[]>;
|
|
24
24
|
configuration?: { properties?: Record<string, ConfigProperty> };
|
|
25
25
|
menus?: Record<string, ContributedMenu[]>;
|
|
26
|
+
keybindings?: ContributedKeybinding[];
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export interface ContributedCommand {
|
|
@@ -52,6 +53,13 @@ export interface ContributedMenu {
|
|
|
52
53
|
group?: string;
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
export interface ContributedKeybinding {
|
|
57
|
+
command: string;
|
|
58
|
+
key: string;
|
|
59
|
+
mac?: string;
|
|
60
|
+
when?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
55
63
|
/** Runtime extension info returned by API */
|
|
56
64
|
export interface ExtensionInfo {
|
|
57
65
|
id: string;
|
|
@@ -340,22 +340,28 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
340
340
|
);
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
// Register CodeLens for inline Run buttons on .sql files
|
|
343
|
+
// Register CodeLens for inline Run buttons on .sql files (scoped to this editor's model)
|
|
344
344
|
if (isSql) {
|
|
345
345
|
codeLensDisposable.current.forEach((d) => d.dispose());
|
|
346
346
|
codeLensDisposable.current = [];
|
|
347
347
|
|
|
348
|
+
const thisModel = editor.getModel();
|
|
348
349
|
const cmdId = editor.addCommand(0, (_accessor: unknown, sql: string) => {
|
|
349
350
|
if (sql) runSqlRef.current(sql);
|
|
350
351
|
});
|
|
351
352
|
|
|
352
|
-
if (cmdId) {
|
|
353
|
+
if (cmdId && thisModel) {
|
|
353
354
|
const provider = monaco.languages.registerCodeLensProvider("sql", {
|
|
354
355
|
provideCodeLenses: (model: MonacoType.editor.ITextModel) => {
|
|
356
|
+
// Only provide lenses for THIS editor's model, not all SQL models
|
|
357
|
+
if (model !== thisModel) return { lenses: [], dispose: () => {} };
|
|
358
|
+
|
|
355
359
|
const lenses: MonacoType.languages.CodeLens[] = [];
|
|
356
|
-
const
|
|
360
|
+
const text = model.getValue();
|
|
361
|
+
const lines = text.split("\n");
|
|
357
362
|
let stmtStartLine = -1;
|
|
358
363
|
let stmtLines: string[] = [];
|
|
364
|
+
let dollarBlock = false; // Track DO $$ ... $$ blocks
|
|
359
365
|
|
|
360
366
|
const addLens = (line: number, stmt: string) => {
|
|
361
367
|
const trimmed = stmt.trim();
|
|
@@ -374,7 +380,13 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
374
380
|
stmtLines = [];
|
|
375
381
|
}
|
|
376
382
|
stmtLines.push(lines[i]!);
|
|
377
|
-
|
|
383
|
+
|
|
384
|
+
// Detect $$ dollar-quoted block start/end
|
|
385
|
+
const dollarMatches = (trimmed.match(/\$\$/g) || []).length;
|
|
386
|
+
if (dollarMatches % 2 === 1) dollarBlock = !dollarBlock;
|
|
387
|
+
|
|
388
|
+
// Only split on ; when NOT inside a $$ block
|
|
389
|
+
if (!dollarBlock && trimmed.endsWith(";")) {
|
|
378
390
|
addLens(stmtStartLine, stmtLines.join("\n"));
|
|
379
391
|
stmtStartLine = -1;
|
|
380
392
|
stmtLines = [];
|