@deepseek-ai/dsh-util-workspace-path 0.1.5-alpha.1 → 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: 0b820992dffe0ce52530f45b01fa73ad8f1c9a23
6
- README.zh.md: 5648d9e37f1fd6bde8a8c0237051c3f801b01a72
5
+ README.md: ab7787f3649c8f75a0f0e0745bc78ea32edb5925
6
+ README.zh.md: 270c79f7bb57def13a7656e8f76f900c1e094f4f
package/README.md CHANGED
@@ -9,7 +9,7 @@ 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, derives Workspace titles from POSIX or Windows paths, and owns the `dsh-resource://file/…` address grammar that names a workspace file across the Sidebar and the resource model. 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
 
@@ -22,9 +22,9 @@ Browser-safe path helpers shared by Workspace-facing client and controller packa
22
22
  <a id="file-addresses"></a>
23
23
  ## File addresses
24
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. A file address has one of two scopes. `dsh-resource://file/session/<sessionId>/<path>` names a file by its path relative to that Session's workspace root (`dsh-resource://file/session/abc123/src/notes.txt`), which the Host resolves against the root it holds for the Session. `dsh-resource://file/absolute/<path>` names a file by its absolute path with the leading `/` dropped (`dsh-resource://file/absolute/home/ys/notes.txt` on POSIX, `dsh-resource://file/absolute/C:/x/y.txt` for a Windows drive, `dsh-resource://file/absolute//server/share/y.txt` for a UNC path, whose empty first segment keeps its identity); it carries no Session, so the reader's own Session resolves it, and the Host's workspace confinement still applies. 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.
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
26
 
27
- `sessionFileAddress(sessionId, relativePath)` and `absoluteFileAddress(absolutePath)` build one: `\` becomes `/`, a leading `./` or `/` is dropped, and every id and path segment is component-encoded with `:` kept literal, so `#`, `?`, and spaces in a name survive while a drive letter reads as written. `fileAddressFor(sessionId, cwd, path)` chooses the scope for a path as a caller holds it: a relative path, or an absolute path inside `cwd`, becomes `session`-relative; any other absolute path becomes `absolute`. `parseFileAddress(address)` reads one back through `new URL()`: the scheme must be `dsh-resource` and the host exactly `file`; a `session` address yields `{ scope, sessionId, path }` with the workspace-relative path, an `absolute` address yields `{ scope, path }` with the leading `/` restored (`//` for a UNC path) unless the path starts with a drive letter. It returns `undefined` for another type or scheme, an unknown scope, a missing id or path, a non-URL, or a malformed escape the caller decides whether that is a failure.
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
28
 
29
29
  -----
30
30
 
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-library"
9
9
 
10
10
  ## 概述
11
11
 
12
- 供 Workspace 相关客户端和控制器包共享、可在浏览器使用的路径辅助函数。该包负责拼接 Workspace 相对路径、缩写用于展示的 POSIX 主目录、从 POSIX 或 Windows 路径提取 Workspace 标题,并拥有在 Sidebar 与资源模型之间命名工作区文件的 `dsh-resource://file/…` 地址语法;它不提供 Cordis service,也不持有运行时状态。
12
+ 供 Workspace 相关客户端和控制器包共享、可在浏览器使用的路径辅助函数。该包负责拼接 Workspace 相对路径、缩写用于展示的 POSIX 主目录、从 POSIX 或 Windows 路径提取 Workspace 标题、把路径拆成目录部分与末段供展示,并拥有在 Sidebar 与资源模型之间命名工作区文件的 `dsh-resource://file/…` 地址语法。`relativizeToCwd` 在显示时省略工作区前缀,并保留该目录以外的路径。它不提供 Cordis service,也不持有运行时状态。
13
13
 
14
14
  ## 目录
15
15
 
@@ -22,9 +22,9 @@ kind: "package-library"
22
22
  <a id="file-addresses"></a>
23
23
  ## 文件地址
24
24
 
25
- 资源地址 = `dsh-resource://<type>/…`,type(URI 的 host)即资源协议键(`file`,或插件在 `ResourceProtocolMap` 中声明的键);其他 scheme 属导航协议,另行定义。文件地址有两种作用域。`dsh-resource://file/session/<sessionId>/<path>` 以相对该 Session 工作区根的路径命名文件(`dsh-resource://file/session/abc123/src/notes.txt`),由 Host 对它为该 Session 持有的根解析。`dsh-resource://file/absolute/<path>` 以去掉前导 `/` 的绝对路径命名文件(POSIX 上为 `dsh-resource://file/absolute/home/ys/notes.txt`,Windows 盘符为 `dsh-resource://file/absolute/C:/x/y.txt`,UNC 路径为 `dsh-resource://file/absolute//server/share/y.txt`,其空的首段保留 UNC 身份);它不带 Session,由读者自己的 Session 解析,Host 的工作区限制照样适用。语法住在 [`src/file-address.ts`](src/file-address.ts);路径辅助函数留在 [`src/index.ts`](src/index.ts) 并再导出它。
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
26
 
27
- `sessionFileAddress(sessionId, relativePath)` `absoluteFileAddress(absolutePath)` 构造地址:`\` 归一为 `/`,去掉前导 `./` `/`,id 与每个路径段做组件编码但 `:` 保持字面,因此名字里的 `#`、`?`、空格都能保留,盘符也照原样可读。`fileAddressFor(sessionId, cwd, path)` 按调用方手里的路径选作用域:相对路径或落在 `cwd` 内的绝对路径成为 `session` 相对地址,其他绝对路径成为 `absolute` 地址。`parseFileAddress(address)` `new URL()` 读回:scheme 必须是 `dsh-resource`、host 必须恰为 `file`;`session` 地址得到 `{ scope, sessionId, path }`(path 为工作区相对路径),`absolute` 地址得到 `{ scope, path }` 并还原前导 `/`(UNC 路径还原为 `//`),以盘符开头者除外。对其他 type 或 scheme、未知作用域、缺 id 或路径、非 URL、或转义格式错误的输入返回 `undefined`,是否算失败由调用方决定。
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
28
 
29
29
  -----
30
30
 
package/lib/index.js CHANGED
@@ -20,14 +20,14 @@ function isDriveSegment(segment) {
20
20
  return segment !== void 0 && /^[A-Za-z]:$/.test(segment);
21
21
  }
22
22
  /**
23
- * Build the address of a file inside one Session's workspace.
24
- * @param sessionId - the Session whose workspace root the path is relative to.
25
- * @param path - workspace-relative path; backslashes are normalized to `/`, and a leading `./` or `/` is dropped.
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
26
  * @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
27
27
  */
28
28
  function sessionFileAddress(sessionId, path) {
29
- const relative = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "").replace(/^\/+/, "");
30
- return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(relative)}`;
29
+ const normalized = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
30
+ return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(normalized)}`;
31
31
  }
32
32
  /**
33
33
  * Build the address of a file by its absolute path.
@@ -42,15 +42,16 @@ function absoluteFileAddress(path) {
42
42
  return `${FILE_ADDRESS_PREFIX}absolute/${unc ? "/" : ""}${encodePath(absolute)}`;
43
43
  }
44
44
  /**
45
- * Read a file address back into its parts.
45
+ * Read a file address back into its parts without resolving `.` or `..`.
46
+ * Query and fragment suffixes are ignored; encoded path segments are decoded.
46
47
  * @param address - a candidate address.
47
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.
48
49
  */
49
50
  function parseFileAddress(address) {
50
51
  try {
51
- const url = new URL(address);
52
- if (url.protocol !== "dsh-resource:" || url.host !== "file") return void 0;
53
- const [, scope, ...rest] = url.pathname.split("/");
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("/");
54
55
  if (scope === "session") {
55
56
  const [id, ...segments] = rest;
56
57
  if (id === void 0 || id === "" || segments.length === 0) return void 0;
@@ -135,10 +136,31 @@ function workspaceTitleOf(path) {
135
136
  return trimmed.slice(separator + 1);
136
137
  }
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
+ /**
138
160
  * The address for a path as a caller holds it: a relative path, or an absolute
139
161
  * path inside the Session's workspace, becomes a `session`-scoped address; an
140
- * absolute path outside it, or one whose workspace root is unknown, becomes an
141
- * `absolute`-scoped address.
162
+ * absolute path outside it, or one whose workspace root is unknown, keeps its
163
+ * absolute path in that Session's address.
142
164
  * @param sessionId - the Session the path is read in.
143
165
  * @param cwd - that Session's workspace root, when known.
144
166
  * @param path - absolute or workspace-relative path, in either separator spelling.
@@ -150,7 +172,19 @@ function fileAddressFor(sessionId, cwd, path) {
150
172
  const root = cwd === void 0 ? "" : cwd.replace(/\\/g, "/").replace(/\/+$/, "");
151
173
  if (root !== "" && normalized === root) return sessionFileAddress(sessionId, "");
152
174
  if (root !== "" && normalized.startsWith(`${root}/`)) return sessionFileAddress(sessionId, normalized.slice(root.length + 1));
153
- return absoluteFileAddress(normalized);
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;
154
188
  }
155
189
  //#endregion
156
- export { abbreviateHomePath, absoluteFileAddress, fileAddressFor, isAbsoluteWorkspacePath, parseFileAddress, resolveWorkspacePath, sessionFileAddress, workspaceTitleOf };
190
+ export { abbreviateHomePath, absoluteFileAddress, fileAddressFor, isAbsoluteWorkspacePath, parseFileAddress, pathPartsOf, relativizeToCwd, resolveWorkspacePath, sessionFileAddress, workspaceTitleOf };
@@ -10,15 +10,14 @@
10
10
  * Every resource address is `dsh-resource://<type>/…`, the URI host naming the
11
11
  * resource protocol; for `file` the path opens with the scope:
12
12
  *
13
- * - `dsh-resource://file/session/<sessionId>/<path>` names a file by its path
14
- * relative to that Session's workspace root (`src/a.ts`, no leading `/`); the
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
15
  * Host resolves it against the root it holds for the Session.
16
16
  * - `dsh-resource://file/absolute/<path>` names a file by its absolute path with
17
17
  * the leading `/` dropped (`dsh-resource://file/absolute/home/ys/notes.txt`;
18
18
  * Windows `dsh-resource://file/absolute/C:/x/y.txt`; a UNC path keeps an empty
19
19
  * first segment, `dsh-resource://file/absolute//server/share/x.txt`). It carries
20
- * no Session: the reader's own Session resolves it, and the Host's workspace
21
- * confinement still applies.
20
+ * no Session.
22
21
  *
23
22
  * Every id and path segment is component-encoded, so a name carrying `#`, `?`,
24
23
  * or a space survives the round trip; `:` stays literal so a drive letter reads
@@ -26,9 +25,9 @@
26
25
  */
27
26
  export type FileAddress = {
28
27
  readonly scope: 'session';
29
- /** The Session whose workspace root the path is relative to. */
28
+ /** The Session whose Host workspace resolves the path. */
30
29
  readonly sessionId: string;
31
- /** Workspace-relative `/`-separated path, no leading `/`; empty for the root itself. */
30
+ /** Absolute or workspace-relative `/`-separated path; empty for the workspace root itself. */
32
31
  readonly path: string;
33
32
  } | {
34
33
  readonly scope: 'absolute';
@@ -36,9 +35,9 @@ export type FileAddress = {
36
35
  readonly path: string;
37
36
  };
38
37
  /**
39
- * Build the address of a file inside one Session's workspace.
40
- * @param sessionId - the Session whose workspace root the path is relative to.
41
- * @param path - workspace-relative path; backslashes are normalized to `/`, and a leading `./` or `/` is dropped.
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.
42
41
  * @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
43
42
  */
44
43
  export declare function sessionFileAddress(sessionId: string, path: string): string;
@@ -50,7 +49,8 @@ export declare function sessionFileAddress(sessionId: string, path: string): str
50
49
  */
51
50
  export declare function absoluteFileAddress(path: string): string;
52
51
  /**
53
- * Read a file address back into its parts.
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
54
  * @param address - a candidate address.
55
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
56
  */
@@ -25,16 +25,36 @@ export declare function abbreviateHomePath(path: string, home?: string): string;
25
25
  * @returns the final segment, or an empty string for a separator-only path.
26
26
  */
27
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
+ };
28
41
  export * from './file-address.ts';
29
42
  /**
30
43
  * The address for a path as a caller holds it: a relative path, or an absolute
31
44
  * path inside the Session's workspace, becomes a `session`-scoped address; an
32
- * absolute path outside it, or one whose workspace root is unknown, becomes an
33
- * `absolute`-scoped address.
45
+ * absolute path outside it, or one whose workspace root is unknown, keeps its
46
+ * absolute path in that Session's address.
34
47
  * @param sessionId - the Session the path is read in.
35
48
  * @param cwd - that Session's workspace root, when known.
36
49
  * @param path - absolute or workspace-relative path, in either separator spelling.
37
50
  * @returns the `dsh-resource://file/…` address.
38
51
  */
39
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;
40
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.5-alpha.1",
4
+ "version": "0.1.5-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },