@deepseek-ai/dsh-util-workspace-path 0.1.3-alpha.2 → 0.1.5-alpha.2

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/util/workspace-path/README.md
5
- README.md: 1b09bda74c8836655789c57fa05d03a2892c16b9
6
- README.zh.md: 25128e5fec504e1724f5cc7952fed976985912d5
5
+ README.md: ab7787f3649c8f75a0f0e0745bc78ea32edb5925
6
+ README.zh.md: 270c79f7bb57def13a7656e8f76f900c1e094f4f
package/README.md CHANGED
@@ -9,15 +9,25 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- Browser-safe path helpers shared by Workspace-facing client and controller packages. The package joins Workspace-relative paths, abbreviates POSIX home directories for display, and derives Workspace titles from POSIX or Windows paths. It has no Cordis service or runtime state.
12
+ Browser-safe path helpers shared by Workspace-facing client and controller packages. The package joins Workspace-relative paths, abbreviates POSIX home directories for display, derives Workspace titles from POSIX or Windows paths, splits a path into its directories and final segment for display, and owns the `dsh-resource://file/…` address grammar that names a workspace file across the Sidebar and the resource model. `relativizeToCwd` removes the workspace prefix for display while preserving paths outside that directory. It has no Cordis service or runtime state.
13
13
 
14
14
  ## Table of Contents
15
15
 
16
+ - [File addresses](#file-addresses)
16
17
  - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
17
18
  - [Dev Note](#dev-note)
18
19
 
19
20
  -----
20
21
 
22
+ <a id="file-addresses"></a>
23
+ ## File addresses
24
+
25
+ A resource address is `dsh-resource://<type>/…`, and the type — the URI host — is the resource protocol key (`file`, or one a plugin declares in `ResourceProtocolMap`); any other scheme is a navigation protocol, defined elsewhere. `dsh-resource://file/session/<sessionId>/<path>` names the Session that authorizes the Host read and a workspace-relative or absolute path. Leading slashes remain part of the path: `/etc/hosts` is `dsh-resource://file/session/s//etc/hosts`, a Windows drive is `dsh-resource://file/session/s/C:/x/y.txt`, and UNC is `dsh-resource://file/session/s///server/share/y.txt`. The Host resolves paths and enforces access. The `absolute/<path>` form remains parseable but carries no authorizing Session, so the file provider cannot read it and Preview does not claim it; neither current nor Tab Session is borrowed. The grammar lives in [`src/file-address.ts`](src/file-address.ts); the path helpers stay in [`src/index.ts`](src/index.ts), which re-exports it.
26
+
27
+ `sessionFileAddress(sessionId, path)` normalizes `\` to `/` and drops leading `./`, but preserves leading `/` characters. Every id and path segment is component-encoded with `:` kept literal. `fileAddressFor(sessionId, cwd, path)` always builds a Session address: paths inside `cwd` become relative; other absolute paths, including when `cwd` is unknown, stay absolute within that Session address. `absoluteFileAddress(absolutePath)` builds only the Session-less form. `parseFileAddress(address)` checks the exact file-address prefix, ignores query and fragment suffixes, decodes each segment, and returns `{ scope, sessionId, path }` for a Session address or `{ scope, path }` for the Session-less form. Another type or scheme, an unknown scope, a missing id or path, or a malformed escape returns `undefined`.
28
+
29
+ -----
30
+
21
31
  ## Known Limitations and Deferred Work
22
32
 
23
33
  <a id="known-limitations-and-deferred-work"></a>
package/README.zh.md CHANGED
@@ -9,15 +9,25 @@ kind: "package-library"
9
9
 
10
10
  ## 概述
11
11
 
12
- 供 Workspace 相关客户端和控制器包共享、可在浏览器使用的路径辅助函数。该包负责拼接 Workspace 相对路径、缩写用于展示的 POSIX 主目录,以及从 POSIX 或 Windows 路径提取 Workspace 标题;它不提供 Cordis service,也不持有运行时状态。
12
+ 供 Workspace 相关客户端和控制器包共享、可在浏览器使用的路径辅助函数。该包负责拼接 Workspace 相对路径、缩写用于展示的 POSIX 主目录、从 POSIX 或 Windows 路径提取 Workspace 标题、把路径拆成目录部分与末段供展示,并拥有在 Sidebar 与资源模型之间命名工作区文件的 `dsh-resource://file/…` 地址语法。`relativizeToCwd` 在显示时省略工作区前缀,并保留该目录以外的路径。它不提供 Cordis service,也不持有运行时状态。
13
13
 
14
14
  ## 目录
15
15
 
16
+ - [文件地址](#file-addresses)
16
17
  - [已知限制与暂缓事项](#known-limitations-and-deferred-work)
17
18
  - [开发备注](#dev-note)
18
19
 
19
20
  -----
20
21
 
22
+ <a id="file-addresses"></a>
23
+ ## 文件地址
24
+
25
+ 资源地址 = `dsh-resource://<type>/…`,type(URI 的 host)即资源协议键(`file`,或插件在 `ResourceProtocolMap` 中声明的键);其他 scheme 属导航协议,另行定义。`dsh-resource://file/session/<sessionId>/<path>` 指定授权 Host 读取的 Session,以及工作区相对或绝对路径。前导斜杠保留在路径中:`/etc/hosts` 对应 `dsh-resource://file/session/s//etc/hosts`,Windows 盘符对应 `dsh-resource://file/session/s/C:/x/y.txt`,UNC 对应 `dsh-resource://file/session/s///server/share/y.txt`。Host 解析路径并执行访问检查。`absolute/<path>` 形式仍可解析,但不携带授权 Session,因此 file 提供方不能读取,Preview 也不认领;两者均不借用当前或 Tab Session。语法住在 [`src/file-address.ts`](src/file-address.ts);路径辅助函数留在 [`src/index.ts`](src/index.ts) 并再导出它。
26
+
27
+ `sessionFileAddress(sessionId, path)` 将 `\` 归一为 `/`,去掉前导 `./`,但保留前导 `/` 字符。id 和每个路径段都做组件编码,`:` 保持字面。`fileAddressFor(sessionId, cwd, path)` 始终构造 Session 地址:`cwd` 内的路径转为相对路径;其他绝对路径(包括 `cwd` 未知时)仍作为该 Session 地址内的绝对路径。`absoluteFileAddress(absolutePath)` 只构造不带 Session 的形式。`parseFileAddress(address)` 检查精确的文件地址前缀、忽略查询与片段后缀、逐段解码,并为 Session 地址返回 `{ scope, sessionId, path }`,为不带 Session 的形式返回 `{ scope, path }`。其他 type 或 scheme、未知作用域、缺 id 或路径,或错误转义都返回 `undefined`。
28
+
29
+ -----
30
+
21
31
  ## 已知限制与暂缓事项
22
32
 
23
33
  <a id="known-limitations-and-deferred-work"></a>
package/lib/index.js CHANGED
@@ -1,3 +1,85 @@
1
+ //#region lib/types/file-address.js
2
+ /**
3
+ * The `dsh-resource://file/…` address grammar: how a file is named across the
4
+ * Sidebar and the resource model, built and parsed without touching a
5
+ * filesystem.
6
+ * @module
7
+ */
8
+ /** The scheme and type every file address opens with. */
9
+ const FILE_ADDRESS_PREFIX = "dsh-resource://file/";
10
+ /** Component-encode one id or path segment, keeping `:` literal for drive letters. */
11
+ function encodeSegment(segment) {
12
+ return encodeURIComponent(segment).replace(/%3A/gi, ":");
13
+ }
14
+ /** Encode a `/`-separated path segment by segment. */
15
+ function encodePath(path) {
16
+ return path.split("/").map(encodeSegment).join("/");
17
+ }
18
+ /** Whether a decoded first path segment is a Windows drive (`C:`). */
19
+ function isDriveSegment(segment) {
20
+ return segment !== void 0 && /^[A-Za-z]:$/.test(segment);
21
+ }
22
+ /**
23
+ * Build the address of a file read through one Session.
24
+ * @param sessionId - the Session whose Host workspace resolves the path.
25
+ * @param path - absolute or workspace-relative path; backslashes are normalized to `/`, and leading `./` prefixes are dropped.
26
+ * @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
27
+ */
28
+ function sessionFileAddress(sessionId, path) {
29
+ const normalized = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
30
+ return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(normalized)}`;
31
+ }
32
+ /**
33
+ * Build the address of a file by its absolute path.
34
+ * @param path - absolute path; backslashes are normalized to `/` and the leading `/` is dropped,
35
+ * except that a UNC path (`\\server\share`) keeps one empty first segment.
36
+ * @returns the `dsh-resource://file/absolute/<path>` address.
37
+ */
38
+ function absoluteFileAddress(path) {
39
+ const normalized = path.replace(/\\/g, "/");
40
+ const unc = normalized.startsWith("//");
41
+ const absolute = normalized.replace(/^\/+/, "");
42
+ return `${FILE_ADDRESS_PREFIX}absolute/${unc ? "/" : ""}${encodePath(absolute)}`;
43
+ }
44
+ /**
45
+ * Read a file address back into its parts without resolving `.` or `..`.
46
+ * Query and fragment suffixes are ignored; encoded path segments are decoded.
47
+ * @param address - a candidate address.
48
+ * @returns the parts, or `undefined` when the string is not a `dsh-resource://file/` URI in a known scope with a path, or a segment is not validly encoded.
49
+ */
50
+ function parseFileAddress(address) {
51
+ try {
52
+ if (!address.startsWith(FILE_ADDRESS_PREFIX)) return void 0;
53
+ const end = address.search(/[?#]/);
54
+ const [scope, ...rest] = address.slice(20, end === -1 ? void 0 : end).split("/");
55
+ if (scope === "session") {
56
+ const [id, ...segments] = rest;
57
+ if (id === void 0 || id === "" || segments.length === 0) return void 0;
58
+ return {
59
+ scope,
60
+ sessionId: decodeURIComponent(id),
61
+ path: segments.map(decodeURIComponent).join("/")
62
+ };
63
+ }
64
+ if (scope === "absolute") {
65
+ const unc = rest[0] === "" && rest.length > 1;
66
+ const segments = (unc ? rest.slice(1) : rest).map(decodeURIComponent);
67
+ if (segments.length === 0 || segments[0] === "") return void 0;
68
+ if (unc) return {
69
+ scope,
70
+ path: `//${segments.join("/")}`
71
+ };
72
+ return {
73
+ scope,
74
+ path: isDriveSegment(segments[0]) ? segments.join("/") : `/${segments.join("/")}`
75
+ };
76
+ }
77
+ return;
78
+ } catch {
79
+ return;
80
+ }
81
+ }
82
+ //#endregion
1
83
  //#region lib/types/index.js
2
84
  /**
3
85
  * Browser-safe Workspace path and display helpers.
@@ -8,13 +90,21 @@ function isWindowsStylePath(value) {
8
90
  return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
9
91
  }
10
92
  /**
93
+ * Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`) or Windows drive or UNC.
94
+ * @param path - the path to classify.
95
+ * @returns `true` for an absolute path; `false` for a Workspace-relative one.
96
+ */
97
+ function isAbsoluteWorkspacePath(path) {
98
+ return path.startsWith("/") || isWindowsStylePath(path);
99
+ }
100
+ /**
11
101
  * Resolve a Workspace-relative path into the Host-facing spelling used by path operations.
12
102
  * @param cwd - Session Workspace root, when known.
13
103
  * @param path - Absolute or Workspace-relative path.
14
104
  * @returns an absolute path when a Workspace root is available, otherwise the original path.
15
105
  */
16
106
  function resolveWorkspacePath(cwd, path) {
17
- if (path.startsWith("/") || isWindowsStylePath(path)) return path;
107
+ if (isAbsoluteWorkspacePath(path)) return path;
18
108
  if (cwd === void 0 || cwd === "") return path;
19
109
  const separator = isWindowsStylePath(cwd) && cwd.includes("\\") ? "\\" : "/";
20
110
  return `${cwd.replace(/[/\\]+$/, "")}${separator}${path.replace(/^[/\\]+/, "")}`;
@@ -45,5 +135,56 @@ function workspaceTitleOf(path) {
45
135
  const separator = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
46
136
  return trimmed.slice(separator + 1);
47
137
  }
138
+ /**
139
+ * Split a path for display: the directories through their last separator, and
140
+ * the final segment after it. Both `/` and `\` separate, so a Windows path
141
+ * splits where its own segments end; trailing separators are dropped first, so
142
+ * a directory path names its own last segment. A path with no separator, or a
143
+ * separator-only path, is all name.
144
+ * @param path - file or directory path using POSIX or Windows separators.
145
+ * @returns the directory prefix (possibly empty) and the final segment.
146
+ */
147
+ function pathPartsOf(path) {
148
+ const trimmed = path.replace(/[/\\]+$/, "");
149
+ if (trimmed === "") return {
150
+ directory: "",
151
+ name: path
152
+ };
153
+ const cut = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\")) + 1;
154
+ return {
155
+ directory: trimmed.slice(0, cut),
156
+ name: trimmed.slice(cut)
157
+ };
158
+ }
159
+ /**
160
+ * The address for a path as a caller holds it: a relative path, or an absolute
161
+ * path inside the Session's workspace, becomes a `session`-scoped address; an
162
+ * absolute path outside it, or one whose workspace root is unknown, keeps its
163
+ * absolute path in that Session's address.
164
+ * @param sessionId - the Session the path is read in.
165
+ * @param cwd - that Session's workspace root, when known.
166
+ * @param path - absolute or workspace-relative path, in either separator spelling.
167
+ * @returns the `dsh-resource://file/…` address.
168
+ */
169
+ function fileAddressFor(sessionId, cwd, path) {
170
+ const normalized = path.replace(/\\/g, "/");
171
+ if (!isAbsoluteWorkspacePath(normalized)) return sessionFileAddress(sessionId, normalized);
172
+ const root = cwd === void 0 ? "" : cwd.replace(/\\/g, "/").replace(/\/+$/, "");
173
+ if (root !== "" && normalized === root) return sessionFileAddress(sessionId, "");
174
+ if (root !== "" && normalized.startsWith(`${root}/`)) return sessionFileAddress(sessionId, normalized.slice(root.length + 1));
175
+ return sessionFileAddress(sessionId, normalized);
176
+ }
177
+ /**
178
+ * Strip the workspace root from a workspace-rooted absolute path (display only).
179
+ * @param text - the path to shorten.
180
+ * @param cwd - session workspace root; absent or empty leaves the path unchanged.
181
+ * @returns the path relative to the workspace root, or unchanged when it is not rooted there.
182
+ */
183
+ function relativizeToCwd(text, cwd) {
184
+ if (cwd === void 0 || cwd === "") return text;
185
+ const root = cwd.replace(/[/\\]+$/, "");
186
+ if (text.startsWith(`${root}/`) || text.startsWith(`${root}\\`)) return text.slice(root.length + 1);
187
+ return text;
188
+ }
48
189
  //#endregion
49
- export { abbreviateHomePath, resolveWorkspacePath, workspaceTitleOf };
190
+ export { abbreviateHomePath, absoluteFileAddress, fileAddressFor, isAbsoluteWorkspacePath, parseFileAddress, pathPartsOf, relativizeToCwd, resolveWorkspacePath, sessionFileAddress, workspaceTitleOf };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The `dsh-resource://file/…` address grammar: how a file is named across the
3
+ * Sidebar and the resource model, built and parsed without touching a
4
+ * filesystem.
5
+ * @module
6
+ */
7
+ /**
8
+ * A file resource address, in one of two scopes.
9
+ *
10
+ * Every resource address is `dsh-resource://<type>/…`, the URI host naming the
11
+ * resource protocol; for `file` the path opens with the scope:
12
+ *
13
+ * - `dsh-resource://file/session/<sessionId>/<path>` names a file by its path,
14
+ * relative to that Session's workspace root or absolute; the
15
+ * Host resolves it against the root it holds for the Session.
16
+ * - `dsh-resource://file/absolute/<path>` names a file by its absolute path with
17
+ * the leading `/` dropped (`dsh-resource://file/absolute/home/ys/notes.txt`;
18
+ * Windows `dsh-resource://file/absolute/C:/x/y.txt`; a UNC path keeps an empty
19
+ * first segment, `dsh-resource://file/absolute//server/share/x.txt`). It carries
20
+ * no Session.
21
+ *
22
+ * Every id and path segment is component-encoded, so a name carrying `#`, `?`,
23
+ * or a space survives the round trip; `:` stays literal so a drive letter reads
24
+ * as written.
25
+ */
26
+ export type FileAddress = {
27
+ readonly scope: 'session';
28
+ /** The Session whose Host workspace resolves the path. */
29
+ readonly sessionId: string;
30
+ /** Absolute or workspace-relative `/`-separated path; empty for the workspace root itself. */
31
+ readonly path: string;
32
+ } | {
33
+ readonly scope: 'absolute';
34
+ /** Absolute `/`-separated path: `/a/b` on POSIX, `C:/a/b` for a Windows drive, `//server/share/a` for a UNC path. */
35
+ readonly path: string;
36
+ };
37
+ /**
38
+ * Build the address of a file read through one Session.
39
+ * @param sessionId - the Session whose Host workspace resolves the path.
40
+ * @param path - absolute or workspace-relative path; backslashes are normalized to `/`, and leading `./` prefixes are dropped.
41
+ * @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
42
+ */
43
+ export declare function sessionFileAddress(sessionId: string, path: string): string;
44
+ /**
45
+ * Build the address of a file by its absolute path.
46
+ * @param path - absolute path; backslashes are normalized to `/` and the leading `/` is dropped,
47
+ * except that a UNC path (`\\server\share`) keeps one empty first segment.
48
+ * @returns the `dsh-resource://file/absolute/<path>` address.
49
+ */
50
+ export declare function absoluteFileAddress(path: string): string;
51
+ /**
52
+ * Read a file address back into its parts without resolving `.` or `..`.
53
+ * Query and fragment suffixes are ignored; encoded path segments are decoded.
54
+ * @param address - a candidate address.
55
+ * @returns the parts, or `undefined` when the string is not a `dsh-resource://file/` URI in a known scope with a path, or a segment is not validly encoded.
56
+ */
57
+ export declare function parseFileAddress(address: string): FileAddress | undefined;
58
+ //# sourceMappingURL=file-address.d.ts.map
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Browser-safe Workspace path and display helpers.
3
- * @module @deepseek-ai/dsh-util-workspace-path
2
+ * Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`) or Windows drive or UNC.
3
+ * @param path - the path to classify.
4
+ * @returns `true` for an absolute path; `false` for a Workspace-relative one.
4
5
  */
6
+ export declare function isAbsoluteWorkspacePath(path: string): boolean;
5
7
  /**
6
8
  * Resolve a Workspace-relative path into the Host-facing spelling used by path operations.
7
9
  * @param cwd - Session Workspace root, when known.
@@ -23,4 +25,36 @@ export declare function abbreviateHomePath(path: string, home?: string): string;
23
25
  * @returns the final segment, or an empty string for a separator-only path.
24
26
  */
25
27
  export declare function workspaceTitleOf(path: string): string;
28
+ /**
29
+ * Split a path for display: the directories through their last separator, and
30
+ * the final segment after it. Both `/` and `\` separate, so a Windows path
31
+ * splits where its own segments end; trailing separators are dropped first, so
32
+ * a directory path names its own last segment. A path with no separator, or a
33
+ * separator-only path, is all name.
34
+ * @param path - file or directory path using POSIX or Windows separators.
35
+ * @returns the directory prefix (possibly empty) and the final segment.
36
+ */
37
+ export declare function pathPartsOf(path: string): {
38
+ readonly directory: string;
39
+ readonly name: string;
40
+ };
41
+ export * from './file-address.ts';
42
+ /**
43
+ * The address for a path as a caller holds it: a relative path, or an absolute
44
+ * path inside the Session's workspace, becomes a `session`-scoped address; an
45
+ * absolute path outside it, or one whose workspace root is unknown, keeps its
46
+ * absolute path in that Session's address.
47
+ * @param sessionId - the Session the path is read in.
48
+ * @param cwd - that Session's workspace root, when known.
49
+ * @param path - absolute or workspace-relative path, in either separator spelling.
50
+ * @returns the `dsh-resource://file/…` address.
51
+ */
52
+ export declare function fileAddressFor(sessionId: string, cwd: string | undefined, path: string): string;
53
+ /**
54
+ * Strip the workspace root from a workspace-rooted absolute path (display only).
55
+ * @param text - the path to shorten.
56
+ * @param cwd - session workspace root; absent or empty leaves the path unchanged.
57
+ * @returns the path relative to the workspace root, or unchanged when it is not rooted there.
58
+ */
59
+ export declare function relativizeToCwd(text: string, cwd: string | undefined): string;
26
60
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-util-workspace-path",
3
3
  "description": "Browser-safe Workspace path and display helpers",
4
- "version": "0.1.3-alpha.2",
4
+ "version": "0.1.5-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },