@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
69 lines • 3.23 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { FileType } from "../../types.js";
import { rstripSlash } from "../../utils/slash.js";
import { download, upload, createDirectory, listDirectoryContents } from "./client.js";
import { backendPath } from "./path.js";
import { read } from "./read.js";
import { stat } from "./stat.js";
import { write } from "./write.js";
import { eisdir } from "../../errors/fs.js";
/**
* Copy a directory tree file by file, filling `copied` with each source
* entry copied, by backend path, as its listing showed it, for a move to
* delete only an entry still as it was. Mirrors Python's `copy_tree`.
*/
export async function copyTree(accessor, remoteSrc, remoteDst, copied = null) {
await createDirectory(accessor, remoteDst);
for (const entry of await listDirectoryContents(accessor, remoteSrc)) {
const name = rstripSlash(entry.path).split('/').pop() ?? '';
const childDst = `${rstripSlash(remoteDst)}/${name}`;
if (entry.isDirectory) {
await copyTree(accessor, entry.path, childDst, copied);
}
else {
await upload(accessor, childDst, await download(accessor, entry.path), true);
}
copied?.set(entry.path, entry);
}
}
export async function copy(accessor, src, dst, recursive = false) {
const srcStat = await stat(accessor, src);
// Same-path guard runs after stat (and the non-recursive directory check)
// so a missing source or `cp` of a directory still raises.
const samePath = backendPath(accessor.config, src) === backendPath(accessor.config, dst);
if (srcStat.type === FileType.DIRECTORY) {
if (!recursive)
throw eisdir(src.virtual);
if (samePath)
return;
const remoteSrc = backendPath(accessor.config, src);
const remoteDst = backendPath(accessor.config, dst);
if (remoteDst.startsWith(remoteSrc + '/')) {
// Copying a directory into its own subtree creates the destination
// inside the source, so the walk would descend into the fresh copy
// forever. Refuse before any create_directory/upload.
throw new Error(`cannot copy a directory, '${src.virtual}', into itself, '${dst.virtual}'`);
}
await copyTree(accessor, remoteSrc, remoteDst);
return;
}
if (samePath) {
// Copying a file onto itself would re-upload it; skip.
return;
}
const data = await read(accessor, src);
await write(accessor, dst, data);
}
//# sourceMappingURL=copy.js.map