@vincenthanxiaodu/pi-web 1.202608.1
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/LICENSE +21 -0
- package/README.md +185 -0
- package/dist/cli.js +1149 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/apple-touch-icon.png +0 -0
- package/dist/client/assets/CodeViewer-D_Q6ljLV.js +4 -0
- package/dist/client/assets/TerminalPanel-CTGixJIz.js +187 -0
- package/dist/client/assets/index-C2phil_t.js +5637 -0
- package/dist/client/assets/vendor-editor-core-CXO8gGab.js +12 -0
- package/dist/client/assets/vendor-editor-languages-CpW4sJsX.js +46 -0
- package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +1 -0
- package/dist/client/assets/vendor-terminal-BrP-ENHg.css +1 -0
- package/dist/client/assets/vendor-terminal-D8k4UKM2.js +35 -0
- package/dist/client/favicon.svg +11 -0
- package/dist/client/index.html +124 -0
- package/dist/client/manifest.webmanifest +24 -0
- package/dist/client/pwa-icon-192.png +0 -0
- package/dist/client/pwa-icon-512.png +0 -0
- package/dist/config.js +505 -0
- package/dist/config.js.map +1 -0
- package/dist/docker/piWebDockerCommandPlan.js +212 -0
- package/dist/docker/piWebDockerCommandPlan.js.map +1 -0
- package/dist/environment.js +4 -0
- package/dist/environment.js.map +1 -0
- package/dist/nativeServices/installedServiceDefinitions.js +602 -0
- package/dist/nativeServices/installedServiceDefinitions.js.map +1 -0
- package/dist/nativeServices/serviceAction.js +174 -0
- package/dist/nativeServices/serviceAction.js.map +1 -0
- package/dist/nativeServices/serviceDoctor.js +783 -0
- package/dist/nativeServices/serviceDoctor.js.map +1 -0
- package/dist/nativeServices/serviceInstall.js +35 -0
- package/dist/nativeServices/serviceInstall.js.map +1 -0
- package/dist/nativeServices/servicePlan.js +358 -0
- package/dist/nativeServices/servicePlan.js.map +1 -0
- package/dist/nativeServices/serviceProbe.js +447 -0
- package/dist/nativeServices/serviceProbe.js.map +1 -0
- package/dist/nativeServices/serviceRendering.js +132 -0
- package/dist/nativeServices/serviceRendering.js.map +1 -0
- package/dist/pi-web-plugins/git/browser/git-contract.js +108 -0
- package/dist/pi-web-plugins/git/browser/git-panel.js +609 -0
- package/dist/pi-web-plugins/git/browser/gitFileList.js +43 -0
- package/dist/pi-web-plugins/git/browser/gitFileShared.js +16 -0
- package/dist/pi-web-plugins/git/browser/gitFileTree.js +81 -0
- package/dist/pi-web-plugins/git/browser/gitFileViewPreference.js +35 -0
- package/dist/pi-web-plugins/git/browser/gitRoute.js +42 -0
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +10 -0
- package/dist/pi-web-plugins/git/browser/unifiedDiff.js +301 -0
- package/dist/pi-web-plugins/git/git-backend.js +439 -0
- package/dist/pi-web-plugins/git/package.json +16 -0
- package/dist/pi-web-plugins/git/server-plugin.js +176 -0
- package/dist/pi-web-plugins/info/infoInternals.js +222 -0
- package/dist/pi-web-plugins/info/package.json +9 -0
- package/dist/pi-web-plugins/info/pi-web-plugin.js +48 -0
- package/dist/pi-web-plugins/relays/markdownDocument.js +81 -0
- package/dist/pi-web-plugins/relays/package.json +9 -0
- package/dist/pi-web-plugins/relays/pi-web-plugin.js +44 -0
- package/dist/pi-web-plugins/relays/relayDiscovery.js +228 -0
- package/dist/pi-web-plugins/relays/relaysPanelElement.js +550 -0
- package/dist/pi-web-plugins/relays/vendor/README.md +24 -0
- package/dist/pi-web-plugins/relays/vendor/marked.esm.js +76 -0
- package/dist/pi-web-plugins/updates/package.json +9 -0
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +185 -0
- package/dist/pi-web-plugins/updates/updatesLogic.js +97 -0
- package/dist/pi-web-plugins/workspace-tasks/config.js +91 -0
- package/dist/pi-web-plugins/workspace-tasks/package.json +9 -0
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +48 -0
- package/dist/pi-web-plugins/workspace-tasks/taskRunner.js +12 -0
- package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +288 -0
- package/dist/pi-web-plugins/workspace-tasks/workspaceTasksClient.js +47 -0
- package/dist/piWebVersionReport.js +278 -0
- package/dist/piWebVersionReport.js.map +1 -0
- package/dist/plugin-api.d.ts +222 -0
- package/dist/pluginRecoveryCli.js +172 -0
- package/dist/pluginRecoveryCli.js.map +1 -0
- package/dist/server/activeAgentProfileProvider.js +25 -0
- package/dist/server/activeAgentProfileProvider.js.map +1 -0
- package/dist/server/activity/workspaceActivityService.js +106 -0
- package/dist/server/activity/workspaceActivityService.js.map +1 -0
- package/dist/server/app.js +218 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/browserMessageProjection.js +54 -0
- package/dist/server/browserMessageProjection.js.map +1 -0
- package/dist/server/configRoutes.js +293 -0
- package/dist/server/configRoutes.js.map +1 -0
- package/dist/server/diagnostics/nodePtyNativeModule.js +36 -0
- package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -0
- package/dist/server/diagnostics/nodePtySpawnHelper.js +135 -0
- package/dist/server/diagnostics/nodePtySpawnHelper.js.map +1 -0
- package/dist/server/git/gitEnv.js +15 -0
- package/dist/server/git/gitEnv.js.map +1 -0
- package/dist/server/goals/goalArchive.js +184 -0
- package/dist/server/goals/goalArchive.js.map +1 -0
- package/dist/server/goals/goalFile.js +200 -0
- package/dist/server/goals/goalFile.js.map +1 -0
- package/dist/server/goals/goalStore.js +65 -0
- package/dist/server/goals/goalStore.js.map +1 -0
- package/dist/server/index.js +7 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/machines/machineClient.js +191 -0
- package/dist/server/machines/machineClient.js.map +1 -0
- package/dist/server/machines/machinePluginProxyRoutes.js +247 -0
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -0
- package/dist/server/machines/machineProxyRoutes.js +318 -0
- package/dist/server/machines/machineProxyRoutes.js.map +1 -0
- package/dist/server/machines/machineRoutes.js +56 -0
- package/dist/server/machines/machineRoutes.js.map +1 -0
- package/dist/server/machines/machineService.js +274 -0
- package/dist/server/machines/machineService.js.map +1 -0
- package/dist/server/machines/machineStore.js +143 -0
- package/dist/server/machines/machineStore.js.map +1 -0
- package/dist/server/piPackageRoutes.js +91 -0
- package/dist/server/piPackageRoutes.js.map +1 -0
- package/dist/server/piPackageService.js +101 -0
- package/dist/server/piPackageService.js.map +1 -0
- package/dist/server/piWebPluginCatalog.js +584 -0
- package/dist/server/piWebPluginCatalog.js.map +1 -0
- package/dist/server/piWebPluginLifecycle.js +162 -0
- package/dist/server/piWebPluginLifecycle.js.map +1 -0
- package/dist/server/piWebPluginService.js +224 -0
- package/dist/server/piWebPluginService.js.map +1 -0
- package/dist/server/piWebReleaseLookupCache.js +37 -0
- package/dist/server/piWebReleaseLookupCache.js.map +1 -0
- package/dist/server/piWebStatus.js +624 -0
- package/dist/server/piWebStatus.js.map +1 -0
- package/dist/server/piWebStatusCache.js +45 -0
- package/dist/server/piWebStatusCache.js.map +1 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js +69 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -0
- package/dist/server/plugins/serverPluginExec.js +184 -0
- package/dist/server/plugins/serverPluginExec.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +480 -0
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -0
- package/dist/server/projectTrustRoutes.js +80 -0
- package/dist/server/projectTrustRoutes.js.map +1 -0
- package/dist/server/projects/directorySuggestions.js +207 -0
- package/dist/server/projects/directorySuggestions.js.map +1 -0
- package/dist/server/projects/projectService.js +33 -0
- package/dist/server/projects/projectService.js.map +1 -0
- package/dist/server/realtime/sessionEventHub.js +93 -0
- package/dist/server/realtime/sessionEventHub.js.map +1 -0
- package/dist/server/requestCancellation.js +32 -0
- package/dist/server/requestCancellation.js.map +1 -0
- package/dist/server/sessiond/agentHttpDispatcher.js +108 -0
- package/dist/server/sessiond/agentHttpDispatcher.js.map +1 -0
- package/dist/server/sessiond/agentProcessEnvironment.js +61 -0
- package/dist/server/sessiond/agentProcessEnvironment.js.map +1 -0
- package/dist/server/sessiond/pluginBackendRoutes.js +64 -0
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js +31 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +61 -0
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionServiceDependencies.js +34 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -0
- package/dist/server/sessiond/sessiondStateOwnership.js +235 -0
- package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js +31 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js +41 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -0
- package/dist/server/sessiond.js +387 -0
- package/dist/server/sessiond.js.map +1 -0
- package/dist/server/sessions/askUserTool.js +102 -0
- package/dist/server/sessions/askUserTool.js.map +1 -0
- package/dist/server/sessions/attachmentService.js +119 -0
- package/dist/server/sessions/attachmentService.js.map +1 -0
- package/dist/server/sessions/authProviderOptions.js +50 -0
- package/dist/server/sessions/authProviderOptions.js.map +1 -0
- package/dist/server/sessions/authRoutes.js +59 -0
- package/dist/server/sessions/authRoutes.js.map +1 -0
- package/dist/server/sessions/authService.js +122 -0
- package/dist/server/sessions/authService.js.map +1 -0
- package/dist/server/sessions/builtinCommands.js +27 -0
- package/dist/server/sessions/builtinCommands.js.map +1 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js +254 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/editPreview.js +196 -0
- package/dist/server/sessions/editPreview.js.map +1 -0
- package/dist/server/sessions/extensionDialogWaiters.js +76 -0
- package/dist/server/sessions/extensionDialogWaiters.js.map +1 -0
- package/dist/server/sessions/globalProviderPolicy.js +167 -0
- package/dist/server/sessions/globalProviderPolicy.js.map +1 -0
- package/dist/server/sessions/interruptedRunStore.js +147 -0
- package/dist/server/sessions/interruptedRunStore.js.map +1 -0
- package/dist/server/sessions/messagePaging.js +43 -0
- package/dist/server/sessions/messagePaging.js.map +1 -0
- package/dist/server/sessions/modelCatalogRefresher.js +193 -0
- package/dist/server/sessions/modelCatalogRefresher.js.map +1 -0
- package/dist/server/sessions/oauthLoginFlowService.js +346 -0
- package/dist/server/sessions/oauthLoginFlowService.js.map +1 -0
- package/dist/server/sessions/pendingAskStore.js +275 -0
- package/dist/server/sessions/pendingAskStore.js.map +1 -0
- package/dist/server/sessions/pendingExtensionDialogStore.js +196 -0
- package/dist/server/sessions/pendingExtensionDialogStore.js.map +1 -0
- package/dist/server/sessions/piSessionManagerGateway.js +246 -0
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +4059 -0
- package/dist/server/sessions/piSessionService.js.map +1 -0
- package/dist/server/sessions/plainTextTheme.js +112 -0
- package/dist/server/sessions/plainTextTheme.js.map +1 -0
- package/dist/server/sessions/sessionArchiveStore.js +273 -0
- package/dist/server/sessions/sessionArchiveStore.js.map +1 -0
- package/dist/server/sessions/sessionArchiveTree.js +35 -0
- package/dist/server/sessions/sessionArchiveTree.js.map +1 -0
- package/dist/server/sessions/sessionCleanup.js +157 -0
- package/dist/server/sessions/sessionCleanup.js.map +1 -0
- package/dist/server/sessions/sessionCommandService.js +336 -0
- package/dist/server/sessions/sessionCommandService.js.map +1 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js +48 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/sessionFileFormat.js +27 -0
- package/dist/server/sessions/sessionFileFormat.js.map +1 -0
- package/dist/server/sessions/sessionFileHeader.js +104 -0
- package/dist/server/sessions/sessionFileHeader.js.map +1 -0
- package/dist/server/sessions/sessionModelScope.js +141 -0
- package/dist/server/sessions/sessionModelScope.js.map +1 -0
- package/dist/server/sessions/sessionNameGenerator.js +90 -0
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -0
- package/dist/server/sessions/sessionNotificationStore.js +484 -0
- package/dist/server/sessions/sessionNotificationStore.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +744 -0
- package/dist/server/sessions/sessionRoutes.js.map +1 -0
- package/dist/server/sessions/sessionRuntimeStore.js +2 -0
- package/dist/server/sessions/sessionRuntimeStore.js.map +1 -0
- package/dist/server/sessions/sessionService.js +2 -0
- package/dist/server/sessions/sessionService.js.map +1 -0
- package/dist/server/sessions/sessionSummaryScanner.js +491 -0
- package/dist/server/sessions/sessionSummaryScanner.js.map +1 -0
- package/dist/server/sessions/sessionTreeProjection.js +250 -0
- package/dist/server/sessions/sessionTreeProjection.js.map +1 -0
- package/dist/server/sessions/sessionUnreadStore.js +520 -0
- package/dist/server/sessions/sessionUnreadStore.js.map +1 -0
- package/dist/server/sessions/shutdownDrain.js +73 -0
- package/dist/server/sessions/shutdownDrain.js.map +1 -0
- package/dist/server/sessions/spawnSessionTool.js +48 -0
- package/dist/server/sessions/spawnSessionTool.js.map +1 -0
- package/dist/server/sessions/spawnSubsessionTool.js +216 -0
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -0
- package/dist/server/sessions/spawnTargetResolver.js +23 -0
- package/dist/server/sessions/spawnTargetResolver.js.map +1 -0
- package/dist/server/sessions/subsessionTranscript.js +235 -0
- package/dist/server/sessions/subsessionTranscript.js.map +1 -0
- package/dist/server/status/machineStatusRoutes.js +8 -0
- package/dist/server/status/machineStatusRoutes.js.map +1 -0
- package/dist/server/status/machineStatusService.js +181 -0
- package/dist/server/status/machineStatusService.js.map +1 -0
- package/dist/server/status/workspaceAttribution.js +87 -0
- package/dist/server/status/workspaceAttribution.js.map +1 -0
- package/dist/server/storage/projectStore.js +88 -0
- package/dist/server/storage/projectStore.js.map +1 -0
- package/dist/server/terminalProxyRoutes.js +141 -0
- package/dist/server/terminalProxyRoutes.js.map +1 -0
- package/dist/server/terminals/terminalRoutes.js +155 -0
- package/dist/server/terminals/terminalRoutes.js.map +1 -0
- package/dist/server/terminals/terminalService.js +305 -0
- package/dist/server/terminals/terminalService.js.map +1 -0
- package/dist/server/terminals/terminalSize.js +17 -0
- package/dist/server/terminals/terminalSize.js.map +1 -0
- package/dist/server/types.js +2 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server/updates/fleetRoutes.js +111 -0
- package/dist/server/updates/fleetRoutes.js.map +1 -0
- package/dist/server/updates/restartRoutes.js +48 -0
- package/dist/server/updates/restartRoutes.js.map +1 -0
- package/dist/server/updates/selfUpdateRoutes.js +145 -0
- package/dist/server/updates/selfUpdateRoutes.js.map +1 -0
- package/dist/server/webSocketBridge.js +32 -0
- package/dist/server/webSocketBridge.js.map +1 -0
- package/dist/server/workingDirectory.js +44 -0
- package/dist/server/workingDirectory.js.map +1 -0
- package/dist/server/workspaceExplorerRoutes.js +147 -0
- package/dist/server/workspaceExplorerRoutes.js.map +1 -0
- package/dist/server/workspaces/effectivePathAccess.js +8 -0
- package/dist/server/workspaces/effectivePathAccess.js.map +1 -0
- package/dist/server/workspaces/fileContentService.js +190 -0
- package/dist/server/workspaces/fileContentService.js.map +1 -0
- package/dist/server/workspaces/filePreviewResponseHeaders.js +18 -0
- package/dist/server/workspaces/filePreviewResponseHeaders.js.map +1 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js +55 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js.map +1 -0
- package/dist/server/workspaces/filePreviewService.js +81 -0
- package/dist/server/workspaces/filePreviewService.js.map +1 -0
- package/dist/server/workspaces/fileSuggestions.js +520 -0
- package/dist/server/workspaces/fileSuggestions.js.map +1 -0
- package/dist/server/workspaces/fileTreeService.js +35 -0
- package/dist/server/workspaces/fileTreeService.js.map +1 -0
- package/dist/server/workspaces/pathAccessPolicy.js +90 -0
- package/dist/server/workspaces/pathAccessPolicy.js.map +1 -0
- package/dist/server/workspaces/pathSafety.js +45 -0
- package/dist/server/workspaces/pathSafety.js.map +1 -0
- package/dist/server/workspaces/projectPiWebConfig.js +61 -0
- package/dist/server/workspaces/projectPiWebConfig.js.map +1 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js +17 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -0
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +402 -0
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceCatalog.js +47 -0
- package/dist/server/workspaces/workspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceContext.js +6 -0
- package/dist/server/workspaces/workspaceContext.js.map +1 -0
- package/dist/server/workspaces/workspaceDeletionRoutes.js +49 -0
- package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js +651 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -0
- package/dist/server/workspaces/workspaceRemovalService.js +256 -0
- package/dist/server/workspaces/workspaceRemovalService.js.map +1 -0
- package/dist/server/workspaces/workspaceRouteErrors.js +7 -0
- package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js +39 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js.map +1 -0
- package/dist/server-plugin-api.d.ts +140 -0
- package/dist/server-plugin-api.js +2 -0
- package/dist/server-plugin-api.js.map +1 -0
- package/dist/serverPluginRecovery.js +138 -0
- package/dist/serverPluginRecovery.js.map +1 -0
- package/dist/sessiond/activeAgentProfile.js +12 -0
- package/dist/sessiond/activeAgentProfile.js.map +1 -0
- package/dist/sessiond/config.js +20 -0
- package/dist/sessiond/config.js.map +1 -0
- package/dist/sessiond/sessionDaemonClient.js +105 -0
- package/dist/sessiond/sessionDaemonClient.js.map +1 -0
- package/dist/shared/activeAgentProfile.js +37 -0
- package/dist/shared/activeAgentProfile.js.map +1 -0
- package/dist/shared/activity.js +16 -0
- package/dist/shared/activity.js.map +1 -0
- package/dist/shared/apiTypes.js +41 -0
- package/dist/shared/apiTypes.js.map +1 -0
- package/dist/shared/capabilities.js +37 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/federatedRoutes.js +127 -0
- package/dist/shared/federatedRoutes.js.map +1 -0
- package/dist/shared/machinePluginIds.js +43 -0
- package/dist/shared/machinePluginIds.js.map +1 -0
- package/dist/shared/machineStatus.js +94 -0
- package/dist/shared/machineStatus.js.map +1 -0
- package/dist/shared/piWebStatusParsing.js +141 -0
- package/dist/shared/piWebStatusParsing.js.map +1 -0
- package/dist/shared/pluginApiTypes.d.ts +150 -0
- package/dist/shared/pluginApiTypes.js +3 -0
- package/dist/shared/pluginApiTypes.js.map +1 -0
- package/dist/shared/pluginBackendProtocol.js +110 -0
- package/dist/shared/pluginBackendProtocol.js.map +1 -0
- package/dist/shared/pluginIds.js +10 -0
- package/dist/shared/pluginIds.js.map +1 -0
- package/dist/shared/pluginRecoveryCommands.js +14 -0
- package/dist/shared/pluginRecoveryCommands.js.map +1 -0
- package/dist/shared/promptAttachments.js +92 -0
- package/dist/shared/promptAttachments.js.map +1 -0
- package/dist/shared/selfUpdate.js +3 -0
- package/dist/shared/selfUpdate.js.map +1 -0
- package/dist/shared/thinkingLevels.js +31 -0
- package/dist/shared/thinkingLevels.js.map +1 -0
- package/dist/shared/workspaceDeletion.js +12 -0
- package/dist/shared/workspaceDeletion.js.map +1 -0
- package/dist/shared/workspaceFiles.js +42 -0
- package/dist/shared/workspaceFiles.js.map +1 -0
- package/dist/shared/workspaceRemovalProtocol.js +24 -0
- package/dist/shared/workspaceRemovalProtocol.js.map +1 -0
- package/docs/assets/favicon.svg +11 -0
- package/docs/assets/pi-web-banner.png +0 -0
- package/docs/assets/pi-web-desktop.png +0 -0
- package/docs/assets/pi-web-mobile.png +0 -0
- package/docs/assets/pi-web-tablet.png +0 -0
- package/docs/config.md +444 -0
- package/docs/plugins.md +1305 -0
- package/examples/workspace-provider-plugin/README.md +52 -0
- package/examples/workspace-provider-plugin/package.json +25 -0
- package/examples/workspace-provider-plugin/src/browser/index.ts +69 -0
- package/examples/workspace-provider-plugin/src/server.ts +64 -0
- package/examples/workspace-provider-plugin/tsconfig.json +21 -0
- package/extensions/README-pi-web-restart-migration.md +45 -0
- package/extensions/pi-web.ts +324 -0
- package/install.sh +5 -0
- package/package.json +173 -0
- package/plugin-api.d.ts +1 -0
- package/server-plugin-api.d.ts +1 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/git/git-backend.ts. Do not edit directly.
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { realpath } from "node:fs/promises";
|
|
4
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
5
|
+
import { GIT_DIFF_OPERATION, GIT_STATUS_OPERATION, } from "./browser/git-contract.js";
|
|
6
|
+
export { GIT_DIFF_OPERATION, GIT_STATUS_OPERATION } from "./browser/git-contract.js";
|
|
7
|
+
const GIT_COMMAND_TIMEOUT_MS = 10_000;
|
|
8
|
+
const GIT_LOCAL_ENV_VARS = Object.freeze([
|
|
9
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
10
|
+
"GIT_COMMON_DIR",
|
|
11
|
+
"GIT_DIR",
|
|
12
|
+
"GIT_INDEX_FILE",
|
|
13
|
+
"GIT_OBJECT_DIRECTORY",
|
|
14
|
+
"GIT_PREFIX",
|
|
15
|
+
"GIT_QUARANTINE_PATH",
|
|
16
|
+
"GIT_WORK_TREE",
|
|
17
|
+
]);
|
|
18
|
+
/** Dispatch the Git-owned status/diff schema through the provider's public request seam. */
|
|
19
|
+
export async function requestGitBackend(activationContext, request) {
|
|
20
|
+
const runGit = createGitRunner(activationContext, request.signal);
|
|
21
|
+
if (request.operation === GIT_STATUS_OPERATION) {
|
|
22
|
+
requireStatusInput(request.input);
|
|
23
|
+
return statusProviderResponse(await gitStatusWithRunner(runGit, request.workspace.path));
|
|
24
|
+
}
|
|
25
|
+
if (request.operation === GIT_DIFF_OPERATION) {
|
|
26
|
+
return diffProviderResponse(await gitDiffWithRunner(runGit, request.workspace.path, parseDiffInput(request.input)));
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`Unsupported Git workspace backend operation: ${request.operation}`);
|
|
29
|
+
}
|
|
30
|
+
export async function gitStatus(context, cwd, signal) {
|
|
31
|
+
return gitStatusWithRunner(createGitRunner(context, signal), cwd);
|
|
32
|
+
}
|
|
33
|
+
async function gitStatusWithRunner(runGit, cwd) {
|
|
34
|
+
const result = await runGit(cwd, ["status", "--porcelain=v2", "--branch", "--untracked-files=all", "-z"]);
|
|
35
|
+
if (result.code !== 0)
|
|
36
|
+
return { isGitRepo: false, hash: hash(result.stdout + result.stderr), files: [], submodules: [] };
|
|
37
|
+
const parsed = parseStatus(result.stdout, { deferSubmodules: true });
|
|
38
|
+
return expandSubmodules(runGit, cwd, parsed, result.stdout);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Merge each dirty submodule's own changes into the flat file list. A moved
|
|
42
|
+
* commit pointer becomes a single entry keyed by the submodule path (carrying
|
|
43
|
+
* the short SHAs for display); modified/untracked content is listed as regular
|
|
44
|
+
* entries under `<submodule>/<inner path>`. A plain `-dirty` pointer (commit
|
|
45
|
+
* unchanged) is intentionally not surfaced as a pointer entry.
|
|
46
|
+
*/
|
|
47
|
+
async function expandSubmodules(runGit, cwd, parsed, topRaw) {
|
|
48
|
+
// Fan out concurrently — one `git status` per dirty submodule plus one
|
|
49
|
+
// `git rev-parse` per unstaged pointer move — then concatenate in input
|
|
50
|
+
// order so the file list and hash are identical to a serial pass.
|
|
51
|
+
const canonicalRoot = parsed.submodules.length === 0 ? undefined : await canonicalPath(cwd);
|
|
52
|
+
const expanded = await Promise.all(parsed.submodules.map(async (sub) => {
|
|
53
|
+
const location = canonicalRoot === undefined
|
|
54
|
+
? undefined
|
|
55
|
+
: await validatedSubmodule(runGit, cwd, canonicalRoot, sub.path);
|
|
56
|
+
return { path: sub.path, ...(await expandSubmodule(runGit, sub, location)) };
|
|
57
|
+
}));
|
|
58
|
+
const files = [...parsed.files];
|
|
59
|
+
const dirtySubmodulePaths = [];
|
|
60
|
+
let extraForHash = "";
|
|
61
|
+
for (const part of expanded) {
|
|
62
|
+
dirtySubmodulePaths.push(part.path);
|
|
63
|
+
files.push(...part.files);
|
|
64
|
+
extraForHash += part.extraForHash;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
isGitRepo: true,
|
|
68
|
+
hash: hash(topRaw + extraForHash),
|
|
69
|
+
...(parsed.branch === undefined ? {} : { branch: parsed.branch }),
|
|
70
|
+
...(parsed.upstream === undefined ? {} : { upstream: parsed.upstream }),
|
|
71
|
+
...(parsed.ahead === undefined ? {} : { ahead: parsed.ahead }),
|
|
72
|
+
...(parsed.behind === undefined ? {} : { behind: parsed.behind }),
|
|
73
|
+
files,
|
|
74
|
+
submodules: dirtySubmodulePaths,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Expand one dirty submodule: the pointer entry first, then its inner files. */
|
|
78
|
+
async function expandSubmodule(runGit, sub, location) {
|
|
79
|
+
const files = [];
|
|
80
|
+
let extraForHash = "";
|
|
81
|
+
if (sub.commitChanged) {
|
|
82
|
+
files.push({
|
|
83
|
+
path: sub.path,
|
|
84
|
+
index: sub.index,
|
|
85
|
+
workingTree: sub.workingTree,
|
|
86
|
+
submoduleFromCommit: displayFromCommit(sub.headOid),
|
|
87
|
+
submoduleToCommit: short(await resolveSubmoduleToCommit(runGit, location?.cwd, sub)),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if ((sub.hasModifiedContent || sub.hasUntrackedContent) && location !== undefined) {
|
|
91
|
+
const inner = await runGit(location.cwd, ["status", "--porcelain=v2", "--untracked-files=all", "-z"]);
|
|
92
|
+
if (inner.code === 0) {
|
|
93
|
+
extraForHash = `\0${sub.path}\0${inner.stdout}`;
|
|
94
|
+
const innerFiles = parseStatus(inner.stdout, { deferSubmodules: false }).files;
|
|
95
|
+
for (const file of innerFiles) {
|
|
96
|
+
files.push({
|
|
97
|
+
...file,
|
|
98
|
+
path: `${sub.path}/${file.path}`,
|
|
99
|
+
...(file.oldPath === undefined ? {} : { oldPath: `${sub.path}/${file.oldPath}` }),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// non-zero exit: uninitialized / unreadable submodule — skip silently
|
|
104
|
+
}
|
|
105
|
+
return { files, extraForHash };
|
|
106
|
+
}
|
|
107
|
+
async function resolveSubmoduleToCommit(runGit, cwd, sub) {
|
|
108
|
+
// Staged pointer moves already expose the new commit as the index OID; an
|
|
109
|
+
// unstaged move only records the old OID, so read the validated submodule's
|
|
110
|
+
// HEAD. An unavailable checkout cannot safely supply a different pointer.
|
|
111
|
+
if (sub.indexOid !== sub.headOid || cwd === undefined)
|
|
112
|
+
return sub.indexOid;
|
|
113
|
+
const head = await runGit(cwd, ["rev-parse", "HEAD"]);
|
|
114
|
+
const resolved = head.stdout.trim();
|
|
115
|
+
return head.code === 0 && resolved !== "" ? resolved : sub.indexOid;
|
|
116
|
+
}
|
|
117
|
+
export async function gitDiff(context, cwd, options, signal) {
|
|
118
|
+
return gitDiffWithRunner(createGitRunner(context, signal), cwd, options);
|
|
119
|
+
}
|
|
120
|
+
async function gitDiffWithRunner(runGit, cwd, options) {
|
|
121
|
+
const staged = options.staged === true;
|
|
122
|
+
let path;
|
|
123
|
+
if (options.path !== undefined && options.path !== "")
|
|
124
|
+
path = normalizeRelativePath(options.path);
|
|
125
|
+
if (path !== undefined) {
|
|
126
|
+
const owner = await submoduleForPath(runGit, cwd, path);
|
|
127
|
+
if (owner !== undefined)
|
|
128
|
+
return submoduleDiff(runGit, owner, path, staged);
|
|
129
|
+
}
|
|
130
|
+
const args = ["diff", "--no-ext-diff", "--color=never"];
|
|
131
|
+
if (staged)
|
|
132
|
+
args.push("--cached");
|
|
133
|
+
if (path !== undefined)
|
|
134
|
+
args.push("--", path);
|
|
135
|
+
const result = await runGit(cwd, args);
|
|
136
|
+
if (result.code !== 0)
|
|
137
|
+
throw new Error(result.stderr.trim() || "git diff failed");
|
|
138
|
+
if (!staged && path !== undefined && result.stdout === "" && await isUntracked(runGit, cwd, path)) {
|
|
139
|
+
const untracked = await runGit(cwd, ["diff", "--no-ext-diff", "--color=never", "--no-index", "/dev/null", "--", path]);
|
|
140
|
+
if (untracked.code !== 0 && untracked.code !== 1)
|
|
141
|
+
throw new Error(untracked.stderr.trim() || "git diff failed");
|
|
142
|
+
return { path, staged, hash: hash(untracked.stdout), diff: untracked.stdout, truncated: untracked.truncated };
|
|
143
|
+
}
|
|
144
|
+
return { ...(path === undefined ? {} : { path }), staged, hash: hash(result.stdout), diff: result.stdout, truncated: result.truncated };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Run the diff inside the owning submodule's working tree, since `git diff` at
|
|
148
|
+
* the superproject root never shows content changes below a submodule boundary.
|
|
149
|
+
* The response path stays the full superproject-relative path so the viewer and
|
|
150
|
+
* the selected row line up.
|
|
151
|
+
*/
|
|
152
|
+
async function submoduleDiff(runGit, owner, path, staged) {
|
|
153
|
+
const subCwd = owner.cwd;
|
|
154
|
+
const rel = normalizeRelativePath(path.slice(owner.path.length + 1));
|
|
155
|
+
const args = ["diff", "--no-ext-diff", "--color=never"];
|
|
156
|
+
if (staged)
|
|
157
|
+
args.push("--cached");
|
|
158
|
+
args.push("--", rel);
|
|
159
|
+
const result = await runGit(subCwd, args);
|
|
160
|
+
if (result.code !== 0)
|
|
161
|
+
throw new Error(result.stderr.trim() || "git diff failed");
|
|
162
|
+
if (!staged && result.stdout === "" && await isUntracked(runGit, subCwd, rel)) {
|
|
163
|
+
const untracked = await runGit(subCwd, ["diff", "--no-ext-diff", "--color=never", "--no-index", "/dev/null", "--", rel]);
|
|
164
|
+
if (untracked.code !== 0 && untracked.code !== 1)
|
|
165
|
+
throw new Error(untracked.stderr.trim() || "git diff failed");
|
|
166
|
+
return { path, staged, hash: hash(untracked.stdout), diff: untracked.stdout, truncated: untracked.truncated };
|
|
167
|
+
}
|
|
168
|
+
return { path, staged, hash: hash(result.stdout), diff: result.stdout, truncated: result.truncated };
|
|
169
|
+
}
|
|
170
|
+
async function isUntracked(runGit, cwd, path) {
|
|
171
|
+
const result = await runGit(cwd, ["ls-files", "--others", "--exclude-standard", "-z", "--", path]);
|
|
172
|
+
return result.code === 0 && result.stdout.split("\0").includes(path);
|
|
173
|
+
}
|
|
174
|
+
/** Configured direct-submodule paths (depth 1), read from `.gitmodules`. */
|
|
175
|
+
async function configuredSubmodulePaths(runGit, cwd) {
|
|
176
|
+
// `-z` emits `<key>\n<value>\0` records; keys may themselves contain spaces
|
|
177
|
+
// (`submodule.my sub.path`), so splitting lines at the first space mangles
|
|
178
|
+
// paths with spaces in them.
|
|
179
|
+
const result = await runGit(cwd, ["config", "-z", "--file", ".gitmodules", "--get-regexp", "^submodule\\..+\\.path$"]);
|
|
180
|
+
if (result.code !== 0)
|
|
181
|
+
return [];
|
|
182
|
+
const paths = [];
|
|
183
|
+
for (const record of result.stdout.split("\0")) {
|
|
184
|
+
if (record === "")
|
|
185
|
+
continue;
|
|
186
|
+
const newlineAt = record.indexOf("\n");
|
|
187
|
+
if (newlineAt === -1)
|
|
188
|
+
continue;
|
|
189
|
+
try {
|
|
190
|
+
const path = normalizeRelativePath(record.slice(newlineAt + 1));
|
|
191
|
+
if (path !== "")
|
|
192
|
+
paths.push(path);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// A malformed repository path is not eligible for submodule routing.
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return [...new Set(paths)];
|
|
199
|
+
}
|
|
200
|
+
/** The validated gitlink that strictly contains `path`, if any (longest match wins). */
|
|
201
|
+
async function submoduleForPath(runGit, cwd, path) {
|
|
202
|
+
if (!path.includes("/"))
|
|
203
|
+
return undefined;
|
|
204
|
+
const candidates = (await configuredSubmodulePaths(runGit, cwd))
|
|
205
|
+
.filter((sub) => path.startsWith(`${sub}/`))
|
|
206
|
+
.sort((left, right) => right.length - left.length);
|
|
207
|
+
if (candidates.length === 0)
|
|
208
|
+
return undefined;
|
|
209
|
+
const canonicalRoot = await canonicalPath(cwd);
|
|
210
|
+
if (canonicalRoot === undefined)
|
|
211
|
+
return undefined;
|
|
212
|
+
for (const candidate of candidates) {
|
|
213
|
+
const validated = await validatedSubmodule(runGit, cwd, canonicalRoot, candidate);
|
|
214
|
+
if (validated !== undefined)
|
|
215
|
+
return validated;
|
|
216
|
+
}
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Confirm that repository-controlled `.gitmodules` data names an index
|
|
221
|
+
* gitlink and that its checkout resolves strictly below the workspace root.
|
|
222
|
+
* Commands use the resolved checkout rather than following the configured
|
|
223
|
+
* path as a symlink.
|
|
224
|
+
*/
|
|
225
|
+
async function validatedSubmodule(runGit, cwd, canonicalRoot, path) {
|
|
226
|
+
const index = await runGit(cwd, ["ls-files", "--stage", "-z", "--", path]);
|
|
227
|
+
if (index.code !== 0 || !hasGitlink(index.stdout, path))
|
|
228
|
+
return undefined;
|
|
229
|
+
const candidate = await canonicalPath(join(cwd, path));
|
|
230
|
+
if (candidate === undefined || !isStrictDescendant(canonicalRoot, candidate))
|
|
231
|
+
return undefined;
|
|
232
|
+
return { path, cwd: candidate };
|
|
233
|
+
}
|
|
234
|
+
function hasGitlink(raw, path) {
|
|
235
|
+
return raw.split("\0").some((record) => {
|
|
236
|
+
const separator = record.indexOf("\t");
|
|
237
|
+
if (separator === -1 || record.slice(separator + 1) !== path)
|
|
238
|
+
return false;
|
|
239
|
+
return record.slice(0, separator).split(" ")[0] === "160000";
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async function canonicalPath(path) {
|
|
243
|
+
try {
|
|
244
|
+
return await realpath(path);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function isStrictDescendant(root, candidate) {
|
|
251
|
+
const relation = relative(root, candidate);
|
|
252
|
+
return relation !== ""
|
|
253
|
+
&& relation !== ".."
|
|
254
|
+
&& !relation.startsWith(`..${sep}`)
|
|
255
|
+
&& !isAbsolute(relation);
|
|
256
|
+
}
|
|
257
|
+
function parseStatus(raw, options) {
|
|
258
|
+
const records = raw.split("\0").filter((record) => record !== "");
|
|
259
|
+
const files = [];
|
|
260
|
+
const submodules = [];
|
|
261
|
+
let branch;
|
|
262
|
+
let upstream;
|
|
263
|
+
let ahead;
|
|
264
|
+
let behind;
|
|
265
|
+
for (let i = 0; i < records.length; i += 1) {
|
|
266
|
+
const record = records[i];
|
|
267
|
+
if (record === undefined)
|
|
268
|
+
continue;
|
|
269
|
+
if (record.startsWith("# branch.head "))
|
|
270
|
+
branch = normalizeBranch(record.slice("# branch.head ".length));
|
|
271
|
+
else if (record.startsWith("# branch.upstream "))
|
|
272
|
+
upstream = record.slice("# branch.upstream ".length);
|
|
273
|
+
else if (record.startsWith("# branch.ab ")) {
|
|
274
|
+
const match = /\+(\d+) -(\d+)/.exec(record);
|
|
275
|
+
if (match) {
|
|
276
|
+
ahead = Number(match[1]);
|
|
277
|
+
behind = Number(match[2]);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else if (record.startsWith("? "))
|
|
281
|
+
files.push({ path: record.slice(2), index: "untracked", workingTree: "untracked" });
|
|
282
|
+
else if (record.startsWith("! "))
|
|
283
|
+
files.push({ path: record.slice(2), index: "ignored", workingTree: "ignored" });
|
|
284
|
+
else if (record.startsWith("1 ")) {
|
|
285
|
+
const parts = record.split(" ");
|
|
286
|
+
const sub = parts[2];
|
|
287
|
+
const path = parts.slice(8).join(" ");
|
|
288
|
+
const index = stateFor(parts[1]?.[0]);
|
|
289
|
+
const workingTree = stateFor(parts[1]?.[1]);
|
|
290
|
+
// A deleted gitlink has no pointer move or inner content to expand (a
|
|
291
|
+
// staged deletion even reports the index OID as all zeros), so keep it
|
|
292
|
+
// as a plain row instead of deferring it as a submodule.
|
|
293
|
+
if (options.deferSubmodules && sub?.startsWith("S") === true && index !== "deleted" && workingTree !== "deleted") {
|
|
294
|
+
const headOid = parts[6] ?? "";
|
|
295
|
+
const indexOid = parts[7] ?? "";
|
|
296
|
+
submodules.push({
|
|
297
|
+
path,
|
|
298
|
+
index,
|
|
299
|
+
workingTree,
|
|
300
|
+
// `c` only flags unstaged moves (submodule HEAD left the index OID);
|
|
301
|
+
// a staged move leaves HEAD == index, so compare the recorded OIDs.
|
|
302
|
+
commitChanged: sub[1] === "C" || headOid !== indexOid,
|
|
303
|
+
hasModifiedContent: sub[2] === "M",
|
|
304
|
+
hasUntrackedContent: sub[3] === "U",
|
|
305
|
+
headOid,
|
|
306
|
+
indexOid,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
files.push({ path, index, workingTree });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else if (record.startsWith("2 ")) {
|
|
314
|
+
const parts = record.split(" ");
|
|
315
|
+
const path = parts.slice(9).join(" ");
|
|
316
|
+
const oldPath = records[i + 1];
|
|
317
|
+
i += 1;
|
|
318
|
+
files.push({ path, ...(oldPath === undefined ? {} : { oldPath }), index: stateFor(parts[1]?.[0]), workingTree: stateFor(parts[1]?.[1]) });
|
|
319
|
+
}
|
|
320
|
+
else if (record.startsWith("u ")) {
|
|
321
|
+
const parts = record.split(" ");
|
|
322
|
+
files.push({ path: parts.slice(10).join(" "), index: "conflicted", workingTree: "conflicted" });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return { isGitRepo: true, ...(branch === undefined ? {} : { branch }), ...(upstream === undefined ? {} : { upstream }), ...(ahead === undefined ? {} : { ahead }), ...(behind === undefined ? {} : { behind }), files, submodules };
|
|
326
|
+
}
|
|
327
|
+
function stateFor(code) {
|
|
328
|
+
if (code === undefined)
|
|
329
|
+
return "unmodified";
|
|
330
|
+
switch (code) {
|
|
331
|
+
case ".": return "unmodified";
|
|
332
|
+
case "M": return "modified";
|
|
333
|
+
case "A": return "added";
|
|
334
|
+
case "D": return "deleted";
|
|
335
|
+
case "R": return "renamed";
|
|
336
|
+
case "C": return "copied";
|
|
337
|
+
case "U": return "conflicted";
|
|
338
|
+
default: return "unmodified";
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function normalizeBranch(value) {
|
|
342
|
+
return value === "(detached)" ? undefined : value;
|
|
343
|
+
}
|
|
344
|
+
function short(oid) {
|
|
345
|
+
return oid.slice(0, 7);
|
|
346
|
+
}
|
|
347
|
+
/** A newly staged submodule records an all-zero head OID; display the pointer as `new → <sha>`. */
|
|
348
|
+
function displayFromCommit(headOid) {
|
|
349
|
+
return /^0+$/.test(headOid) ? "new" : short(headOid);
|
|
350
|
+
}
|
|
351
|
+
function hash(value) {
|
|
352
|
+
return createHash("sha1").update(value).digest("hex");
|
|
353
|
+
}
|
|
354
|
+
function statusProviderResponse(status) {
|
|
355
|
+
return {
|
|
356
|
+
isGitRepo: status.isGitRepo,
|
|
357
|
+
hash: status.hash,
|
|
358
|
+
...(status.branch === undefined ? {} : { branch: status.branch }),
|
|
359
|
+
...(status.upstream === undefined ? {} : { upstream: status.upstream }),
|
|
360
|
+
...(status.ahead === undefined ? {} : { ahead: status.ahead }),
|
|
361
|
+
...(status.behind === undefined ? {} : { behind: status.behind }),
|
|
362
|
+
files: status.files.map((file) => ({
|
|
363
|
+
path: file.path,
|
|
364
|
+
...(file.oldPath === undefined ? {} : { oldPath: file.oldPath }),
|
|
365
|
+
index: file.index,
|
|
366
|
+
workingTree: file.workingTree,
|
|
367
|
+
...(file.submoduleFromCommit === undefined ? {} : { submoduleFromCommit: file.submoduleFromCommit }),
|
|
368
|
+
...(file.submoduleToCommit === undefined ? {} : { submoduleToCommit: file.submoduleToCommit }),
|
|
369
|
+
})),
|
|
370
|
+
submodules: status.submodules,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function diffProviderResponse(diff) {
|
|
374
|
+
return {
|
|
375
|
+
...(diff.path === undefined ? {} : { path: diff.path }),
|
|
376
|
+
staged: diff.staged,
|
|
377
|
+
hash: diff.hash,
|
|
378
|
+
diff: diff.diff,
|
|
379
|
+
truncated: diff.truncated,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function createGitRunner(context, signal) {
|
|
383
|
+
return async (cwd, args) => commandResult(await context.execFile({
|
|
384
|
+
file: "git",
|
|
385
|
+
args,
|
|
386
|
+
cwd,
|
|
387
|
+
unsetEnv: GIT_LOCAL_ENV_VARS,
|
|
388
|
+
timeoutMs: GIT_COMMAND_TIMEOUT_MS,
|
|
389
|
+
signal,
|
|
390
|
+
}), args);
|
|
391
|
+
}
|
|
392
|
+
function commandResult(result, args) {
|
|
393
|
+
const command = `git ${args.join(" ")}`;
|
|
394
|
+
if (result.signal !== null)
|
|
395
|
+
throw new Error(`${command} ended from signal ${result.signal}`);
|
|
396
|
+
if (result.exitCode === null)
|
|
397
|
+
throw new Error(`${command} ended without an exit code`);
|
|
398
|
+
return {
|
|
399
|
+
code: result.exitCode,
|
|
400
|
+
stdout: result.stdout,
|
|
401
|
+
stderr: result.stderr,
|
|
402
|
+
truncated: result.stdoutTruncated,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function requireStatusInput(input) {
|
|
406
|
+
if (input !== null)
|
|
407
|
+
throw new Error("Git status input must be null");
|
|
408
|
+
}
|
|
409
|
+
function parseDiffInput(input) {
|
|
410
|
+
if (!isRecord(input))
|
|
411
|
+
throw new Error("Git diff input must be an object");
|
|
412
|
+
const unsupported = Object.keys(input).find((key) => key !== "path" && key !== "staged");
|
|
413
|
+
if (unsupported !== undefined)
|
|
414
|
+
throw new Error(`Git diff input contains an unsupported field: ${unsupported}`);
|
|
415
|
+
const path = input["path"];
|
|
416
|
+
const staged = input["staged"];
|
|
417
|
+
if (path !== undefined && typeof path !== "string")
|
|
418
|
+
throw new Error("Git diff input path must be a string");
|
|
419
|
+
if (staged !== undefined && typeof staged !== "boolean")
|
|
420
|
+
throw new Error("Git diff input staged must be a boolean");
|
|
421
|
+
return {
|
|
422
|
+
...(path === undefined ? {} : { path }),
|
|
423
|
+
...(staged === undefined ? {} : { staged }),
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function normalizeRelativePath(input) {
|
|
427
|
+
const value = input ?? "";
|
|
428
|
+
if (value === "" || value === ".")
|
|
429
|
+
return "";
|
|
430
|
+
if (isAbsolute(value))
|
|
431
|
+
throw new Error("Absolute paths are not allowed");
|
|
432
|
+
const parts = value.split(/[\\/]+/u).filter((part) => part !== "" && part !== ".");
|
|
433
|
+
if (parts.some((part) => part === ".."))
|
|
434
|
+
throw new Error("Path traversal is not allowed");
|
|
435
|
+
return parts.join("/");
|
|
436
|
+
}
|
|
437
|
+
function isRecord(value) {
|
|
438
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
439
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pi-web/git-plugin",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"piWeb": {
|
|
6
|
+
"plugins": [
|
|
7
|
+
{
|
|
8
|
+
"id": "git",
|
|
9
|
+
"browserRoot": "browser",
|
|
10
|
+
"module": "browser/pi-web-plugin.js",
|
|
11
|
+
"serverModule": "server-plugin.js",
|
|
12
|
+
"machineSpecific": true
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/git/server-plugin.ts. Do not edit directly.
|
|
2
|
+
import { basename, resolve } from "node:path";
|
|
3
|
+
import { requestGitBackend } from "./git-backend.js";
|
|
4
|
+
const GIT_LOCAL_ENV_VARS = Object.freeze([
|
|
5
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
6
|
+
"GIT_COMMON_DIR",
|
|
7
|
+
"GIT_DIR",
|
|
8
|
+
"GIT_INDEX_FILE",
|
|
9
|
+
"GIT_OBJECT_DIRECTORY",
|
|
10
|
+
"GIT_PREFIX",
|
|
11
|
+
"GIT_QUARANTINE_PATH",
|
|
12
|
+
"GIT_WORK_TREE",
|
|
13
|
+
]);
|
|
14
|
+
const plugin = {
|
|
15
|
+
apiVersion: 1,
|
|
16
|
+
name: "Git",
|
|
17
|
+
activate(context) {
|
|
18
|
+
return { workspaceProvider: createGitWorkspaceProvider(context) };
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
export default plugin;
|
|
22
|
+
export function createGitWorkspaceProvider(context) {
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
fallback: true,
|
|
25
|
+
async probe(project, signal) {
|
|
26
|
+
const result = await runGit(context, project.path, ["rev-parse", "--is-inside-work-tree"], signal);
|
|
27
|
+
if (result.signal !== null)
|
|
28
|
+
throw new Error(`git repository probe ended from signal ${result.signal}`);
|
|
29
|
+
if (result.exitCode !== 0)
|
|
30
|
+
return "pass";
|
|
31
|
+
return result.stdout.trim() === "true" ? "claim" : "pass";
|
|
32
|
+
},
|
|
33
|
+
async list(project, signal) {
|
|
34
|
+
const rootResult = await requireGit(runGit(context, project.path, ["rev-parse", "--show-toplevel"], signal), "resolve the Git worktree root");
|
|
35
|
+
const mainRootOutput = rootResult.stdout.trim();
|
|
36
|
+
if (mainRootOutput === "")
|
|
37
|
+
throw new Error("Git returned an empty worktree root");
|
|
38
|
+
const mainRoot = resolve(mainRootOutput);
|
|
39
|
+
const commonDirectoryResult = await requireGit(runGit(context, project.path, ["rev-parse", "--git-common-dir"], signal), "resolve the Git common directory");
|
|
40
|
+
const commonDirectoryOutput = commonDirectoryResult.stdout.trim();
|
|
41
|
+
if (commonDirectoryOutput === "")
|
|
42
|
+
throw new Error("Git returned an empty common directory");
|
|
43
|
+
const commonDirectory = resolve(project.path, commonDirectoryOutput);
|
|
44
|
+
const listResult = await requireGit(runGit(context, project.path, ["worktree", "list", "--porcelain", "-z"], signal), "list Git worktrees");
|
|
45
|
+
const worktrees = parseGitWorktreeList(listResult.stdout)
|
|
46
|
+
.filter((worktree) => worktree.bare !== true)
|
|
47
|
+
.map((worktree) => {
|
|
48
|
+
const path = resolve(worktree.path);
|
|
49
|
+
return { worktree: { ...worktree, path }, path };
|
|
50
|
+
});
|
|
51
|
+
// Prefer the checkout Git identifies for the registered project. A
|
|
52
|
+
// submodule is the exception: its sole worktree record points at common
|
|
53
|
+
// storage under the superproject instead of at --show-toplevel.
|
|
54
|
+
const mainWorkspacePath = worktrees.some(({ path }) => path === mainRoot)
|
|
55
|
+
? mainRoot
|
|
56
|
+
: commonDirectory;
|
|
57
|
+
const selectable = worktrees
|
|
58
|
+
.map(({ worktree, path }) => ({ worktree, path, isMain: path === mainWorkspacePath }))
|
|
59
|
+
.filter(({ worktree, path, isMain }) => worktree.prunable !== true || isMain || path === project.path);
|
|
60
|
+
if (selectable.length === 0)
|
|
61
|
+
return [singleGitWorkspace(project)];
|
|
62
|
+
return selectable.map(({ worktree, path, isMain }) => {
|
|
63
|
+
const label = worktree.branch ?? (worktree.detached === true ? "detached" : basename(worktree.path) || worktree.path);
|
|
64
|
+
return {
|
|
65
|
+
key: path,
|
|
66
|
+
path,
|
|
67
|
+
label,
|
|
68
|
+
isMain,
|
|
69
|
+
data: {
|
|
70
|
+
worktreePath: worktree.path,
|
|
71
|
+
...(worktree.branch === undefined ? {} : { branch: worktree.branch }),
|
|
72
|
+
},
|
|
73
|
+
publicMetadata: {
|
|
74
|
+
isGitRepo: true,
|
|
75
|
+
isGitWorktree: true,
|
|
76
|
+
...(worktree.branch === undefined ? {} : { branch: worktree.branch }),
|
|
77
|
+
...(worktree.detached === undefined ? {} : { detached: worktree.detached }),
|
|
78
|
+
},
|
|
79
|
+
...(isMain ? {} : { removal: gitRemovalPresentation(label, path) }),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
request: (request) => requestGitBackend(context, request),
|
|
84
|
+
async prepareRemove({ project, workspace, signal }) {
|
|
85
|
+
const privatePath = gitPrivateWorktreePath(workspace);
|
|
86
|
+
if (resolve(privatePath) !== workspace.path) {
|
|
87
|
+
throw new Error("Git workspace removal data no longer matches the current workspace path");
|
|
88
|
+
}
|
|
89
|
+
const listResult = await requireGit(runGit(context, project.path, ["worktree", "list", "--porcelain", "-z"], signal), "validate the Git worktree before removal");
|
|
90
|
+
const current = parseGitWorktreeList(listResult.stdout)
|
|
91
|
+
.find((worktree) => resolve(worktree.path) === workspace.path);
|
|
92
|
+
if (current === undefined || current.prunable === true) {
|
|
93
|
+
throw new Error("Git worktree is no longer available for removal");
|
|
94
|
+
}
|
|
95
|
+
if (current.bare === true)
|
|
96
|
+
throw new Error("A bare Git workspace cannot be removed as a linked worktree");
|
|
97
|
+
return {
|
|
98
|
+
title: `Delete workspace: ${workspace.label}`,
|
|
99
|
+
command: `git worktree remove ${shellQuote(workspace.path)}`,
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/** Parse `git worktree list --porcelain -z` without path quoting or space loss. */
|
|
105
|
+
export function parseGitWorktreeList(stdout) {
|
|
106
|
+
return stdout.split("\0\0").flatMap((record) => {
|
|
107
|
+
if (record === "")
|
|
108
|
+
return [];
|
|
109
|
+
const info = { path: "" };
|
|
110
|
+
for (const field of record.split("\0")) {
|
|
111
|
+
const separator = field.indexOf(" ");
|
|
112
|
+
const key = separator === -1 ? field : field.slice(0, separator);
|
|
113
|
+
const value = separator === -1 ? "" : field.slice(separator + 1);
|
|
114
|
+
if (key === "worktree")
|
|
115
|
+
info.path = value;
|
|
116
|
+
else if (key === "branch")
|
|
117
|
+
info.branch = value.replace(/^refs\/heads\//u, "");
|
|
118
|
+
else if (key === "bare")
|
|
119
|
+
info.bare = true;
|
|
120
|
+
else if (key === "detached")
|
|
121
|
+
info.detached = true;
|
|
122
|
+
else if (key === "prunable")
|
|
123
|
+
info.prunable = true;
|
|
124
|
+
}
|
|
125
|
+
return info.path === "" ? [] : [info];
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function singleGitWorkspace(project) {
|
|
129
|
+
return {
|
|
130
|
+
key: project.path,
|
|
131
|
+
path: project.path,
|
|
132
|
+
label: project.name,
|
|
133
|
+
isMain: true,
|
|
134
|
+
data: { worktreePath: project.path },
|
|
135
|
+
publicMetadata: { isGitRepo: true, isGitWorktree: false },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function gitRemovalPresentation(label, path) {
|
|
139
|
+
return {
|
|
140
|
+
actionLabel: "Delete workspace",
|
|
141
|
+
confirmation: `Delete workspace ${label}?\n\nThis will run git worktree remove and delete:\n${path}\n\nThe Git branch will not be deleted.`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function gitPrivateWorktreePath(workspace) {
|
|
145
|
+
const data = workspace.data;
|
|
146
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
147
|
+
throw new Error("Git workspace removal data is unavailable");
|
|
148
|
+
}
|
|
149
|
+
const path = Reflect.get(data, "worktreePath");
|
|
150
|
+
if (typeof path !== "string" || path === "")
|
|
151
|
+
throw new Error("Git worktree path is unavailable for removal");
|
|
152
|
+
return path;
|
|
153
|
+
}
|
|
154
|
+
function shellQuote(value) {
|
|
155
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
156
|
+
}
|
|
157
|
+
async function runGit(context, cwd, args, signal) {
|
|
158
|
+
const result = await context.execFile({
|
|
159
|
+
file: "git",
|
|
160
|
+
args: ["-C", cwd, ...args],
|
|
161
|
+
unsetEnv: GIT_LOCAL_ENV_VARS,
|
|
162
|
+
signal,
|
|
163
|
+
});
|
|
164
|
+
if (result.stdoutTruncated || result.stderrTruncated) {
|
|
165
|
+
throw new Error(`git ${args.join(" ")} exceeded the host output limit`);
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
async function requireGit(resultPromise, action) {
|
|
170
|
+
const result = await resultPromise;
|
|
171
|
+
if (result.signal === null && result.exitCode === 0)
|
|
172
|
+
return result;
|
|
173
|
+
const detail = result.stderr.trim();
|
|
174
|
+
const outcome = result.signal === null ? `exit ${String(result.exitCode)}` : `signal ${result.signal}`;
|
|
175
|
+
throw new Error(`Unable to ${action} (${outcome})${detail === "" ? "" : `: ${detail}`}`);
|
|
176
|
+
}
|