@cmdoss/file-manager 0.1.0 → 0.2.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.
@@ -1,14 +1,14 @@
1
1
 
2
- > @cmdoss/file-manager@0.1.0 build /home/runner/work/ts-sdks/ts-sdks/packages/file-manager
2
+ > @cmdoss/file-manager@0.2.0 build /home/runner/work/ts-sdks/ts-sdks/packages/file-manager
3
3
  > tsdown --platform browser --minify
4
4
 
5
- ℹ tsdown v0.15.4 powered by rolldown v1.0.0-beta.43
5
+ ℹ tsdown v0.16.8 powered by rolldown v1.0.0-beta.52
6
6
  ℹ entry: src/index.ts
7
7
  ℹ tsconfig: tsconfig.json
8
8
  ℹ Build start
9
- ℹ dist/index.js 1.87 kB │ gzip: 0.82 kB
10
- ℹ dist/index.js.map 5.55 kB │ gzip: 1.92 kB
11
- ℹ dist/index.d.ts.map 0.73 kB │ gzip: 0.35 kB
12
- ℹ dist/index.d.ts 0.79 kB │ gzip: 0.41 kB
13
- ℹ 4 files, total: 8.94 kB
14
- ✔ Build complete in 236ms
9
+ ℹ dist/index.js 2.95 kB │ gzip: 1.21 kB
10
+ ℹ dist/index.js.map 8.31 kB │ gzip: 2.72 kB
11
+ ℹ dist/index.d.ts.map 0.87 kB │ gzip: 0.37 kB
12
+ ℹ dist/index.d.ts 1.06 kB │ gzip: 0.50 kB
13
+ ℹ 4 files, total: 13.20 kB
14
+ ✔ Build complete in 365ms
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
- private workspaceDir;
6
- private backend;
7
- private changeListeners;
8
- constructor(workspaceDir?: string, backend?: "indexeddb" | "zip" | "iso");
9
- mount(data?: ArrayBuffer, force?: boolean): Promise<void>;
10
- writeFile(filePath: string, content: string | Uint8Array): Promise<void>;
11
- removeFile(filePath: string): Promise<void>;
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":["backend: 'indexeddb' | 'zip' | 'iso'"],"sources":["../src/file-manager.ts"],"sourcesContent":[],"mappings":";;;cAMa,gBAAA,YAA4B;;EAAzC,QAAa,OAAA;EAAA,QAAA,eAAA;aAQQ,CAAA,YAAA,CAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,WAAA,GAAA,KAAA,GAAA,KAAA;QAA6B,IAAA,CAAA,EAA7B,WAA6B,EAAA,KAAA,CAAA,EAAA,OAAA,CAAA,EAAA,OAAA,CAAA,IAAA,CAAA;WAuB5B,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GAAA,UAAA,CAAA,EACjB,OADiB,CAAA,IAAA,CAAA;YACjB,CAAA,QAAA,EAAA,MAAA,CAAA,EAciC,OAdjC,CAAA,IAAA,CAAA;UAciC,CAAA,QAAA,EAAA,MAAA,CAAA,EAOF,OAPE,CAOM,UAPN,CAAA;WAOM,CAAA,CAAA,EAOvB,OAPuB,CAAA,MAAA,EAAA,CAAA;cAAR,CAAA,QAAA,EAmBX,mBAnBW,CAAA,EAAA,GAAA,GAAA,IAAA;UAOf,YAAA;SAYI,EAaR,OAbQ,CAAA,IAAA,CAAA;SAaR,CAAA,CAAA,EAAA,IAAA"}
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;EAiBH,eAAA,CAAA,OAAA,EAjBX,WAiBW,CAAA,EAjBG,OAiBH,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;EA/HgB,OAAA,CAAA,CAAA,EAAA,IAAA"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{Iso as e,Zip as t}from"@zenfs/archives";import{configure as n,fs as r}from"@zenfs/core";import*as i from"@zenfs/core/path";import{IndexedDB as a}from"@zenfs/dom";var o=class{changeListeners=new Set;constructor(e=`/workspace`,t=`indexeddb`){this.workspaceDir=e,this.backend=t}async mount(i,o=!1){let s=this.backend===`indexeddb`?a:this.backend===`zip`?t:this.backend===`iso`?e:null;if(!s)throw Error(`Invalid backend specified`);if(await r.promises.access(this.workspaceDir).then(()=>!0).catch(()=>!1)){if(!o)throw Error(`Workspace directory is already mounted`);this.unmount()}await n({mounts:{[this.workspaceDir]:{backend:s,data:i}}})}async writeFile(e,t){e=s(e);let n=i.join(this.workspaceDir,e),a=i.dirname(n);await r.promises.mkdir(a,{recursive:!0});let o=typeof t==`string`?new TextEncoder().encode(t):t;await r.promises.writeFile(n,o),this.notifyChange({type:`updated`,path:e})}async removeFile(e){e=s(e);let t=i.join(this.workspaceDir,e);await r.promises.rm(t),this.notifyChange({type:`removed`,path:e})}async readFile(e){e=s(e);let t=i.join(this.workspaceDir,e);return console.log(`Reading file from path:`,t),await r.promises.readFile(t)}async listFiles(){let e=await r.promises.readdir(this.workspaceDir,{withFileTypes:!0,recursive:!0});return console.log(`Files in workspace:`,e),e.filter(e=>e.isFile()).map(e=>i.resolve(e.parentPath,e.name)).map(s)}onFileChange(e){return this.changeListeners.add(e),()=>{this.changeListeners.delete(e)}}notifyChange(e){for(let t of this.changeListeners)t(e)}async clear(){let e=await r.promises.readdir(this.workspaceDir,{withFileTypes:!0});for(let t of e){let e=i.join(t.parentPath,t.name);await r.promises.rm(e,{recursive:!0,force:!0})}}unmount(){this.changeListeners.clear(),r.umount(this.workspaceDir)}};function s(e){return e.startsWith(`/`)?e:`/${e}`}export{o as ZenFsFileManager};
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(`✅ Files copied to workspace from ZIP archive`)}async readFile(e){o(`📂 Reading file from`,e),e=c(e);let t=r.join(this.workspaceDir,e),i=await n.promises.readFile(t);return o(`✅ File read from`,e,`(`,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":["backend: 'indexeddb' | 'zip' | 'iso'","path"],"sources":["../src/file-manager.ts"],"sourcesContent":["import type { FileChangedCallback, IFileManager } from '@cmdoss/site-builder'\nimport { Iso, Zip } from '@zenfs/archives'\nimport { configure, fs } from '@zenfs/core'\nimport * as path from '@zenfs/core/path'\nimport { IndexedDB } from '@zenfs/dom'\n\nexport class ZenFsFileManager implements IFileManager {\n private changeListeners = new Set<FileChangedCallback>()\n\n constructor(\n private workspaceDir = '/workspace',\n private backend: 'indexeddb' | 'zip' | 'iso' = 'indexeddb'\n ) {}\n\n async mount(data?: ArrayBuffer, force = false): Promise<void> {\n const backend =\n this.backend === 'indexeddb'\n ? IndexedDB\n : this.backend === 'zip'\n ? Zip\n : this.backend === 'iso'\n ? Iso\n : null\n if (!backend) throw new Error('Invalid backend specified')\n const isAccessible = await fs.promises\n .access(this.workspaceDir)\n .then(() => true)\n .catch(() => false)\n if (isAccessible) {\n if (!force) throw new Error('Workspace directory is already mounted')\n this.unmount() // Unmount existing instance\n }\n await configure({ mounts: { [this.workspaceDir]: { backend, data } } })\n }\n\n async writeFile(\n filePath: string,\n content: string | Uint8Array\n ): Promise<void> {\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 this.notifyChange({\n type: 'updated',\n path: filePath\n })\n }\n\n async removeFile(filePath: string): Promise<void> {\n filePath = ensureLeadingSlash(filePath)\n const workspaceFilePath = path.join(this.workspaceDir, filePath)\n await fs.promises.rm(workspaceFilePath)\n this.notifyChange({ type: 'removed', path: filePath })\n }\n\n async readFile(filePath: string): Promise<Uint8Array> {\n filePath = ensureLeadingSlash(filePath)\n const workspaceFilePath = path.join(this.workspaceDir, filePath)\n console.log('Reading file from path:', workspaceFilePath)\n return await fs.promises.readFile(workspaceFilePath)\n }\n\n async listFiles(): Promise<string[]> {\n const files = await fs.promises.readdir(this.workspaceDir, {\n withFileTypes: true,\n recursive: true\n })\n console.log('Files in workspace:', files)\n return files\n .filter(f => f.isFile())\n .map(f => path.resolve(f.parentPath, f.name))\n .map(ensureLeadingSlash)\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 async clear(): Promise<void> {\n const files = await fs.promises.readdir(this.workspaceDir, {\n withFileTypes: true\n })\n for (const file of files) {\n const filePath = path.join(file.parentPath, file.name)\n await fs.promises.rm(filePath, { recursive: true, force: true })\n }\n }\n\n unmount(): void {\n this.changeListeners.clear()\n fs.umount(this.workspaceDir)\n }\n}\n\nfunction ensureLeadingSlash(path: string): string {\n return path.startsWith('/') ? path : `/${path}`\n}\n"],"mappings":"yKAMA,IAAa,EAAb,KAAsD,CACpD,gBAA0B,IAAI,IAE9B,YACE,EAAuB,aACvB,EAA+C,YAC/C,CAFQ,KAAA,aAAA,EACA,KAAA,QAAA,EAGV,MAAM,MAAM,EAAoB,EAAQ,GAAsB,CAC5D,IAAM,EACJ,KAAK,UAAY,YACb,EACA,KAAK,UAAY,MACf,EACA,KAAK,UAAY,MACf,EACA,KACV,GAAI,CAAC,EAAS,MAAU,MAAM,4BAA4B,CAK1D,GAJqB,MAAM,EAAG,SAC3B,OAAO,KAAK,aAAa,CACzB,SAAW,GAAK,CAChB,UAAY,GAAM,CACH,CAChB,GAAI,CAAC,EAAO,MAAU,MAAM,yCAAyC,CACrE,KAAK,SAAS,CAEhB,MAAM,EAAU,CAAE,OAAQ,EAAG,KAAK,cAAe,CAAE,UAAS,OAAM,CAAE,CAAE,CAAC,CAGzE,MAAM,UACJ,EACA,EACe,CACf,EAAW,EAAmB,EAAS,CACvC,IAAM,EAAoB,EAAK,KAAK,KAAK,aAAc,EAAS,CAC1D,EAAM,EAAK,QAAQ,EAAkB,CAC3C,MAAM,EAAG,SAAS,MAAM,EAAK,CAAE,UAAW,GAAM,CAAC,CACjD,IAAM,EACJ,OAAO,GAAY,SAAW,IAAI,aAAa,CAAC,OAAO,EAAQ,CAAG,EACpE,MAAM,EAAG,SAAS,UAAU,EAAmB,EAAa,CAC5D,KAAK,aAAa,CAChB,KAAM,UACN,KAAM,EACP,CAAC,CAGJ,MAAM,WAAW,EAAiC,CAChD,EAAW,EAAmB,EAAS,CACvC,IAAM,EAAoB,EAAK,KAAK,KAAK,aAAc,EAAS,CAChE,MAAM,EAAG,SAAS,GAAG,EAAkB,CACvC,KAAK,aAAa,CAAE,KAAM,UAAW,KAAM,EAAU,CAAC,CAGxD,MAAM,SAAS,EAAuC,CACpD,EAAW,EAAmB,EAAS,CACvC,IAAM,EAAoB,EAAK,KAAK,KAAK,aAAc,EAAS,CAEhE,OADA,QAAQ,IAAI,0BAA2B,EAAkB,CAClD,MAAM,EAAG,SAAS,SAAS,EAAkB,CAGtD,MAAM,WAA+B,CACnC,IAAM,EAAQ,MAAM,EAAG,SAAS,QAAQ,KAAK,aAAc,CACzD,cAAe,GACf,UAAW,GACZ,CAAC,CAEF,OADA,QAAQ,IAAI,sBAAuB,EAAM,CAClC,EACJ,OAAO,GAAK,EAAE,QAAQ,CAAC,CACvB,IAAI,GAAK,EAAK,QAAQ,EAAE,WAAY,EAAE,KAAK,CAAC,CAC5C,IAAI,EAAmB,CAG5B,aAAa,EAA2C,CAEtD,OADA,KAAK,gBAAgB,IAAI,EAAS,KACrB,CACX,KAAK,gBAAgB,OAAO,EAAS,EAIzC,aAAqB,EAA+C,CAClE,IAAK,IAAM,KAAY,KAAK,gBAC1B,EAAS,EAAI,CAIjB,MAAM,OAAuB,CAC3B,IAAM,EAAQ,MAAM,EAAG,SAAS,QAAQ,KAAK,aAAc,CACzD,cAAe,GAChB,CAAC,CACF,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAW,EAAK,KAAK,EAAK,WAAY,EAAK,KAAK,CACtD,MAAM,EAAG,SAAS,GAAG,EAAU,CAAE,UAAW,GAAM,MAAO,GAAM,CAAC,EAIpE,SAAgB,CACd,KAAK,gBAAgB,OAAO,CAC5B,EAAG,OAAO,KAAK,aAAa,GAIhC,SAAS,EAAmB,EAAsB,CAChD,OAAOC,EAAK,WAAW,IAAI,CAAGA,EAAO,IAAIA"}
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('✅ Files copied to workspace from ZIP archive')\n }\n\n async readFile(filePath: string): Promise<Uint8Array> {\n log('📂 Reading file from', filePath)\n filePath = ensureLeadingSlash(filePath)\n const workspaceFilePath = path.join(this.workspaceDir, filePath)\n const content = await fs.promises.readFile(workspaceFilePath)\n log('✅ File read from', filePath, '(', 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.0",
3
+ "version": "0.2.0",
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.2",
20
+ "@zenfs/core": "^2.4.4",
20
21
  "@zenfs/dom": "^1.2.5",
21
- "@cmdoss/site-builder": "0.1.0"
22
+ "debug": "^4.4.3",
23
+ "@cmdoss/site-builder": "0.2.0"
22
24
  },
23
25
  "devDependencies": {
24
- "@types/node": "^22.13.4",
25
- "tsdown": "^0.15.4"
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-types": "tsc --noEmit",
32
+ "check:types": "tsc --noEmit",
31
33
  "build": "tsdown --platform browser --minify"
32
34
  }
33
35
  }
@@ -1,42 +1,119 @@
1
1
  import type { FileChangedCallback, IFileManager } from '@cmdoss/site-builder'
2
- import { Iso, Zip } from '@zenfs/archives'
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
- export class ZenFsFileManager implements IFileManager {
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
- private workspaceDir = '/workspace',
12
- private backend: 'indexeddb' | 'zip' | 'iso' = 'indexeddb'
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('✅ Files copied to workspace from ZIP archive')
65
+ }
66
+
67
+ async readFile(filePath: string): Promise<Uint8Array> {
68
+ log('📂 Reading file from', filePath)
69
+ filePath = ensureLeadingSlash(filePath)
70
+ const workspaceFilePath = path.join(this.workspaceDir, filePath)
71
+ const content = await fs.promises.readFile(workspaceFilePath)
72
+ log('✅ File read from', filePath, '(', content.byteLength, 'bytes )')
73
+ return content
74
+ }
14
75
 
15
- async mount(data?: ArrayBuffer, force = false): Promise<void> {
16
- const backend =
17
- this.backend === 'indexeddb'
18
- ? IndexedDB
19
- : this.backend === 'zip'
20
- ? Zip
21
- : this.backend === 'iso'
22
- ? Iso
23
- : null
24
- if (!backend) throw new Error('Invalid backend specified')
25
- const isAccessible = await fs.promises
26
- .access(this.workspaceDir)
27
- .then(() => true)
28
- .catch(() => false)
29
- if (isAccessible) {
30
- if (!force) throw new Error('Workspace directory is already mounted')
31
- this.unmount() // Unmount existing instance
76
+ async listFiles(): Promise<string[]> {
77
+ log('📄 Listing files in workspace')
78
+ const files = await fs.promises.readdir(this.workspaceDir, {
79
+ withFileTypes: true,
80
+ recursive: true
81
+ })
82
+ const result = files
83
+ .filter(f => f.isFile())
84
+ .map(f => path.join(f.parentPath, f.name))
85
+ .map(ensureLeadingSlash)
86
+ log('✅ Files currently in workspace', result)
87
+ return result
88
+ }
89
+
90
+ async getSize(): Promise<number> {
91
+ log('📏 Calculating total size of files in workspace')
92
+ let totalSize = 0
93
+ const files = await fs.promises.readdir(this.workspaceDir, {
94
+ withFileTypes: true,
95
+ recursive: true
96
+ })
97
+ for (const file of files) {
98
+ if (file.isFile()) {
99
+ const filePath = path.join(
100
+ this.workspaceDir,
101
+ file.parentPath,
102
+ file.name
103
+ )
104
+ const stats = await fs.promises.stat(filePath)
105
+ totalSize += stats.size
106
+ }
32
107
  }
33
- await configure({ mounts: { [this.workspaceDir]: { backend, data } } })
108
+ log('✅ Total size of files in workspace:', totalSize, 'bytes')
109
+ return totalSize
34
110
  }
35
111
 
36
112
  async writeFile(
37
113
  filePath: string,
38
114
  content: string | Uint8Array
39
115
  ): Promise<void> {
116
+ log('✍️ Writing file to', filePath)
40
117
  filePath = ensureLeadingSlash(filePath)
41
118
  const workspaceFilePath = path.join(this.workspaceDir, filePath)
42
119
  const dir = path.dirname(workspaceFilePath)
@@ -44,38 +121,19 @@ export class ZenFsFileManager implements IFileManager {
44
121
  const contentBytes =
45
122
  typeof content === 'string' ? new TextEncoder().encode(content) : content
46
123
  await fs.promises.writeFile(workspaceFilePath, contentBytes)
47
- this.notifyChange({
48
- type: 'updated',
49
- path: filePath
50
- })
124
+ log('✅ File written to', filePath, '(', contentBytes.byteLength, 'bytes )')
125
+ this.notifyChange({ type: 'updated', path: filePath })
51
126
  }
52
127
 
53
- async removeFile(filePath: string): Promise<void> {
128
+ async deleteFile(filePath: string): Promise<void> {
129
+ log('🗑️ Deleting file at', filePath)
54
130
  filePath = ensureLeadingSlash(filePath)
55
131
  const workspaceFilePath = path.join(this.workspaceDir, filePath)
56
132
  await fs.promises.rm(workspaceFilePath)
133
+ log('✅ File deleted at', filePath)
57
134
  this.notifyChange({ type: 'removed', path: filePath })
58
135
  }
59
136
 
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
137
  onFileChange(callback: FileChangedCallback): () => void {
80
138
  this.changeListeners.add(callback)
81
139
  return () => {
@@ -89,19 +147,15 @@ export class ZenFsFileManager implements IFileManager {
89
147
  }
90
148
  }
91
149
 
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
150
  unmount(): void {
151
+ log('🚪 Unmounting workspace directory (', this.workspaceDir, ')')
103
152
  this.changeListeners.clear()
104
- fs.umount(this.workspaceDir)
153
+ if (this.mountDir === this.workspaceDir) {
154
+ // Unmount only if mountDir and workspaceDir are the same
155
+ fs.umount(this.mountDir)
156
+ } else {
157
+ log('⚠️ Skipping unmount: mountDir and workspaceDir are different')
158
+ }
105
159
  }
106
160
  }
107
161
 
package/tsconfig.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "extends": "../../configs/tsconfig.library.json",
4
4
  "compilerOptions": {
5
5
  "lib": ["ES2021", "DOM"],
6
- "outDir": "/dist",
6
+ "outDir": "dist",
7
7
  "emitDeclarationOnly": true,
8
8
  "allowImportingTsExtensions": true
9
9
  },
@@ -1,14 +0,0 @@
1
-
2
- > @cmdoss/file-manager@0.1.0 test /home/runner/work/ts-sdks/ts-sdks/packages/file-manager
3
- > node --test
4
-
5
- TAP version 13
6
- 1..0
7
- # tests 0
8
- # suites 0
9
- # pass 0
10
- # fail 0
11
- # cancelled 0
12
- # skipped 0
13
- # todo 0
14
- # duration_ms 56.835878