@jmfederico/pi-web 1.202607.3 → 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/README.md +4 -4
- package/dist/cli.js +69 -7
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/{CodeViewer-CDVbMiN9.js → CodeViewer-mEiBZFA5.js} +2 -2
- package/dist/client/assets/{TerminalPanel-CTS1CgqF.js → TerminalPanel-O9381Eme.js} +6 -6
- package/dist/client/assets/index-uI2AGFUy.js +4091 -0
- package/dist/client/index.html +1 -1
- package/dist/config.js +109 -73
- package/dist/config.js.map +1 -1
- package/dist/docker/piWebDockerCommandPlan.js +4 -3
- package/dist/docker/piWebDockerCommandPlan.js.map +1 -1
- package/dist/nativeServices/servicePlan.js +1 -1
- package/dist/nativeServices/servicePlan.js.map +1 -1
- 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/{server/git/gitService.js → pi-web-plugins/git/git-backend.js} +209 -60
- 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 +1 -2
- package/dist/pi-web-plugins/info/package.json +1 -1
- package/dist/pi-web-plugins/info/pi-web-plugin.js +2 -2
- package/dist/pi-web-plugins/relays/package.json +1 -1
- package/dist/pi-web-plugins/relays/pi-web-plugin.js +3 -3
- package/dist/pi-web-plugins/relays/relayDiscovery.js +159 -27
- package/dist/pi-web-plugins/relays/relaysPanelElement.js +173 -16
- package/dist/pi-web-plugins/updates/package.json +1 -1
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +1 -1
- package/dist/pi-web-plugins/workspace-tasks/package.json +1 -1
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +3 -3
- package/dist/plugin-api.d.ts +33 -16
- package/dist/pluginRecoveryCli.js +172 -0
- package/dist/pluginRecoveryCli.js.map +1 -0
- package/dist/server/activity/workspaceActivityService.js +27 -19
- package/dist/server/activity/workspaceActivityService.js.map +1 -1
- package/dist/server/app.js +35 -37
- package/dist/server/app.js.map +1 -1
- package/dist/server/browserMessageProjection.js +0 -2
- package/dist/server/browserMessageProjection.js.map +1 -1
- package/dist/server/configRoutes.js +4 -31
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/machines/machineClient.js +23 -4
- package/dist/server/machines/machineClient.js.map +1 -1
- package/dist/server/machines/machinePluginProxyRoutes.js +74 -17
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +167 -21
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/machines/machineService.js +22 -0
- package/dist/server/machines/machineService.js.map +1 -1
- 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 +159 -261
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/piWebStatus.js +40 -45
- package/dist/server/piWebStatus.js.map +1 -1
- 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/realtime/sessionEventHub.js +26 -11
- package/dist/server/realtime/sessionEventHub.js.map +1 -1
- 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 +24 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +2 -2
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +3 -1
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
- 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 +283 -96
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/askUserTool.js +0 -3
- package/dist/server/sessions/askUserTool.js.map +1 -1
- package/dist/server/sessions/authRoutes.js +0 -10
- package/dist/server/sessions/authRoutes.js.map +1 -1
- package/dist/server/sessions/authService.js +15 -78
- package/dist/server/sessions/authService.js.map +1 -1
- package/dist/server/sessions/dockerEnvironmentFacts.js +254 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/messagePaging.js +2 -2
- package/dist/server/sessions/messagePaging.js.map +1 -1
- package/dist/server/sessions/modelCatalogRefresher.js +4 -3
- package/dist/server/sessions/modelCatalogRefresher.js.map +1 -1
- package/dist/server/sessions/oauthLoginFlowService.js +2 -5
- package/dist/server/sessions/oauthLoginFlowService.js.map +1 -1
- package/dist/server/sessions/pendingAskStore.js +0 -3
- package/dist/server/sessions/pendingAskStore.js.map +1 -1
- package/dist/server/sessions/piSessionManagerGateway.js +134 -3
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +318 -253
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionCommandService.js +24 -1
- package/dist/server/sessions/sessionCommandService.js.map +1 -1
- 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 +82 -23
- package/dist/server/sessions/sessionFileHeader.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +88 -56
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/sessionSummaryScanner.js +491 -0
- package/dist/server/sessions/sessionSummaryScanner.js.map +1 -0
- package/dist/server/sessions/spawnSessionTool.js +8 -1
- package/dist/server/sessions/spawnSessionTool.js.map +1 -1
- package/dist/server/sessions/spawnSubsessionTool.js +11 -9
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server/sessions/spawnTargetResolver.js +1 -1
- 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/terminalProxyRoutes.js +2 -1
- package/dist/server/terminalProxyRoutes.js.map +1 -1
- package/dist/server/workspaceExplorerRoutes.js +20 -13
- package/dist/server/workspaceExplorerRoutes.js.map +1 -1
- package/dist/server/workspaces/effectivePathAccess.js +0 -19
- package/dist/server/workspaces/effectivePathAccess.js.map +1 -1
- package/dist/server/workspaces/fileContentService.js +18 -9
- package/dist/server/workspaces/fileContentService.js.map +1 -1
- 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/projectWorkspaceCwds.js +0 -5
- package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -1
- 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 +1 -3
- package/dist/server/workspaces/workspaceContext.js.map +1 -1
- package/dist/server/workspaces/workspaceDeletionRoutes.js +39 -103
- package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -1
- 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 +3 -22
- package/dist/sessiond/activeAgentProfile.js.map +1 -1
- package/dist/sessiond/config.js +13 -2
- package/dist/sessiond/config.js.map +1 -1
- package/dist/sessiond/sessionDaemonClient.js +11 -9
- package/dist/sessiond/sessionDaemonClient.js.map +1 -1
- package/dist/shared/activeAgentProfile.js +2 -41
- package/dist/shared/activeAgentProfile.js.map +1 -1
- package/dist/shared/activity.js +0 -3
- package/dist/shared/activity.js.map +1 -1
- package/dist/shared/apiTypes.js +7 -14
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +8 -40
- package/dist/shared/capabilities.js.map +1 -1
- package/dist/shared/federatedRoutes.js +28 -8
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/machinePluginIds.js +4 -2
- package/dist/shared/machinePluginIds.js.map +1 -1
- package/dist/shared/machineStatus.js +94 -0
- package/dist/shared/machineStatus.js.map +1 -0
- package/dist/shared/piWebStatusParsing.js +29 -0
- package/dist/shared/piWebStatusParsing.js.map +1 -1
- package/dist/shared/pluginApiTypes.d.ts +148 -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 +5 -0
- package/dist/shared/pluginIds.js.map +1 -1
- package/dist/shared/pluginRecoveryCommands.js +14 -0
- package/dist/shared/pluginRecoveryCommands.js.map +1 -0
- package/dist/shared/workspaceFiles.js +41 -2
- package/dist/shared/workspaceFiles.js.map +1 -1
- package/dist/shared/workspaceRemovalProtocol.js +24 -0
- package/dist/shared/workspaceRemovalProtocol.js.map +1 -0
- package/docs/config.md +140 -47
- package/docs/plugins.md +386 -137
- 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/package.json +32 -12
- package/server-plugin-api.d.ts +1 -0
- package/dist/client/assets/UnifiedDiffViewer-Cqke12ZX.js +0 -39
- package/dist/client/assets/index-LME0LfPb.js +0 -4068
- package/dist/plugin-api/unstable.d.ts +0 -22
- package/dist/server/activity/workspaceActivityRoutes.js +0 -4
- package/dist/server/activity/workspaceActivityRoutes.js.map +0 -1
- package/dist/server/git/gitService.js.map +0 -1
- package/dist/server/gitRoutes.js +0 -23
- package/dist/server/gitRoutes.js.map +0 -1
- package/dist/server/sessions/parentSessionLocator.js +0 -75
- package/dist/server/sessions/parentSessionLocator.js.map +0 -1
- package/dist/server/workspaces/gitWorktreeDiscovery.js +0 -43
- package/dist/server/workspaces/gitWorktreeDiscovery.js.map +0 -1
- package/dist/server/workspaces/imagePreviewService.js +0 -40
- package/dist/server/workspaces/imagePreviewService.js.map +0 -1
- package/dist/server/workspaces/workspaceService.js +0 -52
- package/dist/server/workspaces/workspaceService.js.map +0 -1
- package/dist/shared/apiTypes.d.ts +0 -1234
- package/dist/shared/thinkingLevels.d.ts +0 -27
- package/plugin-api/unstable.d.ts +0 -1
|
@@ -1,16 +1,41 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/git/git-backend.ts. Do not edit directly.
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
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) {
|
|
9
34
|
const result = await runGit(cwd, ["status", "--porcelain=v2", "--branch", "--untracked-files=all", "-z"]);
|
|
10
35
|
if (result.code !== 0)
|
|
11
36
|
return { isGitRepo: false, hash: hash(result.stdout + result.stderr), files: [], submodules: [] };
|
|
12
37
|
const parsed = parseStatus(result.stdout, { deferSubmodules: true });
|
|
13
|
-
return expandSubmodules(cwd, parsed, result.stdout);
|
|
38
|
+
return expandSubmodules(runGit, cwd, parsed, result.stdout);
|
|
14
39
|
}
|
|
15
40
|
/**
|
|
16
41
|
* Merge each dirty submodule's own changes into the flat file list. A moved
|
|
@@ -19,11 +44,17 @@ export async function gitStatus(cwd) {
|
|
|
19
44
|
* entries under `<submodule>/<inner path>`. A plain `-dirty` pointer (commit
|
|
20
45
|
* unchanged) is intentionally not surfaced as a pointer entry.
|
|
21
46
|
*/
|
|
22
|
-
async function expandSubmodules(cwd, parsed, topRaw) {
|
|
47
|
+
async function expandSubmodules(runGit, cwd, parsed, topRaw) {
|
|
23
48
|
// Fan out concurrently — one `git status` per dirty submodule plus one
|
|
24
49
|
// `git rev-parse` per unstaged pointer move — then concatenate in input
|
|
25
50
|
// order so the file list and hash are identical to a serial pass.
|
|
26
|
-
const
|
|
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
|
+
}));
|
|
27
58
|
const files = [...parsed.files];
|
|
28
59
|
const dirtySubmodulePaths = [];
|
|
29
60
|
let extraForHash = "";
|
|
@@ -44,7 +75,7 @@ async function expandSubmodules(cwd, parsed, topRaw) {
|
|
|
44
75
|
};
|
|
45
76
|
}
|
|
46
77
|
/** Expand one dirty submodule: the pointer entry first, then its inner files. */
|
|
47
|
-
async function expandSubmodule(
|
|
78
|
+
async function expandSubmodule(runGit, sub, location) {
|
|
48
79
|
const files = [];
|
|
49
80
|
let extraForHash = "";
|
|
50
81
|
if (sub.commitChanged) {
|
|
@@ -53,11 +84,11 @@ async function expandSubmodule(cwd, sub) {
|
|
|
53
84
|
index: sub.index,
|
|
54
85
|
workingTree: sub.workingTree,
|
|
55
86
|
submoduleFromCommit: displayFromCommit(sub.headOid),
|
|
56
|
-
submoduleToCommit: short(await resolveSubmoduleToCommit(cwd, sub)),
|
|
87
|
+
submoduleToCommit: short(await resolveSubmoduleToCommit(runGit, location?.cwd, sub)),
|
|
57
88
|
});
|
|
58
89
|
}
|
|
59
|
-
if (sub.hasModifiedContent || sub.hasUntrackedContent) {
|
|
60
|
-
const inner = await runGit(
|
|
90
|
+
if ((sub.hasModifiedContent || sub.hasUntrackedContent) && location !== undefined) {
|
|
91
|
+
const inner = await runGit(location.cwd, ["status", "--porcelain=v2", "--untracked-files=all", "-z"]);
|
|
61
92
|
if (inner.code === 0) {
|
|
62
93
|
extraForHash = `\0${sub.path}\0${inner.stdout}`;
|
|
63
94
|
const innerFiles = parseStatus(inner.stdout, { deferSubmodules: false }).files;
|
|
@@ -73,24 +104,28 @@ async function expandSubmodule(cwd, sub) {
|
|
|
73
104
|
}
|
|
74
105
|
return { files, extraForHash };
|
|
75
106
|
}
|
|
76
|
-
async function resolveSubmoduleToCommit(cwd, sub) {
|
|
107
|
+
async function resolveSubmoduleToCommit(runGit, cwd, sub) {
|
|
77
108
|
// Staged pointer moves already expose the new commit as the index OID; an
|
|
78
|
-
// unstaged move only records the old OID, so read the submodule's
|
|
79
|
-
|
|
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)
|
|
80
112
|
return sub.indexOid;
|
|
81
|
-
const head = await runGit(
|
|
113
|
+
const head = await runGit(cwd, ["rev-parse", "HEAD"]);
|
|
82
114
|
const resolved = head.stdout.trim();
|
|
83
115
|
return head.code === 0 && resolved !== "" ? resolved : sub.indexOid;
|
|
84
116
|
}
|
|
85
|
-
export async function gitDiff(cwd, options) {
|
|
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) {
|
|
86
121
|
const staged = options.staged === true;
|
|
87
122
|
let path;
|
|
88
123
|
if (options.path !== undefined && options.path !== "")
|
|
89
124
|
path = normalizeRelativePath(options.path);
|
|
90
125
|
if (path !== undefined) {
|
|
91
|
-
const owner = await submoduleForPath(cwd, path);
|
|
126
|
+
const owner = await submoduleForPath(runGit, cwd, path);
|
|
92
127
|
if (owner !== undefined)
|
|
93
|
-
return submoduleDiff(
|
|
128
|
+
return submoduleDiff(runGit, owner, path, staged);
|
|
94
129
|
}
|
|
95
130
|
const args = ["diff", "--no-ext-diff", "--color=never"];
|
|
96
131
|
if (staged)
|
|
@@ -100,7 +135,7 @@ export async function gitDiff(cwd, options) {
|
|
|
100
135
|
const result = await runGit(cwd, args);
|
|
101
136
|
if (result.code !== 0)
|
|
102
137
|
throw new Error(result.stderr.trim() || "git diff failed");
|
|
103
|
-
if (!staged && path !== undefined && result.stdout === "" && await isUntracked(cwd, path)) {
|
|
138
|
+
if (!staged && path !== undefined && result.stdout === "" && await isUntracked(runGit, cwd, path)) {
|
|
104
139
|
const untracked = await runGit(cwd, ["diff", "--no-ext-diff", "--color=never", "--no-index", "/dev/null", "--", path]);
|
|
105
140
|
if (untracked.code !== 0 && untracked.code !== 1)
|
|
106
141
|
throw new Error(untracked.stderr.trim() || "git diff failed");
|
|
@@ -114,9 +149,9 @@ export async function gitDiff(cwd, options) {
|
|
|
114
149
|
* The response path stays the full superproject-relative path so the viewer and
|
|
115
150
|
* the selected row line up.
|
|
116
151
|
*/
|
|
117
|
-
async function submoduleDiff(
|
|
118
|
-
const subCwd =
|
|
119
|
-
const rel = normalizeRelativePath(path.slice(owner.length + 1));
|
|
152
|
+
async function submoduleDiff(runGit, owner, path, staged) {
|
|
153
|
+
const subCwd = owner.cwd;
|
|
154
|
+
const rel = normalizeRelativePath(path.slice(owner.path.length + 1));
|
|
120
155
|
const args = ["diff", "--no-ext-diff", "--color=never"];
|
|
121
156
|
if (staged)
|
|
122
157
|
args.push("--cached");
|
|
@@ -124,7 +159,7 @@ async function submoduleDiff(cwd, owner, path, staged) {
|
|
|
124
159
|
const result = await runGit(subCwd, args);
|
|
125
160
|
if (result.code !== 0)
|
|
126
161
|
throw new Error(result.stderr.trim() || "git diff failed");
|
|
127
|
-
if (!staged && result.stdout === "" && await isUntracked(subCwd, rel)) {
|
|
162
|
+
if (!staged && result.stdout === "" && await isUntracked(runGit, subCwd, rel)) {
|
|
128
163
|
const untracked = await runGit(subCwd, ["diff", "--no-ext-diff", "--color=never", "--no-index", "/dev/null", "--", rel]);
|
|
129
164
|
if (untracked.code !== 0 && untracked.code !== 1)
|
|
130
165
|
throw new Error(untracked.stderr.trim() || "git diff failed");
|
|
@@ -132,12 +167,12 @@ async function submoduleDiff(cwd, owner, path, staged) {
|
|
|
132
167
|
}
|
|
133
168
|
return { path, staged, hash: hash(result.stdout), diff: result.stdout, truncated: result.truncated };
|
|
134
169
|
}
|
|
135
|
-
async function isUntracked(cwd, path) {
|
|
170
|
+
async function isUntracked(runGit, cwd, path) {
|
|
136
171
|
const result = await runGit(cwd, ["ls-files", "--others", "--exclude-standard", "-z", "--", path]);
|
|
137
172
|
return result.code === 0 && result.stdout.split("\0").includes(path);
|
|
138
173
|
}
|
|
139
174
|
/** Configured direct-submodule paths (depth 1), read from `.gitmodules`. */
|
|
140
|
-
async function configuredSubmodulePaths(cwd) {
|
|
175
|
+
async function configuredSubmodulePaths(runGit, cwd) {
|
|
141
176
|
// `-z` emits `<key>\n<value>\0` records; keys may themselves contain spaces
|
|
142
177
|
// (`submodule.my sub.path`), so splitting lines at the first space mangles
|
|
143
178
|
// paths with spaces in them.
|
|
@@ -151,26 +186,73 @@ async function configuredSubmodulePaths(cwd) {
|
|
|
151
186
|
const newlineAt = record.indexOf("\n");
|
|
152
187
|
if (newlineAt === -1)
|
|
153
188
|
continue;
|
|
154
|
-
|
|
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
|
+
}
|
|
155
197
|
}
|
|
156
|
-
return paths;
|
|
198
|
+
return [...new Set(paths)];
|
|
157
199
|
}
|
|
158
|
-
/** The
|
|
159
|
-
async function submoduleForPath(cwd, path) {
|
|
160
|
-
// Cheap bail-outs before spawning `git config`: a path strictly inside a
|
|
161
|
-
// submodule always contains `/`, and without `.gitmodules` there are no
|
|
162
|
-
// configured submodules to look up (every diff call used to pay this spawn).
|
|
200
|
+
/** The validated gitlink that strictly contains `path`, if any (longest match wins). */
|
|
201
|
+
async function submoduleForPath(runGit, cwd, path) {
|
|
163
202
|
if (!path.includes("/"))
|
|
164
203
|
return undefined;
|
|
165
|
-
|
|
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 {
|
|
166
247
|
return undefined;
|
|
167
|
-
const subs = await configuredSubmodulePaths(cwd);
|
|
168
|
-
let best;
|
|
169
|
-
for (const sub of subs) {
|
|
170
|
-
if (sub !== "" && path.startsWith(`${sub}/`) && (best === undefined || sub.length > best.length))
|
|
171
|
-
best = sub;
|
|
172
248
|
}
|
|
173
|
-
|
|
249
|
+
}
|
|
250
|
+
function isStrictDescendant(root, candidate) {
|
|
251
|
+
const relation = relative(root, candidate);
|
|
252
|
+
return relation !== ""
|
|
253
|
+
&& relation !== ".."
|
|
254
|
+
&& !relation.startsWith(`..${sep}`)
|
|
255
|
+
&& !isAbsolute(relation);
|
|
174
256
|
}
|
|
175
257
|
function parseStatus(raw, options) {
|
|
176
258
|
const records = raw.split("\0").filter((record) => record !== "");
|
|
@@ -269,22 +351,89 @@ function displayFromCommit(headOid) {
|
|
|
269
351
|
function hash(value) {
|
|
270
352
|
return createHash("sha1").update(value).digest("hex");
|
|
271
353
|
}
|
|
272
|
-
|
|
273
|
-
return
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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);
|
|
289
439
|
}
|
|
290
|
-
//# sourceMappingURL=gitService.js.map
|
|
@@ -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
|
+
}
|
|
@@ -62,8 +62,7 @@ export function componentDetails(component) {
|
|
|
62
62
|
}
|
|
63
63
|
export function workspaceFlags(workspace) {
|
|
64
64
|
return [
|
|
65
|
-
workspace.
|
|
66
|
-
workspace.isGitWorktree ? "git worktree" : workspace.isGitRepo ? "git repo" : "not a git repo",
|
|
65
|
+
workspace.provider === undefined ? "folder workspace" : `provider: ${workspace.provider.pluginId}`,
|
|
67
66
|
workspace.isMain ? "main workspace" : undefined,
|
|
68
67
|
].filter((flag) => flag !== undefined);
|
|
69
68
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// the internals for your own.
|
|
8
8
|
import { copyDiagnostics, renderInfoPanel } from "./infoInternals.js";
|
|
9
9
|
const plugin = {
|
|
10
|
-
apiVersion:
|
|
10
|
+
apiVersion: 2,
|
|
11
11
|
name: "Info Plugin",
|
|
12
12
|
activate: ({ html, svg }) => ({
|
|
13
13
|
contributions: {
|
|
@@ -24,7 +24,7 @@ const plugin = {
|
|
|
24
24
|
{
|
|
25
25
|
id: "workspace.kind-label",
|
|
26
26
|
order: 100,
|
|
27
|
-
items: (context) => [{ type: "text", text: context.workspace.
|
|
27
|
+
items: (context) => [{ type: "text", text: context.workspace.provider?.pluginId ?? "folder", title: context.workspace.path }],
|
|
28
28
|
},
|
|
29
29
|
],
|
|
30
30
|
workspacePanels: [
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { RELAYS_ROOT } from "./relayDiscovery.js";
|
|
3
3
|
import { defineRelaysPanelElement } from "./relaysPanelElement.js";
|
|
4
4
|
const plugin = {
|
|
5
|
-
apiVersion:
|
|
5
|
+
apiVersion: 2,
|
|
6
6
|
name: "Relays",
|
|
7
|
-
activate: ({
|
|
7
|
+
activate: ({ runtimePluginId, html, svg }) => {
|
|
8
8
|
defineRelaysPanelElement();
|
|
9
9
|
return {
|
|
10
10
|
contributions: {
|
|
@@ -18,7 +18,7 @@ const plugin = {
|
|
|
18
18
|
run: (context) => {
|
|
19
19
|
if (context.state.selectedWorkspace === undefined)
|
|
20
20
|
return;
|
|
21
|
-
context.selectWorkspaceTool(`${
|
|
21
|
+
context.selectWorkspaceTool(`${runtimePluginId}:workspace.relays`);
|
|
22
22
|
},
|
|
23
23
|
},
|
|
24
24
|
],
|