@cmdoss/file-manager 0.1.0 → 0.2.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/.turbo/turbo-build.log +8 -8
- package/dist/index.d.ts +11 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +9 -7
- package/src/file-manager.ts +115 -59
- package/tsconfig.json +1 -1
- package/.turbo/turbo-test.log +0 -14
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
|
|
2
|
-
> @cmdoss/file-manager@0.1
|
|
2
|
+
> @cmdoss/file-manager@0.2.1 build /home/runner/work/ts-sdks/ts-sdks/packages/file-manager
|
|
3
3
|
> tsdown --platform browser --minify
|
|
4
4
|
|
|
5
|
-
[34mℹ[39m tsdown [2mv0.
|
|
5
|
+
[34mℹ[39m tsdown [2mv0.16.8[22m powered by rolldown [2mv1.0.0-beta.52[22m
|
|
6
6
|
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
7
7
|
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
8
8
|
[34mℹ[39m Build start
|
|
9
|
-
[34mℹ[39m [2mdist/[22m[1mindex.js[22m [
|
|
10
|
-
[34mℹ[39m [2mdist/[22mindex.js.map [
|
|
11
|
-
[34mℹ[39m [2mdist/[22mindex.d.ts.map [2m0.
|
|
12
|
-
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.ts[22m[39m [
|
|
13
|
-
[34mℹ[39m 4 files, total:
|
|
14
|
-
[32m✔[39m Build complete in [
|
|
9
|
+
[34mℹ[39m [2mdist/[22m[1mindex.js[22m [2m3.01 kB[22m [2m│ gzip: 1.24 kB[22m
|
|
10
|
+
[34mℹ[39m [2mdist/[22mindex.js.map [2m8.42 kB[22m [2m│ gzip: 2.79 kB[22m
|
|
11
|
+
[34mℹ[39m [2mdist/[22mindex.d.ts.map [2m0.87 kB[22m [2m│ gzip: 0.37 kB[22m
|
|
12
|
+
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.ts[22m[39m [2m1.06 kB[22m [2m│ gzip: 0.50 kB[22m
|
|
13
|
+
[34mℹ[39m 4 files, total: 13.36 kB
|
|
14
|
+
[32m✔[39m Build complete in [32m325ms[39m
|
package/dist/index.d.ts
CHANGED
|
@@ -2,18 +2,21 @@ import { FileChangedCallback, IFileManager } from "@cmdoss/site-builder";
|
|
|
2
2
|
|
|
3
3
|
//#region src/file-manager.d.ts
|
|
4
4
|
declare class ZenFsFileManager implements IFileManager {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
/** The directory of the workspace. Any files within this directory are considered part of the workspace. */
|
|
6
|
+
readonly workspaceDir: string;
|
|
7
|
+
/** The directory where the workspace is mounted in the virtual filesystem. */
|
|
8
|
+
readonly mountDir?: string | undefined;
|
|
9
|
+
protected changeListeners: Set<FileChangedCallback>;
|
|
10
|
+
constructor(workspaceDir?: string, mountDir?: string | undefined);
|
|
11
|
+
initialize(): Promise<void>;
|
|
12
|
+
writeZipArchive(zipData: ArrayBuffer): Promise<void>;
|
|
12
13
|
readFile(filePath: string): Promise<Uint8Array>;
|
|
13
14
|
listFiles(): Promise<string[]>;
|
|
15
|
+
getSize(): Promise<number>;
|
|
16
|
+
writeFile(filePath: string, content: string | Uint8Array): Promise<void>;
|
|
17
|
+
deleteFile(filePath: string): Promise<void>;
|
|
14
18
|
onFileChange(callback: FileChangedCallback): () => void;
|
|
15
19
|
private notifyChange;
|
|
16
|
-
clear(): Promise<void>;
|
|
17
20
|
unmount(): void;
|
|
18
21
|
}
|
|
19
22
|
//#endregion
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":["
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":["mountDir?: string"],"sources":["../src/file-manager.ts"],"sourcesContent":[],"mappings":";;;cASa,gBAAA,YAA4B;;EAAzC,SAAa,YAAA,EAAA,MAAA;EACoB;EAAJ,SAAA,QAAA,CAAA,EAAA,MAAA,GAAA,SAAA;EAyBP,UAAA,eAAA,EAzBO,GAyBP,CAzBW,mBAyBX,CAAA;EAcW,WAAA,CAAA,YAAA,CAAA,EAAA,MAAA,EAAA,QAAA,CAAA,EAAA,MAAA,GAAA,SAAA;EAAc,UAAA,CAAA,CAAA,EAdzB,OAcyB,CAAA,IAAA,CAAA;EAmBH,eAAA,CAAA,OAAA,EAnBX,WAmBW,CAAA,EAnBG,OAmBH,CAAA,IAAA,CAAA;EAAR,QAAA,CAAA,QAAA,EAAA,MAAA,CAAA,EAAA,OAAA,CAAQ,UAAR,CAAA;EASf,SAAA,CAAA,CAAA,EAAA,OAAA,CAAA,MAAA,EAAA,CAAA;EAcF,OAAA,CAAA,CAAA,EAAA,OAAA,CAAA,MAAA,CAAA;EAwBG,SAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GAAA,UAAA,CAAA,EACjB,OADiB,CAAA,IAAA,CAAA;EACjB,UAAA,CAAA,QAAA,EAAA,MAAA,CAAA,EAaiC,OAbjC,CAAA,IAAA,CAAA;EAaiC,YAAA,CAAA,QAAA,EASb,mBATa,CAAA,EAAA,GAAA,GAAA,IAAA;EASb,QAAA,YAAA;EAjIgB,OAAA,CAAA,CAAA,EAAA,IAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{Zip as e}from"@zenfs/archives";import{configure as t,fs as n}from"@zenfs/core";import*as r from"@zenfs/core/path";import{IndexedDB as i}from"@zenfs/dom";import a from"debug";const o=a(`file-manager`);var s=class{changeListeners=new Set;constructor(e=`/workspace`,t){if(this.workspaceDir=e,this.mountDir=t,this.mountDir||=this.workspaceDir,this.mountDir!==this.workspaceDir){let e=r.normalize(this.workspaceDir),t=r.normalize(this.mountDir);if(!e.startsWith(t+`/`)&&e!==t)throw Error(`workspaceDir (${this.workspaceDir}) must be a subdirectory of mountDir (${this.mountDir})`)}}async initialize(){o(`🔧 Configuring filesystem...`),o(`📁 Mounting workspace at ${this.mountDir}`),await t({mounts:{[this.mountDir??this.workspaceDir]:{backend:i,storeName:this.mountDir??this.workspaceDir}}}),o(`✅ Filesystem configured`)}async writeZipArchive(r){let i=`/tmp/zip-write-${crypto.randomUUID()}`;o(`📦 Mounting ZIP archive to temporary directory...`),await t({mounts:{[i]:{backend:e,data:r,lazy:!1}}}),o(`📂 Copying files from ZIP archive to workspace...`),await n.promises.cp(i,this.workspaceDir,{recursive:!0}),o(`📦 Unmounting temporary ZIP directory...`),n.umount(i),o(`✅ Files copied to workspace from ZIP archive`)}async readFile(e){o(`📂 Reading file ${e}...`),e=c(e);let t=r.join(this.workspaceDir,e),i=await n.promises.readFile(t);return o(`✅ File ${e} read (${i.byteLength} bytes)`),i}async listFiles(){o(`📄 Listing files in workspace`);let e=(await n.promises.readdir(this.workspaceDir,{withFileTypes:!0,recursive:!0})).filter(e=>e.isFile()).map(e=>r.join(e.parentPath,e.name)).map(c);return o(`✅ Files currently in workspace:`,e),e}async getSize(){o(`📏 Calculating total size of files in workspace`);let e=0,t=await n.promises.readdir(this.workspaceDir,{withFileTypes:!0,recursive:!0});for(let i of t)if(i.isFile()){let t=r.join(this.workspaceDir,i.parentPath,i.name),a=await n.promises.stat(t);e+=a.size}return o(`✅ Total size of files in workspace:`,e,`bytes`),e}async writeFile(e,t){o(`✍️ Writing file to`,e),e=c(e);let i=r.join(this.workspaceDir,e),a=r.dirname(i);await n.promises.mkdir(a,{recursive:!0});let s=typeof t==`string`?new TextEncoder().encode(t):t;await n.promises.writeFile(i,s),o(`✅ File written to`,e,`(`,s.byteLength,`bytes )`),this.notifyChange({type:`updated`,path:e})}async deleteFile(e){o(`🗑️ Deleting file at`,e),e=c(e);let t=r.join(this.workspaceDir,e);await n.promises.rm(t),o(`✅ File deleted at`,e),this.notifyChange({type:`removed`,path:e})}onFileChange(e){return this.changeListeners.add(e),()=>{this.changeListeners.delete(e)}}notifyChange(e){for(let t of this.changeListeners)t(e)}unmount(){o(`🚪 Unmounting workspace directory (`,this.workspaceDir,`)`),this.changeListeners.clear(),this.mountDir===this.workspaceDir?n.umount(this.mountDir):o(`⚠️ Skipping unmount: mountDir and workspaceDir are different`)}};function c(e){return e.startsWith(`/`)?e:`/${e}`}export{s as ZenFsFileManager};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["
|
|
1
|
+
{"version":3,"file":"index.js","names":["mountDir?: string","path"],"sources":["../src/file-manager.ts"],"sourcesContent":["import type { FileChangedCallback, IFileManager } from '@cmdoss/site-builder'\nimport { Zip } from '@zenfs/archives'\nimport { configure, fs } from '@zenfs/core'\nimport * as path from '@zenfs/core/path'\nimport { IndexedDB } from '@zenfs/dom'\nimport debug from 'debug'\n\nconst log = debug('file-manager')\n\nexport class ZenFsFileManager implements IFileManager {\n protected changeListeners: Set<FileChangedCallback> = new Set()\n constructor(\n /** The directory of the workspace. Any files within this directory are considered part of the workspace. */\n public readonly workspaceDir = '/workspace',\n /** The directory where the workspace is mounted in the virtual filesystem. */\n public readonly mountDir?: string\n ) {\n if (!this.mountDir) {\n this.mountDir = this.workspaceDir\n }\n if (this.mountDir !== this.workspaceDir) {\n // Ensure workspaceDir is a subdirectory of mountDir\n const normalizedWorkspace = path.normalize(this.workspaceDir)\n const normalizedMount = path.normalize(this.mountDir)\n if (\n !normalizedWorkspace.startsWith(normalizedMount + '/') &&\n normalizedWorkspace !== normalizedMount\n ) {\n throw new Error(\n `workspaceDir (${this.workspaceDir}) must be a subdirectory of mountDir (${this.mountDir})`\n )\n }\n }\n }\n\n async initialize(): Promise<void> {\n log('🔧 Configuring filesystem...')\n log(`📁 Mounting workspace at ${this.mountDir}`)\n await configure({\n mounts: {\n [this.mountDir ?? this.workspaceDir]: {\n backend: IndexedDB,\n storeName: this.mountDir ?? this.workspaceDir\n }\n }\n })\n log('✅ Filesystem configured')\n }\n\n async writeZipArchive(zipData: ArrayBuffer): Promise<void> {\n const tmpDir = `/tmp/zip-write-${crypto.randomUUID()}`\n log('📦 Mounting ZIP archive to temporary directory...')\n await configure({\n mounts: {\n [tmpDir]: {\n backend: Zip,\n data: zipData,\n lazy: false // Extract all files immediately\n }\n }\n })\n log('📂 Copying files from ZIP archive to workspace...')\n await fs.promises.cp(tmpDir, this.workspaceDir, { recursive: true })\n log('📦 Unmounting temporary ZIP directory...')\n fs.umount(tmpDir)\n log('✅ Files copied to workspace from ZIP archive')\n }\n\n async readFile(filePath: string): Promise<Uint8Array> {\n log(`📂 Reading file ${filePath}...`)\n filePath = ensureLeadingSlash(filePath)\n const workspaceFilePath = path.join(this.workspaceDir, filePath)\n const content = await fs.promises.readFile(workspaceFilePath)\n log(`✅ File ${filePath} read (${content.byteLength} bytes)`)\n return content\n }\n\n async listFiles(): Promise<string[]> {\n log('📄 Listing files in workspace')\n const files = await fs.promises.readdir(this.workspaceDir, {\n withFileTypes: true,\n recursive: true\n })\n const result = files\n .filter(f => f.isFile())\n .map(f => path.join(f.parentPath, f.name))\n .map(ensureLeadingSlash)\n log('✅ Files currently in workspace:', result)\n return result\n }\n\n async getSize(): Promise<number> {\n log('📏 Calculating total size of files in workspace')\n let totalSize = 0\n const files = await fs.promises.readdir(this.workspaceDir, {\n withFileTypes: true,\n recursive: true\n })\n for (const file of files) {\n if (file.isFile()) {\n const filePath = path.join(\n this.workspaceDir,\n file.parentPath,\n file.name\n )\n const stats = await fs.promises.stat(filePath)\n totalSize += stats.size\n }\n }\n log('✅ Total size of files in workspace:', totalSize, 'bytes')\n return totalSize\n }\n\n async writeFile(\n filePath: string,\n content: string | Uint8Array\n ): Promise<void> {\n log('✍️ Writing file to', filePath)\n filePath = ensureLeadingSlash(filePath)\n const workspaceFilePath = path.join(this.workspaceDir, filePath)\n const dir = path.dirname(workspaceFilePath)\n await fs.promises.mkdir(dir, { recursive: true })\n const contentBytes =\n typeof content === 'string' ? new TextEncoder().encode(content) : content\n await fs.promises.writeFile(workspaceFilePath, contentBytes)\n log('✅ File written to', filePath, '(', contentBytes.byteLength, 'bytes )')\n this.notifyChange({ type: 'updated', path: filePath })\n }\n\n async deleteFile(filePath: string): Promise<void> {\n log('🗑️ Deleting file at', filePath)\n filePath = ensureLeadingSlash(filePath)\n const workspaceFilePath = path.join(this.workspaceDir, filePath)\n await fs.promises.rm(workspaceFilePath)\n log('✅ File deleted at', filePath)\n this.notifyChange({ type: 'removed', path: filePath })\n }\n\n onFileChange(callback: FileChangedCallback): () => void {\n this.changeListeners.add(callback)\n return () => {\n this.changeListeners.delete(callback)\n }\n }\n\n private notifyChange(arg: Parameters<FileChangedCallback>[0]): void {\n for (const listener of this.changeListeners) {\n listener(arg)\n }\n }\n\n unmount(): void {\n log('🚪 Unmounting workspace directory (', this.workspaceDir, ')')\n this.changeListeners.clear()\n if (this.mountDir === this.workspaceDir) {\n // Unmount only if mountDir and workspaceDir are the same\n fs.umount(this.mountDir)\n } else {\n log('⚠️ Skipping unmount: mountDir and workspaceDir are different')\n }\n }\n}\n\nfunction ensureLeadingSlash(path: string): string {\n return path.startsWith('/') ? path : `/${path}`\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmdoss/file-manager",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./dist/index.js",
|
|
@@ -15,19 +15,21 @@
|
|
|
15
15
|
"@zenfs/dom": "^1.2.4"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
+
"@types/debug": "^4.1.12",
|
|
18
19
|
"@zenfs/archives": "^1.3.1",
|
|
19
|
-
"@zenfs/core": "^2.4.
|
|
20
|
+
"@zenfs/core": "^2.4.4",
|
|
20
21
|
"@zenfs/dom": "^1.2.5",
|
|
21
|
-
"
|
|
22
|
+
"debug": "^4.4.3",
|
|
23
|
+
"@cmdoss/site-builder": "0.2.1"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
|
-
"@types/node": "^22.
|
|
25
|
-
"tsdown": "^0.
|
|
26
|
+
"@types/node": "^22.19.1",
|
|
27
|
+
"tsdown": "^0.16.8"
|
|
26
28
|
},
|
|
27
29
|
"scripts": {
|
|
28
|
-
"dev": "tsdown --platform browser --watch --ignore-watch .turbo",
|
|
30
|
+
"dev": "tsdown --platform browser --watch --ignore-watch .turbo --no-clean",
|
|
29
31
|
"test": "node --test",
|
|
30
|
-
"check
|
|
32
|
+
"check:types": "tsc --noEmit",
|
|
31
33
|
"build": "tsdown --platform browser --minify"
|
|
32
34
|
}
|
|
33
35
|
}
|
package/src/file-manager.ts
CHANGED
|
@@ -1,42 +1,121 @@
|
|
|
1
1
|
import type { FileChangedCallback, IFileManager } from '@cmdoss/site-builder'
|
|
2
|
-
import {
|
|
2
|
+
import { Zip } from '@zenfs/archives'
|
|
3
3
|
import { configure, fs } from '@zenfs/core'
|
|
4
4
|
import * as path from '@zenfs/core/path'
|
|
5
5
|
import { IndexedDB } from '@zenfs/dom'
|
|
6
|
+
import debug from 'debug'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
private changeListeners = new Set<FileChangedCallback>()
|
|
8
|
+
const log = debug('file-manager')
|
|
9
9
|
|
|
10
|
+
export class ZenFsFileManager implements IFileManager {
|
|
11
|
+
protected changeListeners: Set<FileChangedCallback> = new Set()
|
|
10
12
|
constructor(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
/** The directory of the workspace. Any files within this directory are considered part of the workspace. */
|
|
14
|
+
public readonly workspaceDir = '/workspace',
|
|
15
|
+
/** The directory where the workspace is mounted in the virtual filesystem. */
|
|
16
|
+
public readonly mountDir?: string
|
|
17
|
+
) {
|
|
18
|
+
if (!this.mountDir) {
|
|
19
|
+
this.mountDir = this.workspaceDir
|
|
20
|
+
}
|
|
21
|
+
if (this.mountDir !== this.workspaceDir) {
|
|
22
|
+
// Ensure workspaceDir is a subdirectory of mountDir
|
|
23
|
+
const normalizedWorkspace = path.normalize(this.workspaceDir)
|
|
24
|
+
const normalizedMount = path.normalize(this.mountDir)
|
|
25
|
+
if (
|
|
26
|
+
!normalizedWorkspace.startsWith(normalizedMount + '/') &&
|
|
27
|
+
normalizedWorkspace !== normalizedMount
|
|
28
|
+
) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`workspaceDir (${this.workspaceDir}) must be a subdirectory of mountDir (${this.mountDir})`
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async initialize(): Promise<void> {
|
|
37
|
+
log('🔧 Configuring filesystem...')
|
|
38
|
+
log(`📁 Mounting workspace at ${this.mountDir}`)
|
|
39
|
+
await configure({
|
|
40
|
+
mounts: {
|
|
41
|
+
[this.mountDir ?? this.workspaceDir]: {
|
|
42
|
+
backend: IndexedDB,
|
|
43
|
+
storeName: this.mountDir ?? this.workspaceDir
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
log('✅ Filesystem configured')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async writeZipArchive(zipData: ArrayBuffer): Promise<void> {
|
|
51
|
+
const tmpDir = `/tmp/zip-write-${crypto.randomUUID()}`
|
|
52
|
+
log('📦 Mounting ZIP archive to temporary directory...')
|
|
53
|
+
await configure({
|
|
54
|
+
mounts: {
|
|
55
|
+
[tmpDir]: {
|
|
56
|
+
backend: Zip,
|
|
57
|
+
data: zipData,
|
|
58
|
+
lazy: false // Extract all files immediately
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
log('📂 Copying files from ZIP archive to workspace...')
|
|
63
|
+
await fs.promises.cp(tmpDir, this.workspaceDir, { recursive: true })
|
|
64
|
+
log('📦 Unmounting temporary ZIP directory...')
|
|
65
|
+
fs.umount(tmpDir)
|
|
66
|
+
log('✅ Files copied to workspace from ZIP archive')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async readFile(filePath: string): Promise<Uint8Array> {
|
|
70
|
+
log(`📂 Reading file ${filePath}...`)
|
|
71
|
+
filePath = ensureLeadingSlash(filePath)
|
|
72
|
+
const workspaceFilePath = path.join(this.workspaceDir, filePath)
|
|
73
|
+
const content = await fs.promises.readFile(workspaceFilePath)
|
|
74
|
+
log(`✅ File ${filePath} read (${content.byteLength} bytes)`)
|
|
75
|
+
return content
|
|
76
|
+
}
|
|
14
77
|
|
|
15
|
-
async
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
78
|
+
async listFiles(): Promise<string[]> {
|
|
79
|
+
log('📄 Listing files in workspace')
|
|
80
|
+
const files = await fs.promises.readdir(this.workspaceDir, {
|
|
81
|
+
withFileTypes: true,
|
|
82
|
+
recursive: true
|
|
83
|
+
})
|
|
84
|
+
const result = files
|
|
85
|
+
.filter(f => f.isFile())
|
|
86
|
+
.map(f => path.join(f.parentPath, f.name))
|
|
87
|
+
.map(ensureLeadingSlash)
|
|
88
|
+
log('✅ Files currently in workspace:', result)
|
|
89
|
+
return result
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getSize(): Promise<number> {
|
|
93
|
+
log('📏 Calculating total size of files in workspace')
|
|
94
|
+
let totalSize = 0
|
|
95
|
+
const files = await fs.promises.readdir(this.workspaceDir, {
|
|
96
|
+
withFileTypes: true,
|
|
97
|
+
recursive: true
|
|
98
|
+
})
|
|
99
|
+
for (const file of files) {
|
|
100
|
+
if (file.isFile()) {
|
|
101
|
+
const filePath = path.join(
|
|
102
|
+
this.workspaceDir,
|
|
103
|
+
file.parentPath,
|
|
104
|
+
file.name
|
|
105
|
+
)
|
|
106
|
+
const stats = await fs.promises.stat(filePath)
|
|
107
|
+
totalSize += stats.size
|
|
108
|
+
}
|
|
32
109
|
}
|
|
33
|
-
|
|
110
|
+
log('✅ Total size of files in workspace:', totalSize, 'bytes')
|
|
111
|
+
return totalSize
|
|
34
112
|
}
|
|
35
113
|
|
|
36
114
|
async writeFile(
|
|
37
115
|
filePath: string,
|
|
38
116
|
content: string | Uint8Array
|
|
39
117
|
): Promise<void> {
|
|
118
|
+
log('✍️ Writing file to', filePath)
|
|
40
119
|
filePath = ensureLeadingSlash(filePath)
|
|
41
120
|
const workspaceFilePath = path.join(this.workspaceDir, filePath)
|
|
42
121
|
const dir = path.dirname(workspaceFilePath)
|
|
@@ -44,38 +123,19 @@ export class ZenFsFileManager implements IFileManager {
|
|
|
44
123
|
const contentBytes =
|
|
45
124
|
typeof content === 'string' ? new TextEncoder().encode(content) : content
|
|
46
125
|
await fs.promises.writeFile(workspaceFilePath, contentBytes)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
path: filePath
|
|
50
|
-
})
|
|
126
|
+
log('✅ File written to', filePath, '(', contentBytes.byteLength, 'bytes )')
|
|
127
|
+
this.notifyChange({ type: 'updated', path: filePath })
|
|
51
128
|
}
|
|
52
129
|
|
|
53
|
-
async
|
|
130
|
+
async deleteFile(filePath: string): Promise<void> {
|
|
131
|
+
log('🗑️ Deleting file at', filePath)
|
|
54
132
|
filePath = ensureLeadingSlash(filePath)
|
|
55
133
|
const workspaceFilePath = path.join(this.workspaceDir, filePath)
|
|
56
134
|
await fs.promises.rm(workspaceFilePath)
|
|
135
|
+
log('✅ File deleted at', filePath)
|
|
57
136
|
this.notifyChange({ type: 'removed', path: filePath })
|
|
58
137
|
}
|
|
59
138
|
|
|
60
|
-
async readFile(filePath: string): Promise<Uint8Array> {
|
|
61
|
-
filePath = ensureLeadingSlash(filePath)
|
|
62
|
-
const workspaceFilePath = path.join(this.workspaceDir, filePath)
|
|
63
|
-
console.log('Reading file from path:', workspaceFilePath)
|
|
64
|
-
return await fs.promises.readFile(workspaceFilePath)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async listFiles(): Promise<string[]> {
|
|
68
|
-
const files = await fs.promises.readdir(this.workspaceDir, {
|
|
69
|
-
withFileTypes: true,
|
|
70
|
-
recursive: true
|
|
71
|
-
})
|
|
72
|
-
console.log('Files in workspace:', files)
|
|
73
|
-
return files
|
|
74
|
-
.filter(f => f.isFile())
|
|
75
|
-
.map(f => path.resolve(f.parentPath, f.name))
|
|
76
|
-
.map(ensureLeadingSlash)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
139
|
onFileChange(callback: FileChangedCallback): () => void {
|
|
80
140
|
this.changeListeners.add(callback)
|
|
81
141
|
return () => {
|
|
@@ -89,19 +149,15 @@ export class ZenFsFileManager implements IFileManager {
|
|
|
89
149
|
}
|
|
90
150
|
}
|
|
91
151
|
|
|
92
|
-
async clear(): Promise<void> {
|
|
93
|
-
const files = await fs.promises.readdir(this.workspaceDir, {
|
|
94
|
-
withFileTypes: true
|
|
95
|
-
})
|
|
96
|
-
for (const file of files) {
|
|
97
|
-
const filePath = path.join(file.parentPath, file.name)
|
|
98
|
-
await fs.promises.rm(filePath, { recursive: true, force: true })
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
152
|
unmount(): void {
|
|
153
|
+
log('🚪 Unmounting workspace directory (', this.workspaceDir, ')')
|
|
103
154
|
this.changeListeners.clear()
|
|
104
|
-
|
|
155
|
+
if (this.mountDir === this.workspaceDir) {
|
|
156
|
+
// Unmount only if mountDir and workspaceDir are the same
|
|
157
|
+
fs.umount(this.mountDir)
|
|
158
|
+
} else {
|
|
159
|
+
log('⚠️ Skipping unmount: mountDir and workspaceDir are different')
|
|
160
|
+
}
|
|
105
161
|
}
|
|
106
162
|
}
|
|
107
163
|
|
package/tsconfig.json
CHANGED
package/.turbo/turbo-test.log
DELETED