@hyperdreamer/pi-webui 1.15.2 → 1.16.0
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/dist/client/assets/{CodeViewer-CHZziPur.js → CodeViewer-CCHq9DpL.js} +1 -1
- package/dist/client/assets/{UnifiedDiffViewer-j1-AFZ8f.js → UnifiedDiffViewer-JFQMCnzP.js} +1 -1
- package/dist/client/assets/{index-2RMTbgnM.js → index-WjewjFVt.js} +469 -469
- package/dist/client/index.html +1 -1
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -1
- package/dist/configMutationCoordinator.js +10 -1
- package/dist/configMutationCoordinator.js.map +1 -1
- package/dist/pi-webui-plugins/workspace-tasks/config.js +3 -132
- package/dist/pi-webui-plugins/workspace-tasks/pi-webui-plugin.js +14 -2
- package/dist/pi-webui-plugins/workspace-tasks/taskDomain.js +203 -0
- package/dist/pi-webui-plugins/workspace-tasks/taskRunner.js +3 -2
- package/dist/pi-webui-plugins/workspace-tasks/tasksPanelElement.js +947 -673
- package/dist/server/app.js +20 -4
- package/dist/server/app.js.map +1 -1
- package/dist/server/configRoutes.js +5 -0
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +4 -4
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/workspaceExplorerRoutes.js +64 -4
- package/dist/server/workspaceExplorerRoutes.js.map +1 -1
- package/dist/server/workspaceTasks/workspaceTasksCatalogService.js +318 -0
- package/dist/server/workspaceTasks/workspaceTasksCatalogService.js.map +1 -0
- package/dist/server/workspaceTasks/workspaceTasksComposition.js +67 -0
- package/dist/server/workspaceTasks/workspaceTasksComposition.js.map +1 -0
- package/dist/server/workspaceTasks/workspaceTasksErrors.js +34 -0
- package/dist/server/workspaceTasks/workspaceTasksErrors.js.map +1 -0
- package/dist/server/workspaceTasks/workspaceTasksGlobalCatalogAdapter.js +169 -0
- package/dist/server/workspaceTasks/workspaceTasksGlobalCatalogAdapter.js.map +1 -0
- package/dist/server/workspaceTasks/workspaceTasksGlobalMutationGate.js +75 -0
- package/dist/server/workspaceTasks/workspaceTasksGlobalMutationGate.js.map +1 -0
- package/dist/server/workspaceTasks/workspaceTasksMoveProtocol.js +313 -0
- package/dist/server/workspaceTasks/workspaceTasksMoveProtocol.js.map +1 -0
- package/dist/server/workspaceTasks/workspaceTasksMoveRegistry.js +352 -0
- package/dist/server/workspaceTasks/workspaceTasksMoveRegistry.js.map +1 -0
- package/dist/server/workspaceTasks/workspaceTasksProxyRoutes.js +63 -0
- package/dist/server/workspaceTasks/workspaceTasksProxyRoutes.js.map +1 -0
- package/dist/server/workspaceTasks/workspaceTasksRoutes.js +153 -0
- package/dist/server/workspaceTasks/workspaceTasksRoutes.js.map +1 -0
- package/dist/server/workspaceTasks/workspaceTasksWorkspaceCatalogAdapter.js +171 -0
- package/dist/server/workspaceTasks/workspaceTasksWorkspaceCatalogAdapter.js.map +1 -0
- package/dist/server/workspaceTasks/workspaceTasksWorkspaceFile.js +227 -0
- package/dist/server/workspaceTasks/workspaceTasksWorkspaceFile.js.map +1 -0
- package/dist/server/workspaceTasks/workspaceTasksWorkspacePathGate.js +42 -0
- package/dist/server/workspaceTasks/workspaceTasksWorkspacePathGate.js.map +1 -0
- package/dist/server/workspaces/fileContentService.js +116 -6
- package/dist/server/workspaces/fileContentService.js.map +1 -1
- package/dist/shared/apiTypes.d.ts +132 -0
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/federatedRoutes.js +15 -0
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/workspaceFiles.js +2 -0
- package/dist/shared/workspaceFiles.js.map +1 -1
- package/dist/shared/workspaceTasks.d.ts +53 -0
- package/dist/shared/workspaceTasks.js +203 -0
- package/dist/shared/workspaceTasks.js.map +1 -0
- package/dist/shared/workspaceTasksApi.js +379 -0
- package/dist/shared/workspaceTasksApi.js.map +1 -0
- package/docs/config.md +22 -4
- package/docs/plugins.md +24 -13
- package/package.json +1 -1
- package/dist/pi-webui-plugins/workspace-tasks/workspaceTasksClient.js +0 -242
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { TASKS_CONFIG_PATH, assertWorkspaceTasksCatalogSize, parseWorkspaceTasksConfig, parseWorkspaceTasksConfigText, serializeWorkspaceTasksConfig, } from "../../shared/workspaceTasks.js";
|
|
2
|
+
import { resolveWorkspaceContext } from "../workspaces/workspaceContext.js";
|
|
3
|
+
import { WorkspaceTasksInvalidCatalogError, WorkspaceTasksRevisionConflictError, WorkspaceTasksUnavailableError, WorkspaceTasksUnknownOutcomeError, } from "./workspaceTasksErrors.js";
|
|
4
|
+
const MISSING_MESSAGE = "No workspace tasks configured here.";
|
|
5
|
+
const MISSING_HINT = `${TASKS_CONFIG_PATH} is optional. Create it in this workspace if you want custom tasks.`;
|
|
6
|
+
const INVALID_MESSAGE = "Workspace tasks configuration is invalid.";
|
|
7
|
+
const INVALID_HINT = `Fix ${TASKS_CONFIG_PATH}, then click Refresh.`;
|
|
8
|
+
const UNAVAILABLE_MESSAGE = "Could not load workspace tasks.";
|
|
9
|
+
const UNAVAILABLE_HINT = `Fix ${TASKS_CONFIG_PATH}, then click Refresh.`;
|
|
10
|
+
export function createWorkspaceTasksWorkspaceCatalogAdapter(options) {
|
|
11
|
+
const { files, authorizer, workspaceMutations } = options;
|
|
12
|
+
return {
|
|
13
|
+
async read(address) {
|
|
14
|
+
try {
|
|
15
|
+
await validateAddress(address);
|
|
16
|
+
return responseFromProjection(await readProjection(address));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return unavailableResponse();
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
async replace(address, input, writeOptions) {
|
|
23
|
+
try {
|
|
24
|
+
await validateAddress(address);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new WorkspaceTasksUnavailableError();
|
|
28
|
+
}
|
|
29
|
+
await authorizer.reconcileGlobalMoveClaim({ scope: "workspace", address }, writeOptions?.permit);
|
|
30
|
+
let authorizationError;
|
|
31
|
+
const publication = { attempted: false, acknowledged: false };
|
|
32
|
+
try {
|
|
33
|
+
return await workspaceMutations.run(address, async () => {
|
|
34
|
+
const current = await readProjection(address);
|
|
35
|
+
if (current.kind === "invalid")
|
|
36
|
+
throw new WorkspaceTasksInvalidCatalogError(current.detail);
|
|
37
|
+
if (input.expectedRevision !== current.revision)
|
|
38
|
+
throw new WorkspaceTasksRevisionConflictError();
|
|
39
|
+
const proposed = canonicalizeCatalog(input.config);
|
|
40
|
+
if (current.kind === "loaded" && current.canonical === proposed.canonical) {
|
|
41
|
+
return responseFromProjection(current);
|
|
42
|
+
}
|
|
43
|
+
const intent = {
|
|
44
|
+
scope: "workspace",
|
|
45
|
+
address,
|
|
46
|
+
expectedRevision: input.expectedRevision,
|
|
47
|
+
config: proposed.config,
|
|
48
|
+
};
|
|
49
|
+
try {
|
|
50
|
+
authorizer.assertWorkspaceMutationAllowed(address, intent, writeOptions?.permit);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
authorizationError = error;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
const hooks = {
|
|
57
|
+
onPublicationAttempt: () => {
|
|
58
|
+
publication.attempted = true;
|
|
59
|
+
},
|
|
60
|
+
onPublished: () => {
|
|
61
|
+
publication.acknowledged = true;
|
|
62
|
+
invokeNonThrowing(writeOptions?.onWriteAcknowledged);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
await files.publishCatalog(address, Buffer.from(proposed.canonical, "utf8"), hooks);
|
|
66
|
+
const verified = await readProjection(address);
|
|
67
|
+
if (verified.kind !== "loaded" || !verified.bytes.equals(Buffer.from(proposed.canonical, "utf8"))) {
|
|
68
|
+
throw new WorkspaceTasksUnknownOutcomeError();
|
|
69
|
+
}
|
|
70
|
+
return responseFromProjection(verified);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error === authorizationError)
|
|
75
|
+
throw error;
|
|
76
|
+
if (error instanceof WorkspaceTasksInvalidCatalogError
|
|
77
|
+
|| error instanceof WorkspaceTasksRevisionConflictError
|
|
78
|
+
|| error instanceof WorkspaceTasksUnknownOutcomeError) {
|
|
79
|
+
if (error instanceof WorkspaceTasksUnknownOutcomeError) {
|
|
80
|
+
invokeNonThrowing(writeOptions?.onWriteOutcomeUnknown);
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
if (publication.attempted) {
|
|
85
|
+
invokeNonThrowing(writeOptions?.onWriteOutcomeUnknown);
|
|
86
|
+
throw new WorkspaceTasksUnknownOutcomeError();
|
|
87
|
+
}
|
|
88
|
+
throw new WorkspaceTasksUnavailableError();
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
async function validateAddress(address) {
|
|
93
|
+
if (options.projects === undefined || options.workspaces === undefined)
|
|
94
|
+
return;
|
|
95
|
+
// The file resolver repeats this check before touching the fixed path. The
|
|
96
|
+
// adapter check keeps route identity failures outside the mutation queue.
|
|
97
|
+
await resolveWorkspaceContext(options.projects, options.workspaces, address.projectId, address.workspaceId);
|
|
98
|
+
}
|
|
99
|
+
async function readProjection(address) {
|
|
100
|
+
let source;
|
|
101
|
+
try {
|
|
102
|
+
source = await files.readCatalog(address);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
throw new WorkspaceTasksUnavailableError();
|
|
106
|
+
}
|
|
107
|
+
if (source.kind === "missing")
|
|
108
|
+
return source;
|
|
109
|
+
if (source.bytes.includes(0))
|
|
110
|
+
throw new WorkspaceTasksUnavailableError();
|
|
111
|
+
let text;
|
|
112
|
+
try {
|
|
113
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(source.bytes);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
throw new WorkspaceTasksUnavailableError();
|
|
117
|
+
}
|
|
118
|
+
const parsed = parseWorkspaceTasksConfigText(text);
|
|
119
|
+
if (!parsed.ok)
|
|
120
|
+
return { kind: "invalid", detail: parsed.error };
|
|
121
|
+
try {
|
|
122
|
+
assertWorkspaceTasksCatalogSize(parsed.config);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return { kind: "invalid", detail: errorMessage(error) };
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
kind: "loaded",
|
|
129
|
+
config: parsed.config,
|
|
130
|
+
canonical: serializeWorkspaceTasksConfig(parsed.config),
|
|
131
|
+
bytes: source.bytes,
|
|
132
|
+
revision: source.revision,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function canonicalizeCatalog(value) {
|
|
136
|
+
const parsed = parseWorkspaceTasksConfig(value);
|
|
137
|
+
if (!parsed.ok)
|
|
138
|
+
throw new WorkspaceTasksInvalidCatalogError(parsed.error);
|
|
139
|
+
try {
|
|
140
|
+
assertWorkspaceTasksCatalogSize(parsed.config);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
throw new WorkspaceTasksInvalidCatalogError(errorMessage(error));
|
|
144
|
+
}
|
|
145
|
+
return { config: parsed.config, canonical: serializeWorkspaceTasksConfig(parsed.config) };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function responseFromProjection(projection) {
|
|
149
|
+
if (projection.kind === "missing") {
|
|
150
|
+
return { kind: "missing", message: MISSING_MESSAGE, hint: MISSING_HINT, revision: projection.revision };
|
|
151
|
+
}
|
|
152
|
+
if (projection.kind === "invalid") {
|
|
153
|
+
return { kind: "invalid", message: INVALID_MESSAGE, hint: INVALID_HINT, detail: projection.detail };
|
|
154
|
+
}
|
|
155
|
+
return { kind: "loaded", config: projection.config, revision: projection.revision };
|
|
156
|
+
}
|
|
157
|
+
function unavailableResponse() {
|
|
158
|
+
return { kind: "unavailable", message: UNAVAILABLE_MESSAGE, hint: UNAVAILABLE_HINT };
|
|
159
|
+
}
|
|
160
|
+
function errorMessage(error) {
|
|
161
|
+
return error instanceof Error ? error.message : String(error);
|
|
162
|
+
}
|
|
163
|
+
function invokeNonThrowing(callback) {
|
|
164
|
+
try {
|
|
165
|
+
callback?.();
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Lifecycle observers must not change the persistence outcome.
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=workspaceTasksWorkspaceCatalogAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceTasksWorkspaceCatalogAdapter.js","sourceRoot":"","sources":["../../../src/server/workspaceTasks/workspaceTasksWorkspaceCatalogAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { lstat, mkdir, open, readFile, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
4
|
+
import { MAX_WORKSPACE_FILE_BYTES } from "../../shared/workspaceFiles.js";
|
|
5
|
+
import { TASKS_CONFIG_PATH } from "../../shared/workspaceTasks.js";
|
|
6
|
+
import { readWorkspaceFileBytesFromTarget } from "../workspaces/fileContentService.js";
|
|
7
|
+
import { ensureInside, isNodeErrorWithCode, normalizeRelativePath } from "../workspaces/pathSafety.js";
|
|
8
|
+
import { resolveWorkspaceContext } from "../workspaces/workspaceContext.js";
|
|
9
|
+
const defaultFileSystem = {
|
|
10
|
+
lstat: (path) => lstat(path),
|
|
11
|
+
realpath: (path) => realpath(path),
|
|
12
|
+
mkdir: (path, options) => mkdir(path, options),
|
|
13
|
+
open: (path) => open(path, "r"),
|
|
14
|
+
readFile: (path) => readFile(path),
|
|
15
|
+
writeFile: (path, bytes, options) => writeFile(path, bytes, options),
|
|
16
|
+
rename: (from, to) => rename(from, to),
|
|
17
|
+
unlink: (path) => unlink(path),
|
|
18
|
+
stat: (path) => stat(path),
|
|
19
|
+
};
|
|
20
|
+
export function createWorkspaceTasksWorkspaceFileResolver(options) {
|
|
21
|
+
const fileSystem = { ...defaultFileSystem, ...(options.fileSystem ?? {}) };
|
|
22
|
+
const resolveContext = options.resolveContext ?? resolveWorkspaceContext;
|
|
23
|
+
async function workspaceRoot(address) {
|
|
24
|
+
const context = await resolveContext(options.projects, options.workspaces, address.projectId, address.workspaceId);
|
|
25
|
+
const root = await fileSystem.realpath(context.root);
|
|
26
|
+
const metadata = await fileSystem.lstat(root);
|
|
27
|
+
if (!metadata.isDirectory())
|
|
28
|
+
throw new Error("Workspace path must be a directory");
|
|
29
|
+
return root;
|
|
30
|
+
}
|
|
31
|
+
async function resolveFixedTaskPath(address, createParent) {
|
|
32
|
+
const root = await workspaceRoot(address);
|
|
33
|
+
const parentPath = join(root, ".pi-webui");
|
|
34
|
+
let parent;
|
|
35
|
+
try {
|
|
36
|
+
parent = await resolveSafeDirectory(root, parentPath, createParent);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (!createParent && isNodeErrorWithCode(error, "ENOENT"))
|
|
40
|
+
return { root, parentMissing: true };
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
const target = join(parent, "tasks.json");
|
|
44
|
+
const final = await inspectFinalEntry(target);
|
|
45
|
+
if (final?.isSymbolicLink() === true)
|
|
46
|
+
throw new Error("Workspace task file must not be a symbolic link");
|
|
47
|
+
return { root, parent, target, final };
|
|
48
|
+
}
|
|
49
|
+
async function readCatalog(address) {
|
|
50
|
+
const resolved = await resolveFixedTaskPath(address, false);
|
|
51
|
+
if ("parentMissing" in resolved || resolved.final === undefined)
|
|
52
|
+
return { kind: "missing", revision: revisionForMissing() };
|
|
53
|
+
if (!resolved.final.isFile())
|
|
54
|
+
throw new Error("Workspace task file must be a regular file");
|
|
55
|
+
if (resolved.final.size > MAX_WORKSPACE_FILE_BYTES) {
|
|
56
|
+
throw new Error(`Workspace task file exceeds ${String(MAX_WORKSPACE_FILE_BYTES)} bytes`);
|
|
57
|
+
}
|
|
58
|
+
let bytes;
|
|
59
|
+
try {
|
|
60
|
+
bytes = await readWorkspaceFileBytesFromTarget(resolved.target, {
|
|
61
|
+
stat: fileSystem.stat,
|
|
62
|
+
open: fileSystem.open,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (isNodeErrorWithCode(error, "ENOENT"))
|
|
67
|
+
return { kind: "missing", revision: revisionForMissing() };
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
return { kind: "present", bytes, revision: revisionForBytes(bytes) };
|
|
71
|
+
}
|
|
72
|
+
async function publishCatalog(address, bytes, hooks = {}) {
|
|
73
|
+
const resolved = await resolveFixedTaskPath(address, true);
|
|
74
|
+
if ("parentMissing" in resolved)
|
|
75
|
+
throw new Error("Workspace task parent could not be created");
|
|
76
|
+
await publishBytes(resolved.parent, resolved.target, Buffer.from(bytes), hooks);
|
|
77
|
+
}
|
|
78
|
+
async function writeExplorerTaskFile(address, body, writeOptions = {}) {
|
|
79
|
+
const resolved = await resolveFixedTaskPath(address, writeOptions.createDirs ?? true);
|
|
80
|
+
if ("parentMissing" in resolved)
|
|
81
|
+
throw new Error("Path does not exist");
|
|
82
|
+
const exists = resolved.final !== undefined;
|
|
83
|
+
if (resolved.final !== undefined && !resolved.final.isFile())
|
|
84
|
+
throw new Error("Path is not a file");
|
|
85
|
+
if (exists && writeOptions.overwrite === false)
|
|
86
|
+
throw new Error(`File already exists: ${TASKS_CONFIG_PATH}`);
|
|
87
|
+
await publishBytes(resolved.parent, resolved.target, Buffer.from(body));
|
|
88
|
+
const metadata = await fileSystem.stat(resolved.target);
|
|
89
|
+
return {
|
|
90
|
+
path: TASKS_CONFIG_PATH,
|
|
91
|
+
size: metadata.size,
|
|
92
|
+
modifiedAt: metadata.mtime.toISOString(),
|
|
93
|
+
created: !exists,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function deleteExplorerTaskFile(address) {
|
|
97
|
+
const resolved = await resolveFixedTaskPath(address, false);
|
|
98
|
+
if ("parentMissing" in resolved || resolved.final === undefined)
|
|
99
|
+
return { path: TASKS_CONFIG_PATH, existed: false };
|
|
100
|
+
if (!resolved.final.isFile())
|
|
101
|
+
throw new Error("Path is not a file");
|
|
102
|
+
try {
|
|
103
|
+
await fileSystem.unlink(resolved.target);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (isNodeErrorWithCode(error, "ENOENT"))
|
|
107
|
+
return { path: TASKS_CONFIG_PATH, existed: false };
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
return { path: TASKS_CONFIG_PATH, existed: true };
|
|
111
|
+
}
|
|
112
|
+
async function moveExplorerTaskFile(address, normalizedMove) {
|
|
113
|
+
const root = await workspaceRoot(address);
|
|
114
|
+
const from = normalizeRelativePath(normalizedMove.fromPath);
|
|
115
|
+
const to = normalizeRelativePath(normalizedMove.toPath);
|
|
116
|
+
if (from === "" || to === "")
|
|
117
|
+
throw new Error("File move paths are required");
|
|
118
|
+
const source = await resolveExplorerEntry(root, from, false);
|
|
119
|
+
if (source.final === undefined)
|
|
120
|
+
throw new Error("Path does not exist");
|
|
121
|
+
if (source.final.isSymbolicLink())
|
|
122
|
+
throw new Error("Workspace task file must not be a symbolic link");
|
|
123
|
+
if (!source.final.isFile())
|
|
124
|
+
throw new Error("Source path is not a file");
|
|
125
|
+
const destination = await resolveExplorerEntry(root, to, normalizedMove.createDirs ?? true);
|
|
126
|
+
if (destination.final !== undefined && normalizedMove.overwrite !== true) {
|
|
127
|
+
throw new Error(`File already exists: ${to}`);
|
|
128
|
+
}
|
|
129
|
+
if (destination.final !== undefined && destination.final.isSymbolicLink() && isTaskPath(to)) {
|
|
130
|
+
throw new Error("Workspace task file must not be a symbolic link");
|
|
131
|
+
}
|
|
132
|
+
await fileSystem.rename(source.target, destination.target);
|
|
133
|
+
const finalMetadata = await fileSystem.stat(destination.target);
|
|
134
|
+
return {
|
|
135
|
+
fromPath: from,
|
|
136
|
+
toPath: to,
|
|
137
|
+
size: finalMetadata.size,
|
|
138
|
+
modifiedAt: finalMetadata.mtime.toISOString(),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return { readCatalog, publishCatalog, writeExplorerTaskFile, deleteExplorerTaskFile, moveExplorerTaskFile };
|
|
142
|
+
async function publishBytes(parent, target, bytes, hooks = {}) {
|
|
143
|
+
const temporary = join(parent, `.${basename(target)}.${randomUUID()}.tmp`);
|
|
144
|
+
let temporaryCreated = true;
|
|
145
|
+
try {
|
|
146
|
+
await fileSystem.writeFile(temporary, bytes, { flag: "wx", mode: 0o600 });
|
|
147
|
+
hooks.onPublicationAttempt?.();
|
|
148
|
+
await fileSystem.rename(temporary, target);
|
|
149
|
+
temporaryCreated = false;
|
|
150
|
+
hooks.onPublished?.();
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
if (temporaryCreated) {
|
|
154
|
+
try {
|
|
155
|
+
await fileSystem.unlink(temporary);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// The publication error is authoritative; cleanup is best effort.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function resolveExplorerEntry(root, relativePath, createParent) {
|
|
164
|
+
const requested = normalizeRelativePath(relativePath);
|
|
165
|
+
const targetPath = join(root, requested);
|
|
166
|
+
ensureInside(root, targetPath);
|
|
167
|
+
const parent = await resolveSafeDirectory(root, dirname(targetPath), createParent);
|
|
168
|
+
const target = join(parent, basename(targetPath));
|
|
169
|
+
return { target, final: await inspectFinalEntry(target) };
|
|
170
|
+
}
|
|
171
|
+
async function resolveSafeDirectory(root, directory, createMissing) {
|
|
172
|
+
const relativeDirectory = normalizeRelativePath(relative(root, directory));
|
|
173
|
+
let current = root;
|
|
174
|
+
for (const part of relativeDirectory === "" ? [] : relativeDirectory.split("/")) {
|
|
175
|
+
const next = join(current, part);
|
|
176
|
+
let metadata;
|
|
177
|
+
try {
|
|
178
|
+
metadata = await fileSystem.lstat(next);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (!isNodeErrorWithCode(error, "ENOENT") || !createMissing)
|
|
182
|
+
throw error;
|
|
183
|
+
await fileSystem.mkdir(next, { recursive: false });
|
|
184
|
+
metadata = await fileSystem.lstat(next);
|
|
185
|
+
}
|
|
186
|
+
if (metadata.isSymbolicLink())
|
|
187
|
+
throw new Error("Workspace task parent must not be a symbolic link");
|
|
188
|
+
if (!metadata.isDirectory())
|
|
189
|
+
throw new Error("Workspace task parent must be a directory");
|
|
190
|
+
const canonical = await fileSystem.realpath(next);
|
|
191
|
+
ensureInside(root, canonical);
|
|
192
|
+
current = canonical;
|
|
193
|
+
}
|
|
194
|
+
return current;
|
|
195
|
+
}
|
|
196
|
+
async function inspectFinalEntry(path) {
|
|
197
|
+
try {
|
|
198
|
+
return await fileSystem.lstat(path);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (isNodeErrorWithCode(error, "ENOENT"))
|
|
202
|
+
return undefined;
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function revisionForMissing() {
|
|
208
|
+
const hash = createHash("sha256");
|
|
209
|
+
hash.update("workspace-task-file:missing\0", "utf8");
|
|
210
|
+
return hash.digest("hex");
|
|
211
|
+
}
|
|
212
|
+
function revisionForBytes(bytes) {
|
|
213
|
+
const hash = createHash("sha256");
|
|
214
|
+
hash.update("workspace-task-file:present\0", "utf8");
|
|
215
|
+
hash.update(bytes);
|
|
216
|
+
return hash.digest("hex");
|
|
217
|
+
}
|
|
218
|
+
function isTaskPath(path) {
|
|
219
|
+
return isWorkspaceTasksPath(path);
|
|
220
|
+
}
|
|
221
|
+
export function normalizeWorkspaceTasksPath(path) {
|
|
222
|
+
return normalizeRelativePath(path);
|
|
223
|
+
}
|
|
224
|
+
export function isWorkspaceTasksPath(path) {
|
|
225
|
+
return normalizeWorkspaceTasksPath(path) === TASKS_CONFIG_PATH;
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=workspaceTasksWorkspaceFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceTasksWorkspaceFile.js","sourceRoot":"","sources":["../../../src/server/workspaceTasks/workspaceTasksWorkspaceFile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isWorkspaceTasksPath } from "./workspaceTasksWorkspaceFile.js";
|
|
2
|
+
const GENERIC_WORKSPACE_EXPECTED_REVISION = "workspace-explorer";
|
|
3
|
+
const EMPTY_CATALOG = { version: 1, tasks: [] };
|
|
4
|
+
/**
|
|
5
|
+
* Serializes explorer operations that target the fixed workspace task file with
|
|
6
|
+
* the same per-workspace authority used by the task adapter.
|
|
7
|
+
*/
|
|
8
|
+
export class WorkspaceTasksWorkspacePathGate {
|
|
9
|
+
constructor(authorizer, workspaceMutations) {
|
|
10
|
+
this.authorizer = authorizer;
|
|
11
|
+
if (workspaceMutations !== undefined) {
|
|
12
|
+
this.workspaceMutations = workspaceMutations;
|
|
13
|
+
}
|
|
14
|
+
else if (isWorkspaceMutationCoordinator(authorizer)) {
|
|
15
|
+
this.workspaceMutations = authorizer;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
throw new Error("Workspace task path gate requires a workspace mutation coordinator");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async run(address, normalizedTargets, operation) {
|
|
22
|
+
if (!normalizedTargets.some((target) => isWorkspaceTasksPath(target)))
|
|
23
|
+
return operation();
|
|
24
|
+
await this.authorizer.reconcileGlobalMoveClaim({ scope: "workspace", address });
|
|
25
|
+
return this.workspaceMutations.run(address, async () => {
|
|
26
|
+
this.authorizer.assertWorkspaceMutationAllowed(address, workspaceMutationIntent(address));
|
|
27
|
+
return operation();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function workspaceMutationIntent(address) {
|
|
32
|
+
return {
|
|
33
|
+
scope: "workspace",
|
|
34
|
+
address,
|
|
35
|
+
expectedRevision: GENERIC_WORKSPACE_EXPECTED_REVISION,
|
|
36
|
+
config: EMPTY_CATALOG,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function isWorkspaceMutationCoordinator(value) {
|
|
40
|
+
return "run" in value && typeof value.run === "function";
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=workspaceTasksWorkspacePathGate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceTasksWorkspacePathGate.js","sourceRoot":"","sources":["../../../src/server/workspaceTasks/workspaceTasksWorkspacePathGate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAIxE,MAAM,mCAAmC,GAAG,oBAAoB,CAAC;AACjE,MAAM,aAAa,GAAG,EAAE,OAAO,EAAE,CAAU,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAEzD;;;GAGG;AACH,MAAM,OAAO,+BAA+B;IAG1C,YACmB,UAA4C,EAC7D,kBAA+D;QAD9C,eAAU,GAAV,UAAU,CAAkC;QAG7D,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;YACrC,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;QAC/C,CAAC;aAAM,IAAI,8BAA8B,CAAC,UAAU,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,kBAAkB,GAAG,UAAU,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CACP,OAAgC,EAChC,iBAAoC,EACpC,SAA2B;QAE3B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;YAAE,OAAO,SAAS,EAAE,CAAC;QAE1F,MAAM,IAAI,CAAC,UAAU,CAAC,wBAAwB,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;QAChF,OAAO,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;YACrD,IAAI,CAAC,UAAU,CAAC,8BAA8B,CAAC,OAAO,EAAE,uBAAuB,CAAC,OAAO,CAAC,CAAC,CAAC;YAC1F,OAAO,SAAS,EAAE,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,SAAS,uBAAuB,CAAC,OAAgC;IAC/D,OAAO;QACL,KAAK,EAAE,WAAW;QAClB,OAAO;QACP,gBAAgB,EAAE,mCAAmC;QACrD,MAAM,EAAE,aAAa;KACtB,CAAC;AACJ,CAAC;AAED,SAAS,8BAA8B,CACrC,KAAuC;IAEvC,OAAO,KAAK,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,UAAU,CAAC;AAC3D,CAAC"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { lstat, mkdir, open, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
2
|
-
import { basename, dirname, join } from "node:path";
|
|
1
|
+
import { lstat, mkdir, open, readlink, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { MAX_WORKSPACE_FILE_BYTES } from "../../shared/workspaceFiles.js";
|
|
3
4
|
import { imageMimeTypeForPath } from "./imagePreviewService.js";
|
|
4
5
|
import { resolveWorkspacePathAccessTarget } from "./pathAccessPolicy.js";
|
|
5
|
-
import { ensureInside, isNodeErrorWithCode, resolveInsideWorkspace, resolveParentInsideWorkspace } from "./pathSafety.js";
|
|
6
|
-
const MAX_BYTES = 512 * 1024;
|
|
6
|
+
import { ensureInside, isNodeErrorWithCode, normalizeRelativePath, resolveInsideWorkspace, resolveParentInsideWorkspace } from "./pathSafety.js";
|
|
7
7
|
export async function readWorkspaceFile(rootPath, path, pathAccess) {
|
|
8
8
|
if (path === undefined || path === "")
|
|
9
9
|
throw new Error("path query parameter is required");
|
|
@@ -11,7 +11,7 @@ export async function readWorkspaceFile(rootPath, path, pathAccess) {
|
|
|
11
11
|
const s = await stat(target);
|
|
12
12
|
if (!s.isFile())
|
|
13
13
|
throw new Error("Path is not a file");
|
|
14
|
-
const bytesToRead = Math.min(s.size,
|
|
14
|
+
const bytesToRead = Math.min(s.size, MAX_WORKSPACE_FILE_BYTES);
|
|
15
15
|
const buffer = await readFilePrefix(target, bytesToRead);
|
|
16
16
|
const media = mediaForPath(displayPath);
|
|
17
17
|
const binary = media.mediaType === "image" || isProbablyBinary(buffer);
|
|
@@ -23,10 +23,63 @@ export async function readWorkspaceFile(rootPath, path, pathAccess) {
|
|
|
23
23
|
size: s.size,
|
|
24
24
|
modifiedAt: s.mtime.toISOString(),
|
|
25
25
|
content: binary ? "" : buffer.toString("utf8"),
|
|
26
|
-
truncated: s.size >
|
|
26
|
+
truncated: s.size > MAX_WORKSPACE_FILE_BYTES,
|
|
27
27
|
binary,
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Read a complete workspace file as bytes while requiring valid UTF-8. This
|
|
32
|
+
* is intentionally separate from the explorer preview, whose contract keeps
|
|
33
|
+
* truncation and replacement-character decoding for arbitrary files.
|
|
34
|
+
*/
|
|
35
|
+
export async function readWorkspaceFileRaw(rootPath, path, pathAccess) {
|
|
36
|
+
if (path === undefined || path === "")
|
|
37
|
+
throw new Error("path query parameter is required");
|
|
38
|
+
let target;
|
|
39
|
+
if (path.startsWith("/") || path.startsWith("~")) {
|
|
40
|
+
({ target } = await resolveWorkspacePathAccessTarget(rootPath, path, pathAccess));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const resolved = await resolveParentInsideWorkspace(rootPath, path);
|
|
44
|
+
const realParent = await realpath(dirname(resolved.target));
|
|
45
|
+
target = join(realParent, basename(resolved.target));
|
|
46
|
+
ensureInside(resolved.root, target);
|
|
47
|
+
const entry = await lstat(target);
|
|
48
|
+
if (entry.isSymbolicLink())
|
|
49
|
+
throw new Error("Workspace file must not be a symbolic link");
|
|
50
|
+
}
|
|
51
|
+
return readWorkspaceFileBytesFromTarget(target);
|
|
52
|
+
}
|
|
53
|
+
export async function readWorkspaceFileBytesFromTarget(target, operations = { stat, open: (path) => open(path, "r") }) {
|
|
54
|
+
const metadata = await operations.stat(target);
|
|
55
|
+
if (!metadata.isFile())
|
|
56
|
+
throw new Error("Path is not a file");
|
|
57
|
+
if (metadata.size > MAX_WORKSPACE_FILE_BYTES) {
|
|
58
|
+
throw new Error(`Workspace file exceeds ${String(MAX_WORKSPACE_FILE_BYTES)} bytes`);
|
|
59
|
+
}
|
|
60
|
+
const buffer = Buffer.alloc(MAX_WORKSPACE_FILE_BYTES + 1);
|
|
61
|
+
const handle = await operations.open(target);
|
|
62
|
+
let bytesRead = 0;
|
|
63
|
+
try {
|
|
64
|
+
while (bytesRead < buffer.length) {
|
|
65
|
+
const result = await handle.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead);
|
|
66
|
+
if (result.bytesRead === 0)
|
|
67
|
+
break;
|
|
68
|
+
bytesRead += result.bytesRead;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
await handle.close();
|
|
73
|
+
}
|
|
74
|
+
if (bytesRead > MAX_WORKSPACE_FILE_BYTES) {
|
|
75
|
+
throw new Error(`Workspace file exceeds ${String(MAX_WORKSPACE_FILE_BYTES)} bytes`);
|
|
76
|
+
}
|
|
77
|
+
const bytes = buffer.subarray(0, bytesRead);
|
|
78
|
+
if (bytes.includes(0))
|
|
79
|
+
throw new Error("Workspace file is binary");
|
|
80
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
81
|
+
return bytes;
|
|
82
|
+
}
|
|
30
83
|
async function readFilePrefix(target, bytesToRead) {
|
|
31
84
|
if (bytesToRead === 0)
|
|
32
85
|
return Buffer.alloc(0);
|
|
@@ -40,6 +93,63 @@ async function readFilePrefix(target, bytesToRead) {
|
|
|
40
93
|
await handle.close();
|
|
41
94
|
}
|
|
42
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolve existing aliases before a route decides whether a fixed-file owner
|
|
98
|
+
* owns a mutation. A caller can stop at its fixed target to preserve that
|
|
99
|
+
* owner's final-entry no-follow checks.
|
|
100
|
+
*/
|
|
101
|
+
export async function resolveWorkspaceFileMutationPath(rootPath, path, options = {}) {
|
|
102
|
+
const { root, relativePath } = await resolveParentInsideWorkspace(rootPath, path ?? "");
|
|
103
|
+
const resolvedPaths = new Set([relativePath]);
|
|
104
|
+
if (options.stopAt?.(relativePath) === true) {
|
|
105
|
+
return { normalizedPath: relativePath, resolvedPaths: [...resolvedPaths] };
|
|
106
|
+
}
|
|
107
|
+
let current = root;
|
|
108
|
+
let remaining = relativePath === "" ? [] : relativePath.split("/");
|
|
109
|
+
let resolvedLinks = 0;
|
|
110
|
+
while (remaining.length > 0) {
|
|
111
|
+
const part = remaining.shift();
|
|
112
|
+
if (part === undefined)
|
|
113
|
+
break;
|
|
114
|
+
const target = join(current, part);
|
|
115
|
+
ensureInside(root, target);
|
|
116
|
+
let entry;
|
|
117
|
+
try {
|
|
118
|
+
entry = await lstat(target);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (!isNodeErrorWithCode(error, "ENOENT"))
|
|
122
|
+
throw error;
|
|
123
|
+
const unresolvedTarget = join(target, ...remaining);
|
|
124
|
+
ensureInside(root, unresolvedTarget);
|
|
125
|
+
const unresolvedPath = normalizeRelativePath(relative(root, unresolvedTarget));
|
|
126
|
+
resolvedPaths.add(unresolvedPath);
|
|
127
|
+
return { normalizedPath: relativePath, resolvedPaths: [...resolvedPaths] };
|
|
128
|
+
}
|
|
129
|
+
if (!entry.isSymbolicLink()) {
|
|
130
|
+
current = target;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (remaining.length === 0 && options.followFinalSymlink === false) {
|
|
134
|
+
return { normalizedPath: relativePath, resolvedPaths: [...resolvedPaths] };
|
|
135
|
+
}
|
|
136
|
+
resolvedLinks += 1;
|
|
137
|
+
if (resolvedLinks > 40)
|
|
138
|
+
throw new Error("Too many symbolic links in workspace mutation path");
|
|
139
|
+
const link = await readlink(target);
|
|
140
|
+
const linkTarget = isAbsolute(link) ? link : resolve(dirname(target), link);
|
|
141
|
+
ensureInside(root, linkTarget);
|
|
142
|
+
const linkPath = normalizeRelativePath(relative(root, linkTarget));
|
|
143
|
+
remaining = [...(linkPath === "" ? [] : linkPath.split("/")), ...remaining];
|
|
144
|
+
const resolvedPath = remaining.join("/");
|
|
145
|
+
resolvedPaths.add(resolvedPath);
|
|
146
|
+
if (options.stopAt?.(resolvedPath) === true) {
|
|
147
|
+
return { normalizedPath: relativePath, resolvedPaths: [...resolvedPaths] };
|
|
148
|
+
}
|
|
149
|
+
current = root;
|
|
150
|
+
}
|
|
151
|
+
return { normalizedPath: relativePath, resolvedPaths: [...resolvedPaths] };
|
|
152
|
+
}
|
|
43
153
|
export async function writeWorkspaceFile(rootPath, path, content, options = {}) {
|
|
44
154
|
if (path === undefined || path === "")
|
|
45
155
|
throw new Error("path query parameter is required");
|