@silo-code/sdk 0.21.0 → 0.23.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/editor-service.d.ts +135 -3
- package/dist/editor-service.d.ts.map +1 -1
- package/dist/event.d.ts +28 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +2 -0
- package/dist/event.js.map +1 -0
- package/dist/file-service.d.ts +33 -2
- package/dist/file-service.d.ts.map +1 -1
- package/dist/index.d.ts +15 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/dist/network-service.d.ts +46 -2
- package/dist/network-service.d.ts.map +1 -1
- package/dist/path.d.ts +62 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +150 -0
- package/dist/path.js.map +1 -0
- package/dist/process-service.d.ts +24 -4
- package/dist/process-service.d.ts.map +1 -1
- package/dist/processes-service.d.ts +1 -1
- package/dist/search-service.d.ts +11 -0
- package/dist/search-service.d.ts.map +1 -1
- package/dist/system-service.d.ts +2 -2
- package/dist/terminal-service.d.ts +66 -0
- package/dist/terminal-service.d.ts.map +1 -1
- package/dist/types.d.ts +12 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/workspace-service.d.ts +13 -0
- package/dist/workspace-service.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/editor-service.ts +141 -5
- package/src/event.ts +28 -0
- package/src/file-service.ts +33 -2
- package/src/index.ts +24 -5
- package/src/network-service.ts +51 -2
- package/src/path.test.ts +135 -0
- package/src/path.ts +188 -0
- package/src/process-service.ts +24 -4
- package/src/processes-service.ts +1 -1
- package/src/search-service.ts +11 -0
- package/src/system-service.ts +2 -2
- package/src/terminal-service.ts +73 -0
- package/src/types.ts +12 -5
- package/src/workspace-service.ts +13 -0
package/src/editor-service.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { Disposable } from "./types";
|
|
2
|
+
import type { Event } from "./event";
|
|
3
|
+
import type { EditorMode } from "./domain-types";
|
|
2
4
|
|
|
3
5
|
// `ctx.editors` — the editor & document domain (public contract). Opening files
|
|
4
|
-
// into editor tabs, the active-editor save/close commands,
|
|
5
|
-
// provider registry. The host implementation
|
|
6
|
+
// into editor tabs, the active-editor save/close commands, the diff-content
|
|
7
|
+
// provider registry, and reactive active-editor state. The host implementation
|
|
8
|
+
// lives in the extension host.
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Options for the editor open methods.
|
|
@@ -65,7 +68,7 @@ export interface EditorViewInfo {
|
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
/**
|
|
68
|
-
* Save callbacks an editor
|
|
71
|
+
* Save callbacks an editor registers via
|
|
69
72
|
* {@link EditorService.registerSaveHandler}, so the active-editor `save` /
|
|
70
73
|
* `saveAs` commands can dispatch to whichever editor is focused.
|
|
71
74
|
*
|
|
@@ -143,11 +146,78 @@ export type DiffContentProvider = (
|
|
|
143
146
|
request: DiffContentRequest,
|
|
144
147
|
) => Promise<DiffContent>;
|
|
145
148
|
|
|
149
|
+
/**
|
|
150
|
+
* A point-in-time snapshot of the focused editor tab. Part of
|
|
151
|
+
* {@link EditorsState}, returned by {@link EditorService.getState} and
|
|
152
|
+
* delivered to {@link EditorService.subscribe} listeners.
|
|
153
|
+
*
|
|
154
|
+
* @category Consumer Services
|
|
155
|
+
* @public
|
|
156
|
+
*/
|
|
157
|
+
export interface ActiveEditorInfo {
|
|
158
|
+
/** The focused editor tab's record id — matches {@link EditorRecord.id}. */
|
|
159
|
+
editorId: string;
|
|
160
|
+
/**
|
|
161
|
+
* The absolute file path of the focused tab, or `null` for an untitled
|
|
162
|
+
* buffer.
|
|
163
|
+
*/
|
|
164
|
+
filePath: string | null;
|
|
165
|
+
/**
|
|
166
|
+
* The {@link Editor.id} of the presenter rendering the tab
|
|
167
|
+
* (e.g. `"core.text-editor"`, `"silo.markdown-preview"`). Empty string
|
|
168
|
+
* when the presenter could not be resolved (rare: editor registered after
|
|
169
|
+
* the tab was opened).
|
|
170
|
+
*/
|
|
171
|
+
viewId: string;
|
|
172
|
+
/** Whether the tab is in text or diff mode. */
|
|
173
|
+
mode: EditorMode;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* A frozen, referentially-stable snapshot of editor domain state. Returned by
|
|
178
|
+
* {@link EditorService.getState} and delivered to
|
|
179
|
+
* {@link EditorService.subscribe} listeners.
|
|
180
|
+
*
|
|
181
|
+
* Compatible with `useServiceState` — just pass `ctx.editors` directly.
|
|
182
|
+
*
|
|
183
|
+
* @category Consumer Services
|
|
184
|
+
* @public
|
|
185
|
+
*/
|
|
186
|
+
export interface EditorsState {
|
|
187
|
+
/**
|
|
188
|
+
* The focused editor tab, or `null` when the active dock panel is not an
|
|
189
|
+
* editor tab (e.g. a terminal, a dock panel kind, or nothing at all).
|
|
190
|
+
*/
|
|
191
|
+
active: ActiveEditorInfo | null;
|
|
192
|
+
/**
|
|
193
|
+
* `true` once the persisted workspace state has loaded from disk. Matches
|
|
194
|
+
* `WorkspaceState.hydrated` — guard state-restoring code on this flag.
|
|
195
|
+
*/
|
|
196
|
+
hydrated: boolean;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Payload delivered to {@link EditorService.onDidSave} listeners after an
|
|
201
|
+
* editor tab's contents are written to disk.
|
|
202
|
+
*
|
|
203
|
+
* @category Consumer Services
|
|
204
|
+
* @public
|
|
205
|
+
*/
|
|
206
|
+
export interface EditorSaveEvent {
|
|
207
|
+
/** The saved tab's editor record id — matches {@link EditorRecord.id}. */
|
|
208
|
+
editorId: string;
|
|
209
|
+
/**
|
|
210
|
+
* Absolute path the contents were written to. For a save-as (or first save
|
|
211
|
+
* of an untitled buffer) this is the newly chosen path, not the old one.
|
|
212
|
+
*/
|
|
213
|
+
filePath: string;
|
|
214
|
+
}
|
|
215
|
+
|
|
146
216
|
/**
|
|
147
217
|
* The editor & document domain, exposed as {@link ExtensionContext.editors}.
|
|
148
218
|
* Open files into editor tabs, drive the active editor (save / close), and let
|
|
149
|
-
*
|
|
150
|
-
*
|
|
219
|
+
* editors register save handlers. The single entry point for opening editors —
|
|
220
|
+
* prefer it over reaching into workspace/editor state.
|
|
151
221
|
*
|
|
152
222
|
* @category Consumer Services
|
|
153
223
|
* @public
|
|
@@ -211,4 +281,70 @@ export interface EditorService {
|
|
|
211
281
|
providerId: string,
|
|
212
282
|
provider: DiffContentProvider,
|
|
213
283
|
): Disposable;
|
|
284
|
+
/**
|
|
285
|
+
* The current buffer text of an open editor tab, including unsaved edits.
|
|
286
|
+
*
|
|
287
|
+
* Resolves `undefined` when the tab isn't text-backed (e.g. the image
|
|
288
|
+
* viewer) or hasn't mounted yet — a lazy-mounted dock panel is **not**
|
|
289
|
+
* force-mounted to read its text. It is async precisely because the text
|
|
290
|
+
* lives in the mounted editor component, not in host state.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* const text = await ctx.editors.getText(editorId);
|
|
295
|
+
* if (text !== undefined) ctx.log.info(`${text.length} chars`);
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
getText(editorId: string): Promise<string | undefined>;
|
|
299
|
+
/**
|
|
300
|
+
* Whether an open editor tab has unsaved changes. Returns `false` for an
|
|
301
|
+
* unknown id or a tab that isn't mounted / text-backed.
|
|
302
|
+
*/
|
|
303
|
+
isDirty(editorId: string): boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Fires after an editor tab's contents are saved to disk — a formatter,
|
|
306
|
+
* linter, or build-on-save extension's entry point. See {@link Event}.
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```ts
|
|
310
|
+
* ctx.subscriptions.push(
|
|
311
|
+
* ctx.editors.onDidSave(({ editorId, filePath }) => {
|
|
312
|
+
* ctx.log.info(`saved ${filePath}`);
|
|
313
|
+
* }),
|
|
314
|
+
* );
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
onDidSave: Event<EditorSaveEvent>;
|
|
318
|
+
/**
|
|
319
|
+
* Current frozen snapshot of editor state. The returned object is
|
|
320
|
+
* referentially stable between renders — `getState() === getState()` when
|
|
321
|
+
* nothing has changed, which satisfies `useSyncExternalStore`'s contract and
|
|
322
|
+
* means `useServiceState(ctx.editors)` works without extra memoization.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* const { active } = ctx.editors.getState();
|
|
327
|
+
* if (active) ctx.log.info(`Active file: ${active.filePath ?? "(untitled)"}`);
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
getState(): EditorsState;
|
|
331
|
+
/**
|
|
332
|
+
* Subscribe to changes in the active editor. The listener is called whenever
|
|
333
|
+
* `active` changes (tab focus moves, workspace switches, editor opens or
|
|
334
|
+
* closes) or `hydrated` flips. Returns a {@link Disposable} that cancels the
|
|
335
|
+
* subscription.
|
|
336
|
+
*
|
|
337
|
+
* Use `useServiceState(ctx.editors)` in React components instead of calling
|
|
338
|
+
* `subscribe` directly — it wraps `getState` + `subscribe` for you.
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```ts
|
|
342
|
+
* ctx.subscriptions.push(
|
|
343
|
+
* ctx.editors.subscribe(({ active }) => {
|
|
344
|
+
* statusItem.setTitle(active?.filePath ?? "No file");
|
|
345
|
+
* }),
|
|
346
|
+
* );
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
subscribe(listener: (state: EditorsState) => void): Disposable;
|
|
214
350
|
}
|
package/src/event.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Disposable } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A subscribable event: call it with a listener and receive a
|
|
5
|
+
* {@link Disposable} that cancels the subscription. Modeled on VS Code's
|
|
6
|
+
* `Event<T>` convention.
|
|
7
|
+
*
|
|
8
|
+
* Services that emit events expose a member typed as `Event<T>` — the
|
|
9
|
+
* consuming extension calls it directly and pushes the returned disposable
|
|
10
|
+
* onto `ctx.subscriptions`:
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* ctx.subscriptions.push(
|
|
15
|
+
* ctx.editors.onDidSave(({ editorId, filePath }) => {
|
|
16
|
+
* console.log("saved", filePath);
|
|
17
|
+
* }),
|
|
18
|
+
* );
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* **Host-side:** the matching emitter (`EventEmitter<T>`) lives in the
|
|
22
|
+
* extension-host package (not the SDK). Only the subscribable `Event<T>` type
|
|
23
|
+
* is public — extensions never construct emitters.
|
|
24
|
+
*
|
|
25
|
+
* @category Core Types
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export type Event<T> = (listener: (e: T) => void) => Disposable;
|
package/src/file-service.ts
CHANGED
|
@@ -82,15 +82,46 @@ export interface FileService {
|
|
|
82
82
|
readBytes(path: string): Promise<ArrayBuffer>;
|
|
83
83
|
/** List a directory's immediate entries. */
|
|
84
84
|
readDir(path: string): Promise<FileMeta[]>;
|
|
85
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* Resolve true if a file or directory exists at `path`. Prefer
|
|
87
|
+
* {@link FileService.stat} when you also need the entry's metadata — `stat`
|
|
88
|
+
* returning non-`null` subsumes this check in one call.
|
|
89
|
+
*/
|
|
86
90
|
pathExists(path: string): Promise<boolean>;
|
|
91
|
+
/**
|
|
92
|
+
* Metadata for a single path, following symlinks, or `null` if nothing
|
|
93
|
+
* exists there. Resolving `null` (rather than rejecting) for an absent path
|
|
94
|
+
* is deliberate — it makes `stat` a one-call replacement for
|
|
95
|
+
* {@link FileService.pathExists} that also returns size / mtime / type.
|
|
96
|
+
* Rejects only on a real I/O error (e.g. a permission failure).
|
|
97
|
+
*/
|
|
98
|
+
stat(path: string): Promise<FileMeta | null>;
|
|
87
99
|
/** Write UTF-8 text to a file, creating or overwriting it. */
|
|
88
100
|
writeText(path: string, content: string): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Write raw bytes to a file, creating or overwriting it (and creating any
|
|
103
|
+
* missing parent directories). The byte-oriented counterpart to
|
|
104
|
+
* {@link FileService.writeText} / {@link FileService.readBytes} — use it for
|
|
105
|
+
* binary assets (images, archives) where `writeText` would corrupt the data.
|
|
106
|
+
*/
|
|
107
|
+
writeBytes(path: string, data: ArrayBuffer | Uint8Array): Promise<void>;
|
|
89
108
|
/** Create a directory (and any missing parents). */
|
|
90
109
|
createDir(path: string): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Copy a file or directory from `src` to `dest`, recursively for
|
|
112
|
+
* directories, creating any missing parent directories. Requires read access
|
|
113
|
+
* to `src` and write access to `dest` (both are workspace-scoped). Overwrites
|
|
114
|
+
* existing files at the destination.
|
|
115
|
+
*/
|
|
116
|
+
copy(src: string, dest: string): Promise<void>;
|
|
91
117
|
/** Rename / move a file or directory. */
|
|
92
118
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
93
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* **Permanently** delete a file or directory (directories are removed
|
|
121
|
+
* recursively). This does **not** move the entry to the OS trash/recycle
|
|
122
|
+
* bin — the delete is irreversible, so confirm destructive removals with the
|
|
123
|
+
* user first. Rejects if the path does not exist.
|
|
124
|
+
*/
|
|
94
125
|
delete(path: string): Promise<void>;
|
|
95
126
|
/** Reveal a path in the OS file manager (Finder / Explorer). */
|
|
96
127
|
reveal(path: string): Promise<void>;
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The public
|
|
3
|
-
* extension author imports from.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* The `@silo-code/sdk` public surface — the single curated entry point an
|
|
3
|
+
* extension author imports from. Re-exports **only** the blessed, permanently
|
|
4
|
+
* supported types and runtime helpers. Anything not re-exported here is
|
|
5
|
+
* host-internal and may change without notice.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* **What's here:** the types-first extension contract (see `types.ts`) plus a
|
|
8
|
+
* small set of blessed runtime helpers (`Tooltip`, `useFocusGroup`,
|
|
9
|
+
* `useServiceState`, `DND_MIME`, `PathDeniedError`, `NetworkError`). The SDK
|
|
10
|
+
* peer-depends on React 19; changes to the runtime helpers can be breaking
|
|
11
|
+
* even when the types are unchanged.
|
|
12
|
+
*
|
|
13
|
+
* This is also the entry point the API-reference generator (TypeDoc) reads, so
|
|
8
14
|
* the published reference is exactly this surface — no more, no less.
|
|
9
15
|
*
|
|
10
16
|
* @packageDocumentation
|
|
@@ -66,6 +72,9 @@ export type {
|
|
|
66
72
|
DiffContent,
|
|
67
73
|
DiffContentRequest,
|
|
68
74
|
DiffContentProvider,
|
|
75
|
+
ActiveEditorInfo,
|
|
76
|
+
EditorsState,
|
|
77
|
+
EditorSaveEvent,
|
|
69
78
|
} from "./editor-service";
|
|
70
79
|
export type {
|
|
71
80
|
LayoutService,
|
|
@@ -167,6 +176,7 @@ export type {
|
|
|
167
176
|
NetworkService,
|
|
168
177
|
NetworkRequestOptions,
|
|
169
178
|
NetworkResponse,
|
|
179
|
+
NetworkBytesResponse,
|
|
170
180
|
} from "./network-service";
|
|
171
181
|
|
|
172
182
|
// Static host-platform metadata: OS, CPU arch, and Silo version.
|
|
@@ -180,6 +190,15 @@ export type { LogService, LogLevel } from "./output-service";
|
|
|
180
190
|
// Context keys referenced by `when` predicates on menu items / keybindings.
|
|
181
191
|
export type { ContextKeys } from "./context-keys";
|
|
182
192
|
|
|
193
|
+
// Typed event primitive: a subscribable Event<T> that returns a Disposable.
|
|
194
|
+
// The matching host-side emitter is internal to the extension-host package.
|
|
195
|
+
export type { Event } from "./event";
|
|
196
|
+
|
|
197
|
+
// Pure path utilities — cross-platform replacement for `node:path` (banned in
|
|
198
|
+
// extensions). All outputs use forward-slash separators. Both "/" and "\" are
|
|
199
|
+
// accepted as input. Exported as a namespaced `path` object.
|
|
200
|
+
export { path } from "./path";
|
|
201
|
+
|
|
183
202
|
// Tooltip — the same styled hover popup the host uses in the status bar.
|
|
184
203
|
// Extensions use this instead of native `title` attributes to match host chrome.
|
|
185
204
|
export { Tooltip } from "./Tooltip";
|
package/src/network-service.ts
CHANGED
|
@@ -44,8 +44,12 @@ export interface NetworkRequestOptions {
|
|
|
44
44
|
method?: "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
45
45
|
/** Request headers to send. */
|
|
46
46
|
headers?: Record<string, string>;
|
|
47
|
-
/**
|
|
48
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Request body. A string is sent as-is; an `ArrayBuffer` / `Uint8Array` is
|
|
49
|
+
* sent as raw bytes (e.g. uploading a file). Only meaningful for methods that
|
|
50
|
+
* carry a body.
|
|
51
|
+
*/
|
|
52
|
+
body?: string | ArrayBuffer | Uint8Array;
|
|
49
53
|
/** Follow HTTP redirects. Defaults to `true`. */
|
|
50
54
|
followRedirects?: boolean;
|
|
51
55
|
/** Request timeout in milliseconds. Omit for the platform default (~30 s). */
|
|
@@ -72,6 +76,24 @@ export interface NetworkResponse {
|
|
|
72
76
|
finalUrl: string;
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Response from {@link NetworkService.fetchBytes} — identical to
|
|
81
|
+
* {@link NetworkResponse} but with the body delivered as raw bytes.
|
|
82
|
+
*
|
|
83
|
+
* @category Core Types
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
export interface NetworkBytesResponse {
|
|
87
|
+
/** HTTP status code. */
|
|
88
|
+
status: number;
|
|
89
|
+
/** Response headers, lowercased (multi-value joined with `", "`). */
|
|
90
|
+
headers: Record<string, string>;
|
|
91
|
+
/** Response body as raw bytes. */
|
|
92
|
+
body: ArrayBuffer;
|
|
93
|
+
/** Final URL after redirects. */
|
|
94
|
+
finalUrl: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
75
97
|
/**
|
|
76
98
|
* Server-side HTTP client exposed as {@link ExtensionContext.net}. Requests
|
|
77
99
|
* run in the Rust backend via `reqwest`, so they bypass the browser's CORS
|
|
@@ -104,6 +126,33 @@ export interface NetworkService {
|
|
|
104
126
|
*/
|
|
105
127
|
fetch(url: string, options?: NetworkRequestOptions): Promise<NetworkResponse>;
|
|
106
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Like {@link NetworkService.fetch}, but resolves the response body as raw
|
|
131
|
+
* bytes ({@link NetworkBytesResponse}) instead of decoding it as UTF-8 text —
|
|
132
|
+
* for downloading images, archives, or any binary payload.
|
|
133
|
+
*
|
|
134
|
+
* @param url - The URL to fetch.
|
|
135
|
+
* @param options - Method, headers, body, redirect and timeout controls. The
|
|
136
|
+
* body may itself be binary (`ArrayBuffer` / `Uint8Array`).
|
|
137
|
+
* @throws {@link NetworkError} if the request fails.
|
|
138
|
+
*
|
|
139
|
+
* @remarks
|
|
140
|
+
* The body rides Tauri's binary IPC channel (no base64), but the whole
|
|
141
|
+
* response is still buffered in memory on both sides — suitable for typical
|
|
142
|
+
* asset downloads (up to a few tens of MB), not for streaming multi-hundred-MB
|
|
143
|
+
* files.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* const { body } = await ctx.net.fetchBytes("https://example.com/logo.png");
|
|
148
|
+
* await ctx.files.writeBytes("logo.png", body);
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
fetchBytes(
|
|
152
|
+
url: string,
|
|
153
|
+
options?: NetworkRequestOptions,
|
|
154
|
+
): Promise<NetworkBytesResponse>;
|
|
155
|
+
|
|
107
156
|
/**
|
|
108
157
|
* Send a `HEAD` request and return only the response headers — no body is
|
|
109
158
|
* downloaded. More efficient than {@link NetworkService.fetch} when you only
|
package/src/path.test.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { path } from "./path";
|
|
3
|
+
|
|
4
|
+
describe("path.normalize", () => {
|
|
5
|
+
it("collapses dot segments", () =>
|
|
6
|
+
expect(path.normalize("a/./b")).toBe("a/b"));
|
|
7
|
+
it("resolves dot-dot segments", () =>
|
|
8
|
+
expect(path.normalize("a/b/../c")).toBe("a/c"));
|
|
9
|
+
it("converts backslash to forward-slash", () =>
|
|
10
|
+
expect(path.normalize("a\\b")).toBe("a/b"));
|
|
11
|
+
it("collapses duplicate slashes", () =>
|
|
12
|
+
expect(path.normalize("a//b")).toBe("a/b"));
|
|
13
|
+
it("preserves POSIX absolute", () =>
|
|
14
|
+
expect(path.normalize("/a/b")).toBe("/a/b"));
|
|
15
|
+
it("handles Windows drive", () =>
|
|
16
|
+
expect(path.normalize("C:\\a\\b")).toBe("C:/a/b"));
|
|
17
|
+
it("handles Windows drive with forward-slash", () =>
|
|
18
|
+
expect(path.normalize("C:/a/b")).toBe("C:/a/b"));
|
|
19
|
+
it("stays at root when dot-dot exceeds root", () =>
|
|
20
|
+
expect(path.normalize("/a/../..")).toBe("/"));
|
|
21
|
+
it("returns dot for empty-ish relative path", () =>
|
|
22
|
+
expect(path.normalize("a/..")).toBe("."));
|
|
23
|
+
it("UNC path is preserved", () =>
|
|
24
|
+
expect(path.normalize("\\\\server\\share\\foo")).toBe(
|
|
25
|
+
"//server/share/foo",
|
|
26
|
+
));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("path.join", () => {
|
|
30
|
+
it("joins two segments", () => expect(path.join("a", "b")).toBe("a/b"));
|
|
31
|
+
it("joins many segments", () =>
|
|
32
|
+
expect(path.join("a", "b", "c")).toBe("a/b/c"));
|
|
33
|
+
it("resolves dot-dot across join", () =>
|
|
34
|
+
expect(path.join("a/b", "../c")).toBe("a/c"));
|
|
35
|
+
it("preserves leading slash from first segment", () =>
|
|
36
|
+
expect(path.join("/a", "b")).toBe("/a/b"));
|
|
37
|
+
it("converts backslash", () => expect(path.join("a\\b", "c")).toBe("a/b/c"));
|
|
38
|
+
it("returns dot for no arguments", () => expect(path.join()).toBe("."));
|
|
39
|
+
it("ignores empty string segments", () =>
|
|
40
|
+
expect(path.join("a", "", "b")).toBe("a/b"));
|
|
41
|
+
it("handles Windows drive", () =>
|
|
42
|
+
expect(path.join("C:\\a", "b")).toBe("C:/a/b"));
|
|
43
|
+
it('join("/", rel) produces "/rel" not "//rel" (UNC)', () =>
|
|
44
|
+
expect(path.join("/", "linked.md")).toBe("/linked.md"));
|
|
45
|
+
it("join of POSIX root with nested rel stays absolute", () =>
|
|
46
|
+
expect(path.join("/", "a/b/../c")).toBe("/a/c"));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("path.dirname", () => {
|
|
50
|
+
it("returns parent dir", () => expect(path.dirname("/a/b/c")).toBe("/a/b"));
|
|
51
|
+
it("returns slash for top-level file", () =>
|
|
52
|
+
expect(path.dirname("/a")).toBe("/"));
|
|
53
|
+
it("returns dot for bare filename", () =>
|
|
54
|
+
expect(path.dirname("a")).toBe("."));
|
|
55
|
+
it("handles Windows drive", () =>
|
|
56
|
+
expect(path.dirname("C:\\a\\b")).toBe("C:/a"));
|
|
57
|
+
it("returns drive root for top-level", () =>
|
|
58
|
+
expect(path.dirname("C:\\a")).toBe("C:/"));
|
|
59
|
+
it("resolves trailing slash before computing", () =>
|
|
60
|
+
expect(path.dirname("/a/b/")).toBe("/a"));
|
|
61
|
+
it("handles UNC path", () =>
|
|
62
|
+
expect(path.dirname("//server/share/foo")).toBe("//server/share"));
|
|
63
|
+
it("returns dot for relative bare name", () =>
|
|
64
|
+
expect(path.dirname("file.ts")).toBe("."));
|
|
65
|
+
it("returns relative parent", () => expect(path.dirname("a/b")).toBe("a"));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("path.basename", () => {
|
|
69
|
+
it("returns last segment", () =>
|
|
70
|
+
expect(path.basename("/a/b/file.ts")).toBe("file.ts"));
|
|
71
|
+
it("strips provided extension", () =>
|
|
72
|
+
expect(path.basename("file.ts", ".ts")).toBe("file"));
|
|
73
|
+
it("does not strip if extension doesn't match", () =>
|
|
74
|
+
expect(path.basename("file.ts", ".js")).toBe("file.ts"));
|
|
75
|
+
it("handles trailing slash", () => expect(path.basename("/a/b/")).toBe("b"));
|
|
76
|
+
it("handles Windows path", () =>
|
|
77
|
+
expect(path.basename("C:\\a\\b.txt")).toBe("b.txt"));
|
|
78
|
+
it("returns empty string for root", () =>
|
|
79
|
+
expect(path.basename("/")).toBe(""));
|
|
80
|
+
it("returns bare name", () =>
|
|
81
|
+
expect(path.basename("file.ts")).toBe("file.ts"));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("path.extname", () => {
|
|
85
|
+
it("returns extension with dot", () =>
|
|
86
|
+
expect(path.extname("file.ts")).toBe(".ts"));
|
|
87
|
+
it("returns last extension only", () =>
|
|
88
|
+
expect(path.extname("file.test.ts")).toBe(".ts"));
|
|
89
|
+
it("returns empty for no extension", () =>
|
|
90
|
+
expect(path.extname("file")).toBe(""));
|
|
91
|
+
it("returns empty for dotfile (dot at position 0)", () =>
|
|
92
|
+
expect(path.extname(".gitignore")).toBe(""));
|
|
93
|
+
it("returns dot for trailing dot", () =>
|
|
94
|
+
expect(path.extname("file.")).toBe("."));
|
|
95
|
+
it("handles full path", () =>
|
|
96
|
+
expect(path.extname("/a/b/file.md")).toBe(".md"));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("path.isAbsolute", () => {
|
|
100
|
+
it("true for POSIX absolute", () =>
|
|
101
|
+
expect(path.isAbsolute("/foo")).toBe(true));
|
|
102
|
+
it("true for Windows drive with backslash", () =>
|
|
103
|
+
expect(path.isAbsolute("C:\\foo")).toBe(true));
|
|
104
|
+
it("true for Windows drive with forward-slash", () =>
|
|
105
|
+
expect(path.isAbsolute("C:/foo")).toBe(true));
|
|
106
|
+
it("true for UNC backslash", () =>
|
|
107
|
+
expect(path.isAbsolute("\\\\server\\share")).toBe(true));
|
|
108
|
+
it("true for UNC forward-slash", () =>
|
|
109
|
+
expect(path.isAbsolute("//server/share")).toBe(true));
|
|
110
|
+
it("false for relative path", () =>
|
|
111
|
+
expect(path.isAbsolute("foo/bar")).toBe(false));
|
|
112
|
+
it("false for drive-relative (no slash)", () =>
|
|
113
|
+
expect(path.isAbsolute("C:foo")).toBe(false));
|
|
114
|
+
it("false for bare filename", () =>
|
|
115
|
+
expect(path.isAbsolute("file.ts")).toBe(false));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("path.relative", () => {
|
|
119
|
+
it("computes sibling relative path", () =>
|
|
120
|
+
expect(path.relative("/a/b", "/a/c")).toBe("../c"));
|
|
121
|
+
it("returns dot for same path", () =>
|
|
122
|
+
expect(path.relative("/a/b", "/a/b")).toBe("."));
|
|
123
|
+
it("returns descendant path", () =>
|
|
124
|
+
expect(path.relative("/a", "/a/b/c")).toBe("b/c"));
|
|
125
|
+
it("returns parent path", () =>
|
|
126
|
+
expect(path.relative("/a/b/c", "/a")).toBe("../.."));
|
|
127
|
+
it("handles root to subpath", () =>
|
|
128
|
+
expect(path.relative("/", "/a/b")).toBe("a/b"));
|
|
129
|
+
it("returns normalized `to` for different Windows drives", () =>
|
|
130
|
+
expect(path.relative("C:/a", "D:/b")).toBe("D:/b"));
|
|
131
|
+
it("same drive computes relative", () =>
|
|
132
|
+
expect(path.relative("C:/a/b", "C:/a/c")).toBe("../c"));
|
|
133
|
+
it("handles backslash inputs", () =>
|
|
134
|
+
expect(path.relative("C:\\a\\b", "C:\\a\\c")).toBe("../c"));
|
|
135
|
+
});
|