@prettier-ai/dsh-workspace 0.1.2-rc.1 → 0.1.3-alpha.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.i18n.yaml +2 -2
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/index.js +49 -21
- package/lib/invariant.js +1 -1
- package/lib/types/index.d.ts +8 -6
- package/lib/types/index.js +18 -14
- package/lib/types/paths.d.ts +24 -8
- package/lib/types/paths.js +36 -8
- package/lib/types/types.d.ts +1 -1
- package/package.json +14 -14
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/workspace/workspace/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: eb3d2a4f89b6023d8a916a41723500186cc10d93
|
|
6
|
+
README.zh.md: 5059a6edd69a82bd8b0a364793ca1b5c941d67e2
|
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ With these rows mounted, creating a project shows up in the list immediately and
|
|
|
50
50
|
|
|
51
51
|
### Creating and ordering projects
|
|
52
52
|
|
|
53
|
-
Create a project from any directory that exists:
|
|
53
|
+
Create a project from any fully qualified directory that exists: filesystem roots such as `C:\` and ordinary directories are valid. Relative paths, Windows drive-relative paths such as `C:work`, missing paths, and files are rejected without creating a project; creating a project for a directory that already has one returns the existing project unchanged. Rename a project at any time, and move it to any position in the list:
|
|
54
54
|
|
|
55
55
|
```text
|
|
56
56
|
// Host consumer code, after the composition above is loaded:
|
package/README.zh.md
CHANGED
|
@@ -50,7 +50,7 @@ kind: "package-reference"
|
|
|
50
50
|
|
|
51
51
|
### 创建与排序项目
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
从任何存在且完整限定的目录创建项目:`C:\` 等文件系统根目录和普通目录都有效。相对路径、`C:work` 等 Windows 盘符相对路径、不存在的路径和文件都会被拒绝,且不会创建项目;为已有项目的目录再次创建会原样返回现有项目。你可以随时重命名项目,并把它移动到列表中的任意位置:
|
|
54
54
|
|
|
55
55
|
```text
|
|
56
56
|
// Host consumer code, after the composition above is loaded:
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { realpath, stat } from "node:fs/promises";
|
|
3
|
-
import { basename } from "node:path";
|
|
4
3
|
import { Service } from "@prettier-ai/cordis";
|
|
4
|
+
import { posix, win32 } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { brandString } from "@prettier-ai/dsh-brand";
|
|
7
7
|
import { defineDomain, domainTable } from "@prettier-ai/dsh-storage-domain";
|
|
@@ -11,18 +11,42 @@ import { defineDomain, domainTable } from "@prettier-ai/dsh-storage-domain";
|
|
|
11
11
|
* @module @prettier-ai/dsh-workspace/src/paths
|
|
12
12
|
*/
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
* Check whether a path names one fixed Host location without process cwd or
|
|
15
|
+
* current-drive resolution.
|
|
16
|
+
* @param path - Candidate Workspace path.
|
|
17
|
+
* @param platform - Host platform; injectable for deterministic path tests.
|
|
18
|
+
* @returns Whether the path is fully qualified on that platform.
|
|
19
|
+
*/
|
|
20
|
+
function fullyQualifiedWorkspacePath(path, platform = process.platform) {
|
|
21
|
+
if (platform !== "win32") return posix.isAbsolute(path);
|
|
22
|
+
const root = win32.parse(path).root;
|
|
23
|
+
return win32.isAbsolute(path) && root !== "\\" && root !== "/";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Derive a non-empty default title from a canonical Workspace path.
|
|
27
|
+
* @param path - Canonical Workspace path.
|
|
28
|
+
* @param platform - Host platform; injectable for deterministic path tests.
|
|
29
|
+
* @returns The final segment when present, otherwise the complete root spelling.
|
|
30
|
+
*/
|
|
31
|
+
function defaultWorkspaceTitle(path, platform = process.platform) {
|
|
32
|
+
const pathApi = platform === "win32" ? win32 : posix;
|
|
33
|
+
return pathApi.basename(path) || pathApi.parse(path).root;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Canonicalize a fully qualified directory path via `fs.realpath`: trailing
|
|
37
|
+
* slashes, `..` segments, and symlinks are all resolved. This is the ONE
|
|
38
|
+
* uniqueness canon of the package — workspace paths are stored canonicalized,
|
|
39
|
+
* uniqueness is string equality of canonicalized paths (a symlink to an
|
|
40
|
+
* existing workspace's directory collides), and attach-time session `cwd`
|
|
41
|
+
* checks go through the same canon. Relative paths reject before `realpath` can
|
|
42
|
+
* resolve them from the Host cwd or current Windows drive. A path that does not
|
|
43
|
+
* exist rejects with the original `ENOENT` — this is `create`'s reject path (a
|
|
44
|
+
* workspace must point at an existing directory).
|
|
22
45
|
* @param path - The path to canonicalize.
|
|
23
46
|
* @returns the canonical absolute path.
|
|
24
47
|
*/
|
|
25
48
|
async function realpathNormalize(path) {
|
|
49
|
+
if (!fullyQualifiedWorkspacePath(path)) throw new TypeError(`Workspace path is not fully qualified: '${path}'`);
|
|
26
50
|
return await realpath(path);
|
|
27
51
|
}
|
|
28
52
|
//#endregion
|
|
@@ -318,23 +342,23 @@ var WorkspaceRegistry = class extends Service {
|
|
|
318
342
|
await this.recoverPendingMutation();
|
|
319
343
|
this.validateStoredState(this.state);
|
|
320
344
|
if (!this.state.initialized) {
|
|
321
|
-
const headers = await this.
|
|
345
|
+
const headers = await this.listStoredHeaders();
|
|
322
346
|
await this.replaceHeaderIndex(headers);
|
|
323
347
|
await this.bootstrap(headers);
|
|
324
|
-
} else if (this.table.size > 0) await this.replaceHeaderIndex(await this.
|
|
348
|
+
} else if (this.table.size > 0) await this.replaceHeaderIndex(await this.listStoredHeaders());
|
|
325
349
|
await this.indexLiveSessions();
|
|
326
350
|
this.validateStoredState(this.requireState());
|
|
327
351
|
this.rebuildEntities();
|
|
328
352
|
this.reportFilteredCandidates();
|
|
329
353
|
}
|
|
330
354
|
/**
|
|
331
|
-
* Create or reuse a workspace for an existing directory. The
|
|
332
|
-
* canonicalized through `fs.realpath`; a nonexistent
|
|
333
|
-
*
|
|
334
|
-
*
|
|
355
|
+
* Create or reuse a workspace for an existing directory. The fully qualified
|
|
356
|
+
* path is canonicalized through `fs.realpath`; a relative, nonexistent, or
|
|
357
|
+
* non-directory path rejects. Repeated calls for the same canonical path
|
|
358
|
+
* return the existing entity without changing its title.
|
|
335
359
|
* A newly created workspace is prepended to the durable registry order.
|
|
336
360
|
* Different canonical paths may share a display title.
|
|
337
|
-
* @param path - Existing directory to own, in
|
|
361
|
+
* @param path - Existing directory to own, in a fully qualified path spelling.
|
|
338
362
|
* @param title - Display title used only when a new record is created.
|
|
339
363
|
* @returns the existing or newly durable workspace.
|
|
340
364
|
*/
|
|
@@ -439,14 +463,14 @@ var WorkspaceRegistry = class extends Service {
|
|
|
439
463
|
async sessionKnown(id) {
|
|
440
464
|
if (this.ctx.get("sessions")?.get(id) !== void 0) return true;
|
|
441
465
|
if (this.headers.has(id)) return true;
|
|
442
|
-
await this.indexHeaders(await this.
|
|
466
|
+
await this.indexHeaders(await this.listStoredHeaders());
|
|
443
467
|
return this.headers.has(id);
|
|
444
468
|
}
|
|
445
469
|
/**
|
|
446
470
|
* Resolve by canonical directory path without creating or mutating a
|
|
447
471
|
* workspace. A missing path rejects during `realpath`; an existing unowned
|
|
448
472
|
* directory returns `undefined`.
|
|
449
|
-
* @param path - Existing directory path in
|
|
473
|
+
* @param path - Existing directory path in a fully qualified spelling.
|
|
450
474
|
* @returns the workspace owning the canonical path, when one exists.
|
|
451
475
|
*/
|
|
452
476
|
async resolveByPath(path) {
|
|
@@ -455,7 +479,7 @@ var WorkspaceRegistry = class extends Service {
|
|
|
455
479
|
}
|
|
456
480
|
async createCanonical(canonical, title) {
|
|
457
481
|
for (const entity of this.entities.values()) if (entity.path === canonical) return entity;
|
|
458
|
-
const workspaceName = title ??
|
|
482
|
+
const workspaceName = title ?? defaultWorkspaceTitle(canonical);
|
|
459
483
|
const table = this.requireTable();
|
|
460
484
|
const state = this.requireState();
|
|
461
485
|
const id = WorkspaceId(randomUUID());
|
|
@@ -602,7 +626,7 @@ var WorkspaceRegistry = class extends Service {
|
|
|
602
626
|
const createdAt = new Date(group.newestAt).toISOString();
|
|
603
627
|
const record = {
|
|
604
628
|
path: group.path,
|
|
605
|
-
title:
|
|
629
|
+
title: defaultWorkspaceTitle(group.path),
|
|
606
630
|
sessionIds,
|
|
607
631
|
createdAt,
|
|
608
632
|
updatedAt: createdAt
|
|
@@ -701,6 +725,10 @@ var WorkspaceRegistry = class extends Service {
|
|
|
701
725
|
this.invalidSessionPaths.set(header.id, `cwd '${header.cwd}' does not resolve`);
|
|
702
726
|
}
|
|
703
727
|
}
|
|
728
|
+
/** Every stored session's header, projected from the persistence snapshot listing. */
|
|
729
|
+
async listStoredHeaders() {
|
|
730
|
+
return (await this.ctx.sessionPersistence.list()).map((snapshot) => snapshot.header);
|
|
731
|
+
}
|
|
704
732
|
async indexLiveSessions() {
|
|
705
733
|
const sessions = this.ctx.get("sessions");
|
|
706
734
|
if (sessions === void 0) return;
|
|
@@ -725,7 +753,7 @@ var WorkspaceRegistry = class extends Service {
|
|
|
725
753
|
}
|
|
726
754
|
const cached = this.headers.get(id);
|
|
727
755
|
if (cached !== void 0) return cached;
|
|
728
|
-
const headers = await this.
|
|
756
|
+
const headers = await this.listStoredHeaders();
|
|
729
757
|
await this.indexHeaders(headers);
|
|
730
758
|
const header = this.headers.get(id);
|
|
731
759
|
if (header === void 0) throw new Error(`cannot validate session '${id}': session persistence holds no such session`);
|
package/lib/invariant.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:crypto";
|
|
2
2
|
import "node:fs/promises";
|
|
3
|
-
import "node:path";
|
|
4
3
|
import { Service } from "@prettier-ai/cordis";
|
|
4
|
+
import "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { brandString } from "@prettier-ai/dsh-brand";
|
|
7
7
|
import { defineDomain, domainTable } from "@prettier-ai/dsh-storage-domain";
|
package/lib/types/index.d.ts
CHANGED
|
@@ -66,13 +66,13 @@ export declare class WorkspaceRegistry extends Service {
|
|
|
66
66
|
/** Open the domain, finish bootstrap when required, and rebuild the ordered cache. */
|
|
67
67
|
protected [Service.init](): Promise<void>;
|
|
68
68
|
/**
|
|
69
|
-
* Create or reuse a workspace for an existing directory. The
|
|
70
|
-
* canonicalized through `fs.realpath`; a nonexistent
|
|
71
|
-
*
|
|
72
|
-
*
|
|
69
|
+
* Create or reuse a workspace for an existing directory. The fully qualified
|
|
70
|
+
* path is canonicalized through `fs.realpath`; a relative, nonexistent, or
|
|
71
|
+
* non-directory path rejects. Repeated calls for the same canonical path
|
|
72
|
+
* return the existing entity without changing its title.
|
|
73
73
|
* A newly created workspace is prepended to the durable registry order.
|
|
74
74
|
* Different canonical paths may share a display title.
|
|
75
|
-
* @param path - Existing directory to own, in
|
|
75
|
+
* @param path - Existing directory to own, in a fully qualified path spelling.
|
|
76
76
|
* @param title - Display title used only when a new record is created.
|
|
77
77
|
* @returns the existing or newly durable workspace.
|
|
78
78
|
*/
|
|
@@ -133,7 +133,7 @@ export declare class WorkspaceRegistry extends Service {
|
|
|
133
133
|
* Resolve by canonical directory path without creating or mutating a
|
|
134
134
|
* workspace. A missing path rejects during `realpath`; an existing unowned
|
|
135
135
|
* directory returns `undefined`.
|
|
136
|
-
* @param path - Existing directory path in
|
|
136
|
+
* @param path - Existing directory path in a fully qualified spelling.
|
|
137
137
|
* @returns the workspace owning the canonical path, when one exists.
|
|
138
138
|
*/
|
|
139
139
|
resolveByPath(path: string): Promise<Workspace | undefined>;
|
|
@@ -151,6 +151,8 @@ export declare class WorkspaceRegistry extends Service {
|
|
|
151
151
|
private replaceHeaderIndex;
|
|
152
152
|
private indexHeaders;
|
|
153
153
|
private indexHeader;
|
|
154
|
+
/** Every stored session's header, projected from the persistence snapshot listing. */
|
|
155
|
+
private listStoredHeaders;
|
|
154
156
|
private indexLiveSessions;
|
|
155
157
|
private reportFilteredCandidates;
|
|
156
158
|
private readSessionHeader;
|
package/lib/types/index.js
CHANGED
|
@@ -6,11 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
8
|
import { stat } from 'node:fs/promises';
|
|
9
|
-
import { basename } from 'node:path';
|
|
10
9
|
import { Service } from '@prettier-ai/cordis';
|
|
11
10
|
import { WorkspaceEntity } from "./entity.js";
|
|
12
11
|
export { WorkspaceMoveInvalidError } from "./entity.js";
|
|
13
|
-
import { realpathNormalize } from "./paths.js";
|
|
12
|
+
import { defaultWorkspaceTitle, realpathNormalize } from "./paths.js";
|
|
14
13
|
import { workspaceDomainSpec } from "./spec.js";
|
|
15
14
|
export { workspaceDomainState, workspaceRecord, workspaceDomainSpec } from "./spec.js";
|
|
16
15
|
export { realpathNormalize } from "./paths.js";
|
|
@@ -90,12 +89,12 @@ export class WorkspaceRegistry extends Service {
|
|
|
90
89
|
await this.recoverPendingMutation();
|
|
91
90
|
this.validateStoredState(this.state);
|
|
92
91
|
if (!this.state.initialized) {
|
|
93
|
-
const headers = await this.
|
|
92
|
+
const headers = await this.listStoredHeaders();
|
|
94
93
|
await this.replaceHeaderIndex(headers);
|
|
95
94
|
await this.bootstrap(headers);
|
|
96
95
|
}
|
|
97
96
|
else if (this.table.size > 0) {
|
|
98
|
-
await this.replaceHeaderIndex(await this.
|
|
97
|
+
await this.replaceHeaderIndex(await this.listStoredHeaders());
|
|
99
98
|
}
|
|
100
99
|
await this.indexLiveSessions();
|
|
101
100
|
this.validateStoredState(this.requireState());
|
|
@@ -103,13 +102,13 @@ export class WorkspaceRegistry extends Service {
|
|
|
103
102
|
this.reportFilteredCandidates();
|
|
104
103
|
}
|
|
105
104
|
/**
|
|
106
|
-
* Create or reuse a workspace for an existing directory. The
|
|
107
|
-
* canonicalized through `fs.realpath`; a nonexistent
|
|
108
|
-
*
|
|
109
|
-
*
|
|
105
|
+
* Create or reuse a workspace for an existing directory. The fully qualified
|
|
106
|
+
* path is canonicalized through `fs.realpath`; a relative, nonexistent, or
|
|
107
|
+
* non-directory path rejects. Repeated calls for the same canonical path
|
|
108
|
+
* return the existing entity without changing its title.
|
|
110
109
|
* A newly created workspace is prepended to the durable registry order.
|
|
111
110
|
* Different canonical paths may share a display title.
|
|
112
|
-
* @param path - Existing directory to own, in
|
|
111
|
+
* @param path - Existing directory to own, in a fully qualified path spelling.
|
|
113
112
|
* @param title - Display title used only when a new record is created.
|
|
114
113
|
* @returns the existing or newly durable workspace.
|
|
115
114
|
*/
|
|
@@ -225,14 +224,14 @@ export class WorkspaceRegistry extends Service {
|
|
|
225
224
|
return true;
|
|
226
225
|
if (this.headers.has(id))
|
|
227
226
|
return true;
|
|
228
|
-
await this.indexHeaders(await this.
|
|
227
|
+
await this.indexHeaders(await this.listStoredHeaders());
|
|
229
228
|
return this.headers.has(id);
|
|
230
229
|
}
|
|
231
230
|
/**
|
|
232
231
|
* Resolve by canonical directory path without creating or mutating a
|
|
233
232
|
* workspace. A missing path rejects during `realpath`; an existing unowned
|
|
234
233
|
* directory returns `undefined`.
|
|
235
|
-
* @param path - Existing directory path in
|
|
234
|
+
* @param path - Existing directory path in a fully qualified spelling.
|
|
236
235
|
* @returns the workspace owning the canonical path, when one exists.
|
|
237
236
|
*/
|
|
238
237
|
async resolveByPath(path) {
|
|
@@ -248,7 +247,7 @@ export class WorkspaceRegistry extends Service {
|
|
|
248
247
|
if (entity.path === canonical)
|
|
249
248
|
return entity;
|
|
250
249
|
}
|
|
251
|
-
const workspaceName = title ??
|
|
250
|
+
const workspaceName = title ?? defaultWorkspaceTitle(canonical);
|
|
252
251
|
const table = this.requireTable();
|
|
253
252
|
const state = this.requireState();
|
|
254
253
|
const id = WorkspaceId(randomUUID());
|
|
@@ -413,7 +412,7 @@ export class WorkspaceRegistry extends Service {
|
|
|
413
412
|
const createdAt = new Date(group.newestAt).toISOString();
|
|
414
413
|
const record = {
|
|
415
414
|
path: group.path,
|
|
416
|
-
title:
|
|
415
|
+
title: defaultWorkspaceTitle(group.path),
|
|
417
416
|
sessionIds,
|
|
418
417
|
createdAt,
|
|
419
418
|
updatedAt: createdAt,
|
|
@@ -532,6 +531,11 @@ export class WorkspaceRegistry extends Service {
|
|
|
532
531
|
this.invalidSessionPaths.set(header.id, `cwd '${header.cwd}' does not resolve`);
|
|
533
532
|
}
|
|
534
533
|
}
|
|
534
|
+
/** Every stored session's header, projected from the persistence snapshot listing. */
|
|
535
|
+
async listStoredHeaders() {
|
|
536
|
+
const snapshots = await this.ctx.sessionPersistence.list();
|
|
537
|
+
return snapshots.map(snapshot => snapshot.header);
|
|
538
|
+
}
|
|
535
539
|
async indexLiveSessions() {
|
|
536
540
|
const sessions = this.ctx.get('sessions');
|
|
537
541
|
if (sessions === undefined)
|
|
@@ -562,7 +566,7 @@ export class WorkspaceRegistry extends Service {
|
|
|
562
566
|
const cached = this.headers.get(id);
|
|
563
567
|
if (cached !== undefined)
|
|
564
568
|
return cached;
|
|
565
|
-
const headers = await this.
|
|
569
|
+
const headers = await this.listStoredHeaders();
|
|
566
570
|
await this.indexHeaders(headers);
|
|
567
571
|
const header = this.headers.get(id);
|
|
568
572
|
if (header === undefined) {
|
package/lib/types/paths.d.ts
CHANGED
|
@@ -3,14 +3,30 @@
|
|
|
3
3
|
* @module @prettier-ai/dsh-workspace/src/paths
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
* Check whether a path names one fixed Host location without process cwd or
|
|
7
|
+
* current-drive resolution.
|
|
8
|
+
* @param path - Candidate Workspace path.
|
|
9
|
+
* @param platform - Host platform; injectable for deterministic path tests.
|
|
10
|
+
* @returns Whether the path is fully qualified on that platform.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fullyQualifiedWorkspacePath(path: string, platform?: NodeJS.Platform): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Derive a non-empty default title from a canonical Workspace path.
|
|
15
|
+
* @param path - Canonical Workspace path.
|
|
16
|
+
* @param platform - Host platform; injectable for deterministic path tests.
|
|
17
|
+
* @returns The final segment when present, otherwise the complete root spelling.
|
|
18
|
+
*/
|
|
19
|
+
export declare function defaultWorkspaceTitle(path: string, platform?: NodeJS.Platform): string;
|
|
20
|
+
/**
|
|
21
|
+
* Canonicalize a fully qualified directory path via `fs.realpath`: trailing
|
|
22
|
+
* slashes, `..` segments, and symlinks are all resolved. This is the ONE
|
|
23
|
+
* uniqueness canon of the package — workspace paths are stored canonicalized,
|
|
24
|
+
* uniqueness is string equality of canonicalized paths (a symlink to an
|
|
25
|
+
* existing workspace's directory collides), and attach-time session `cwd`
|
|
26
|
+
* checks go through the same canon. Relative paths reject before `realpath` can
|
|
27
|
+
* resolve them from the Host cwd or current Windows drive. A path that does not
|
|
28
|
+
* exist rejects with the original `ENOENT` — this is `create`'s reject path (a
|
|
29
|
+
* workspace must point at an existing directory).
|
|
14
30
|
* @param path - The path to canonicalize.
|
|
15
31
|
* @returns the canonical absolute path.
|
|
16
32
|
*/
|
package/lib/types/paths.js
CHANGED
|
@@ -3,19 +3,47 @@
|
|
|
3
3
|
* @module @prettier-ai/dsh-workspace/src/paths
|
|
4
4
|
*/
|
|
5
5
|
import { realpath } from 'node:fs/promises';
|
|
6
|
+
import { posix, win32 } from 'node:path';
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
* Check whether a path names one fixed Host location without process cwd or
|
|
9
|
+
* current-drive resolution.
|
|
10
|
+
* @param path - Candidate Workspace path.
|
|
11
|
+
* @param platform - Host platform; injectable for deterministic path tests.
|
|
12
|
+
* @returns Whether the path is fully qualified on that platform.
|
|
13
|
+
*/
|
|
14
|
+
export function fullyQualifiedWorkspacePath(path, platform = process.platform) {
|
|
15
|
+
if (platform !== 'win32')
|
|
16
|
+
return posix.isAbsolute(path);
|
|
17
|
+
const root = win32.parse(path).root;
|
|
18
|
+
return win32.isAbsolute(path) && root !== '\\' && root !== '/';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Derive a non-empty default title from a canonical Workspace path.
|
|
22
|
+
* @param path - Canonical Workspace path.
|
|
23
|
+
* @param platform - Host platform; injectable for deterministic path tests.
|
|
24
|
+
* @returns The final segment when present, otherwise the complete root spelling.
|
|
25
|
+
*/
|
|
26
|
+
export function defaultWorkspaceTitle(path, platform = process.platform) {
|
|
27
|
+
const pathApi = platform === 'win32' ? win32 : posix;
|
|
28
|
+
return pathApi.basename(path) || pathApi.parse(path).root;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Canonicalize a fully qualified directory path via `fs.realpath`: trailing
|
|
32
|
+
* slashes, `..` segments, and symlinks are all resolved. This is the ONE
|
|
33
|
+
* uniqueness canon of the package — workspace paths are stored canonicalized,
|
|
34
|
+
* uniqueness is string equality of canonicalized paths (a symlink to an
|
|
35
|
+
* existing workspace's directory collides), and attach-time session `cwd`
|
|
36
|
+
* checks go through the same canon. Relative paths reject before `realpath` can
|
|
37
|
+
* resolve them from the Host cwd or current Windows drive. A path that does not
|
|
38
|
+
* exist rejects with the original `ENOENT` — this is `create`'s reject path (a
|
|
39
|
+
* workspace must point at an existing directory).
|
|
15
40
|
* @param path - The path to canonicalize.
|
|
16
41
|
* @returns the canonical absolute path.
|
|
17
42
|
*/
|
|
18
43
|
export async function realpathNormalize(path) {
|
|
44
|
+
if (!fullyQualifiedWorkspacePath(path)) {
|
|
45
|
+
throw new TypeError(`Workspace path is not fully qualified: '${path}'`);
|
|
46
|
+
}
|
|
19
47
|
return await realpath(path);
|
|
20
48
|
}
|
|
21
49
|
//# sourceMappingURL=paths.js.map
|
package/lib/types/types.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface Workspace {
|
|
|
34
34
|
* afterwards, even when the directory disappears (see {@link status}).
|
|
35
35
|
*/
|
|
36
36
|
readonly path: string;
|
|
37
|
-
/** Display title. Defaults to
|
|
37
|
+
/** Display title. Defaults to the final path segment, or a filesystem root's own spelling; duplicates are allowed. */
|
|
38
38
|
readonly title: string;
|
|
39
39
|
/** ISO-8601 creation instant, stamped at create and never rewritten. */
|
|
40
40
|
readonly createdAt: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prettier-ai/dsh-workspace",
|
|
3
3
|
"description": "Workspace entity registry (ctx.workspaceRegistry): durable workspace records with validated session attachment over the domain data form for the DeepSeek Harness",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -37,25 +37,25 @@
|
|
|
37
37
|
],
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@prettier-ai/dsh-invariants": "^0.1.2-rc.1",
|
|
41
40
|
"@prettier-ai/cordis": "^4.0.2",
|
|
42
|
-
"@prettier-ai/dsh-
|
|
43
|
-
"@prettier-ai/dsh-
|
|
44
|
-
"@prettier-ai/dsh-
|
|
45
|
-
"@prettier-ai/dsh-
|
|
46
|
-
"@prettier-ai/dsh-
|
|
41
|
+
"@prettier-ai/dsh-invariants": "^0.1.3-alpha.1",
|
|
42
|
+
"@prettier-ai/dsh-session-persistence": "^0.1.3-alpha.1",
|
|
43
|
+
"@prettier-ai/dsh-storage": "^0.1.3-alpha.1",
|
|
44
|
+
"@prettier-ai/dsh-session": "^0.1.3-alpha.1",
|
|
45
|
+
"@prettier-ai/dsh-storage-domain": "^0.1.3-alpha.1",
|
|
46
|
+
"@prettier-ai/dsh-typert-protocol": "^0.1.3-alpha.1"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"zod": "^4.4.3",
|
|
50
|
-
"@prettier-ai/dsh-brand": "^0.1.
|
|
50
|
+
"@prettier-ai/dsh-brand": "^0.1.3-alpha.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@prettier-ai/cordis": "^4.0.2",
|
|
54
|
-
"@prettier-ai/dsh-session
|
|
55
|
-
"@prettier-ai/dsh-
|
|
56
|
-
"@prettier-ai/dsh-
|
|
57
|
-
"@prettier-ai/dsh-
|
|
58
|
-
"@prettier-ai/dsh-
|
|
59
|
-
"@prettier-ai/dsh-
|
|
54
|
+
"@prettier-ai/dsh-session": "^0.1.3-alpha.1",
|
|
55
|
+
"@prettier-ai/dsh-storage": "^0.1.3-alpha.1",
|
|
56
|
+
"@prettier-ai/dsh-session-persistence": "^0.1.3-alpha.1",
|
|
57
|
+
"@prettier-ai/dsh-invariants": "^0.1.3-alpha.1",
|
|
58
|
+
"@prettier-ai/dsh-typert-protocol": "^0.1.3-alpha.1",
|
|
59
|
+
"@prettier-ai/dsh-storage-domain": "^0.1.3-alpha.1"
|
|
60
60
|
}
|
|
61
61
|
}
|