Feat filegraph (#194)

* feat: update filegraph on touch command

* feat: update filegraph on mkdir

* feat: add different parsing opts for each command

* refactor: replace axios with fetch. put handleCommand to a new file

* feat: add config for mounting cli in dev stage

* refactor: clean up unused validators

* feat: add command handler for copy files. reuse resolvePath function

* feat: add rm handler, support specific files deletion. Glob not supported yet

* feat: handle discover cases that the folder is actually empty

* feat: handle discover empty folder better and support rmdir

* feat: handle mv command

* refactor: remove unused console logs
This commit is contained in:
Justin Lin authored and GitHub committed 2025-04-29 20:40:02 +08:00
1 parent a732c1d119
commit f0a09ac4d9
19 files changed
+326 -119

No files matched your search

+3
View File
@@ -4,6 +4,7 @@ export interface FileObject {
path: string
type: 'file' | 'directory'
discovered: boolean
empty?: boolean
}
export default class File implements FileObject {
@@ -11,12 +12,14 @@ export default class File implements FileObject {
name: string
type: 'file' | 'directory'
discovered: boolean
empty?: boolean
constructor(obj: FileObject) {
this.path = obj.path.trimEnd()
this.name = basename(obj.path)
this.type = obj.type
this.discovered = obj.discovered
this.empty = obj.empty && obj.type === 'directory'
}
isDirectory() {
+8 -11
View File
@@ -45,17 +45,14 @@ export default class FileGraph extends FileNode {
handleEvent(event: FileGraphUpdateEvent) {
// eslint-disable-next-line default-case
switch (true) {
case !!event.add:
this.add(event.add)
break
case !!event.remove:
this.remove(event.remove)
break
case !!event.discover:
this.discover(event.discover)
break
// Implement other cases if needed
if (event.add) {
this.add(event.add)
}
if (event.remove) {
this.remove(event.remove)
}
if (event.discover) {
this.discover(event.discover)
}
}
}
+31 -13
View File
@@ -1,9 +1,5 @@
import File, { FileObject } from './file.js'
import {
DuplicateItemError,
ParentNotExistsError,
FileNotExistsError,
} from './errors.js'
import { ParentNotExistsError, FileNotExistsError } from './errors.js'
import { dirname } from './utils.js'
export interface IFileNode {
@@ -29,15 +25,24 @@ export default class FileNode extends File implements IFileNode {
) {
const file = new FileNode(fileInput)
if (file.path === this.path) {
throw new DuplicateItemError(
`File ${file.path} already exists in the tree: ${this.toString()}`
)
if (!this.discovered) {
this.discovered = file.discovered
}
return
}
if (this.children.some((child) => child.path === file.path)) {
throw new DuplicateItemError(
`File ${file.path} already exists in the tree: ${this.toString()}`
)
let existing = false
for (const child of this.children) {
if (child.path === file.path) {
existing = true
if (!child.discovered) {
child.discovered = file.discovered
}
break
}
}
if (existing) {
return
}
const parentPath = dirname(file.path)
@@ -48,7 +53,7 @@ export default class FileNode extends File implements IFileNode {
// Recursively call addChild on the correct child node
const childNode = this.children.find((child) => child.contains(file))
if (childNode) {
childNode.addChild(file)
childNode.addChild(file, { makeParents })
} else if (makeParents) {
const parent = new FileNode({
path: parentPath,
@@ -95,6 +100,12 @@ export default class FileNode extends File implements IFileNode {
// Update the state of this node
this.discovered = this.discovered || fileNode.discovered
// the provided directory is empty, remove all children
if (fileNode.empty) {
this.children = []
return
}
fileNode.children.forEach((child) => {
const existingChild = this.children.find((c) => c.path === child.path)
if (existingChild) {
@@ -140,4 +151,11 @@ export default class FileNode extends File implements IFileNode {
.join('')
return `${name}\n${childrenString}`
}
include(filePath: string, type?: 'file' | 'directory'): boolean {
if (this.path === filePath) {
return type ? this.type === type : true
}
return this.children.some((child) => child.include(filePath, type))
}
}
+65 -1
View File
@@ -28,12 +28,44 @@ describe('FileGraph', () => {
type: 'directory',
discovered: true,
},
{
path: '/home/user/folder2/file2.txt',
type: 'file',
discovered: true,
},
]
fileGraph.add(filesToAdd)
expect(fileGraph.children.length).toBe(1)
expect(fileGraph.children[0].children[0].children.length).toBe(2)
expect(fileGraph.children[0].children[0].children.length).toBe(3)
})
test('ignore files that already exist', () => {
const fileGraph = new FileGraph(root)
const filesToAdd: FileObject[] = [
{
path: '/home/user/folder1',
type: 'directory',
discovered: true,
},
{
path: '/home/user/folder1/file1.txt',
type: 'file',
discovered: true,
},
]
fileGraph.add(filesToAdd)
expect(fileGraph.children.length).toBe(1)
expect(fileGraph.children[0].children[0].children.length).toBe(1)
fileGraph.add([filesToAdd[1]])
expect(fileGraph.children.length).toBe(1)
expect(fileGraph.children[0].children[0].children.length).toBe(1)
})
test('remove files from graph', () => {
@@ -175,4 +207,36 @@ describe('FileGraph', () => {
3
)
})
test('discover a empty directory which children just being deleted', () => {
const fileGraph = new FileGraph(root)
const discoverFiles = [
{
path: '/home/user/folder1',
type: 'directory',
discovered: true,
},
{
path: '/home/user/folder1/file1.txt',
type: 'file',
discovered: true,
},
]
fileGraph.add(discoverFiles as FileObject[])
expect(fileGraph.include('/home/user/folder1/file1.txt', 'file')).toBe(true)
fileGraph.discover([
{
path: '/home/user/folder1',
type: 'directory',
discovered: true,
empty: true,
},
])
expect(fileGraph.include('/home/user/folder1/file1.txt', 'file')).toBe(
false
)
})
})
+7 -6
View File
@@ -1,5 +1,5 @@
import { describe, test, expect } from '@jest/globals'
import { DuplicateItemError, ParentNotExistsError } from '../errors.js'
import { ParentNotExistsError } from '../errors.js'
import FileNode, { IFileNode } from '../fileNode.js'
import { FileObject } from '../file.js'
@@ -47,11 +47,11 @@ describe('FileNode', () => {
})
})
test('should throw error when adding duplicate item', () => {
test('should override discovered when adding duplicate item', () => {
const child: FileObject = {
path: '/home/rudeus/test',
type: 'directory',
discovered: true,
discovered: false,
}
// Adding the child for the first time
@@ -60,9 +60,10 @@ describe('FileNode', () => {
expect(rootNode.children.length).toBe(1)
// Trying to add the same child again should throw an error
expect(() => {
rootNode.addChild(child)
}).toThrow(DuplicateItemError)
child.discovered = true
rootNode.addChild(child)
expect(rootNode.children[0].discovered).toBe(true)
})
test('should throw error when parent does not exist', () => {