@young1lin/dsh-ui-gitworkbench 0.1.14 → 0.1.16

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/CHANGELOG_EN.md +23 -0
  3. package/README.md +29 -3
  4. package/README_EN.md +1 -1
  5. package/lib/client.js +1512 -470
  6. package/lib/dir-listing.js +34 -0
  7. package/lib/fs-remove.js +5 -36
  8. package/lib/index.js +346 -117
  9. package/lib/path-lock.js +54 -0
  10. package/lib/repo-root.js +60 -0
  11. package/lib/worktree.js +83 -0
  12. package/lib/write-checked.js +1 -1
  13. package/package.json +5 -5
  14. package/src/client/ChromeGlyph.tsx +5 -0
  15. package/src/client/CodeEditor.tsx +19 -1
  16. package/src/client/DiffViews.tsx +116 -121
  17. package/src/client/FileBrowser.tsx +196 -23
  18. package/src/client/GitWorkbenchPanel.module.css +1 -0
  19. package/src/client/GitWorkbenchPanel.tsx +100 -12
  20. package/src/client/SideRails.tsx +106 -0
  21. package/src/client/diff-cells.tsx +147 -0
  22. package/src/client/diff-nav.ts +4 -1
  23. package/src/client/dir-tree.ts +31 -1
  24. package/src/client/file-rows.ts +40 -0
  25. package/src/client/h-rail.ts +70 -0
  26. package/src/client/ignored-cache.ts +193 -0
  27. package/src/client/index.ts +22 -3
  28. package/src/client/locales.ts +12 -2
  29. package/src/client/row-heights.ts +225 -0
  30. package/src/client/styles/changes.css +33 -2
  31. package/src/client/styles/controls.css +5 -0
  32. package/src/client/styles/files.css +5 -0
  33. package/src/client/styles/rails.css +72 -0
  34. package/src/client/use-row-window.ts +7 -3
  35. package/src/client/use-variable-row-window.ts +210 -0
  36. package/src/dir-listing.ts +47 -0
  37. package/src/fs-remove.ts +5 -36
  38. package/src/index.ts +372 -118
  39. package/src/path-lock.ts +56 -0
  40. package/src/repo-root.ts +66 -0
  41. package/src/types/dsh-shim.d.ts +12 -2
  42. package/src/worktree.ts +97 -0
  43. package/src/write-checked.ts +1 -1
@@ -0,0 +1,47 @@
1
+ /**
2
+ * One lazily listed directory, shaped for the wire.
3
+ *
4
+ * The Files tab browses ignored directories by reading them from the
5
+ * filesystem ONE level at a time — git cannot do this scoped: a pathspec
6
+ * under `--directory` still collapses the whole ignored directory to a
7
+ * single line, and dropping `--directory` would enumerate every file inside
8
+ * `node_modules` at once. A `readdir` is one level by construction, costs
9
+ * milliseconds, and says what a browser wants to know: what is HERE.
10
+ *
11
+ * Only the shaping is pure (ordering, capping); the read itself stays in
12
+ * `index.ts`, which vitest cannot load. Same split as `fs-remove.ts`.
13
+ *
14
+ * @module @young1lin/dsh-ui-gitworkbench/dir-listing
15
+ */
16
+
17
+ /** One entry of a listed directory: a name plus whether expanding it again
18
+ * makes sense. `dir` decides the row's glyph and whether it is clickable
19
+ * as a folder. */
20
+ export interface DirChild {
21
+ readonly name: string
22
+ readonly dir: boolean
23
+ }
24
+
25
+ /** The most entries one expansion returns. A real directory level is a few
26
+ * hundred at most (`node_modules`'s own top level); a cap beyond that is a
27
+ * reported fuse against a pathological directory, not a working number. */
28
+ export const DIR_CHILD_CAP = 5_000
29
+
30
+ /**
31
+ * Order and cap raw readdir results: directories before files (the shape
32
+ * every file tree has, matching `treeRows`), each run by name, and a cut
33
+ * REPORTED rather than silent.
34
+ *
35
+ * @param raw - the directory's entries with their best-known dir-ness
36
+ * (symlinks already resolved by the caller).
37
+ * @param cap - most entries to return.
38
+ */
39
+ export function shapeDirChildren(
40
+ raw: readonly DirChild[],
41
+ cap: number = DIR_CHILD_CAP,
42
+ ): { entries: DirChild[]; truncated: boolean } {
43
+ const byName = (a: DirChild, b: DirChild): number => a.name.localeCompare(b.name)
44
+ const ordered = [...raw.filter(entry => entry.dir).sort(byName), ...raw.filter(entry => !entry.dir).sort(byName)]
45
+ const truncated = ordered.length > cap
46
+ return { entries: truncated ? ordered.slice(0, cap) : ordered, truncated }
47
+ }
package/src/fs-remove.ts CHANGED
@@ -6,8 +6,8 @@
6
6
  * `git clean` refuses paths it cannot index, which on Windows includes every
7
7
  * reserved device name (`nul`, `con`, `aux`, `com1`, and the same names with
8
8
  * any extension). So the removal goes through the filesystem, where git's own
9
- * refusal to leave the repository does not apply — hence the checks here
10
- * rather than a bare `rm`.
9
+ * refusal to leave the repository does not apply — hence the path lock in
10
+ * `path-lock.ts` rather than a bare `rm`.
11
11
  *
12
12
  * Lives outside `index.ts` so vitest can load it: the class there needs the
13
13
  * dsh runtime, and the property worth testing is "what does this delete, and
@@ -17,37 +17,8 @@
17
17
  */
18
18
 
19
19
  import { rm } from 'node:fs/promises'
20
- import { resolve, sep } from 'node:path'
21
20
 
22
- import { isSafeRelativePath } from './discard-ops.js'
23
-
24
- /**
25
- * Resolve a repo-relative path against the worktree root, refusing to leave it.
26
- *
27
- * The second lock rather than the only one: {@link isSafeRelativePath} already
28
- * rejected traversal spellings when the plan was made. This re-checks the
29
- * RESOLVED path, which is the form the filesystem acts on, so a path that
30
- * survives the first check by being spelled unusually still has to land inside
31
- * the root to be acted on.
32
- *
33
- * @param root - the worktree directory, absolute.
34
- * @param relative - repo-relative path from a plan step.
35
- * @returns the absolute path to act on.
36
- * @throws if the path is not a safe relative path, resolves outside the root,
37
- * or IS the root.
38
- */
39
- export function resolveInside(root: string, relative: string): string {
40
- if (!isSafeRelativePath(relative)) {
41
- throw new Error(`unsafe path to delete: ${JSON.stringify(relative)}`)
42
- }
43
- const base = resolve(root)
44
- const target = resolve(base, relative)
45
- if (target === base) throw new Error('refusing to delete the worktree root')
46
- if (!target.startsWith(base + sep)) {
47
- throw new Error(`refusing to delete outside the worktree: ${JSON.stringify(relative)}`)
48
- }
49
- return target
50
- }
21
+ import { resolveInside } from './path-lock.js'
51
22
 
52
23
  /**
53
24
  * Remove one entry from the worktree, having proven it is inside it.
@@ -63,10 +34,8 @@ export function resolveInside(root: string, relative: string): string {
63
34
  * `force` makes an absent entry a success: the reader asked for it to be gone,
64
35
  * and it is.
65
36
  *
66
- * A symlinked directory inside the worktree could still point outward; that is
67
- * a repository someone already has write access to, and resolving link targets
68
- * per segment on every delete would cost a stat per segment for a case git
69
- * itself does not defend against.
37
+ * A symlinked directory inside the worktree could still point outward the
38
+ * limit of a lexical resolve, stated where the lock is.
70
39
  *
71
40
  * @param root - the worktree directory, absolute.
72
41
  * @param relative - repo-relative path from a plan step.