@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
138 lines • 5.57 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 { ListingCheckStore } from "../../cache/index/ram.js";
import { FileStat, FileType } from "../../types.js";
import { enoent } from "../../errors/fs.js";
import { stripSlash } from "../../utils/slash.js";
import { GitHubApiError } from "./client.js";
import { fetchDirTree, fetchHead, pointRow, refreshTree, rowOf } from "./tree.js";
import { makeTreeEntry } from "./tree_entry.js";
/**
* Every entry under a directory of the repository, each as its row, from one
* recursive tree. Null when GitHub truncates that tree, which then names only
* some paths, or when `path` is no directory in it, so the walk's readdir and
* stat report what is there. Mirrors `tree` in `mirage/core/github/stat.py`.
*/
export async function tree(accessor, path) {
if (accessor.truncated)
return null;
const fetched = await refreshTree(accessor);
if (fetched === null)
return null;
const base = stripSlash(path.mountPath);
if (base !== '' && fetched.tree[base]?.type !== 'tree')
return null;
const lead = base === '' ? '' : `${base}/`;
const found = new Map();
for (const [key, entry] of Object.entries(fetched.tree)) {
if (key === base || !key.startsWith(lead))
continue;
found.set(key.slice(lead.length), rowOf(entry, key.slice(key.lastIndexOf('/') + 1)));
}
return found;
}
/**
* Stat one path in the repository.
*
* A path answers with its row, as its folder's listing names it. The gate's
* check is handed no listed row, so it asks for the one path with one
* directory listing, its parent's; a parent it cannot see, and a truncated
* listing without the name, are no answer, so the whole tree is asked
* instead, and one GitHub truncates is walked down from the ref one directory
* at a time.
*
* Mirrors Python's `stat`.
*/
export async function stat(accessor, path, entry, index) {
const rel = stripSlash(path.mountPath);
if (rel === '') {
return new FileStat({
name: '/',
type: FileType.DIRECTORY,
fingerprint: await rootVersion(accessor, index),
});
}
if (entry !== undefined && entry !== null)
return entry;
if (!(index instanceof ListingCheckStore))
throw enoent(path.virtual);
const answer = await pointRow(accessor, rel);
if (answer !== null) {
if (answer.entry !== null)
return rowOf(answer.entry, answer.entry.path);
if (!answer.truncated)
throw enoent(path.virtual);
}
if (!accessor.truncated) {
const whole = await refreshTree(accessor);
if (whole !== null) {
const row = whole.tree[rel];
if (row === undefined)
throw enoent(path.virtual);
return rowOf(row, rel.slice(rel.lastIndexOf('/') + 1));
}
}
const found = await walked(accessor, rel);
if (found === null)
throw enoent(path.virtual);
return found;
}
/**
* The row at `rel`, walked down from the ref one directory at a time; null
* when a level does not name it. Mirrors Python's `_walked`.
*/
async function walked(accessor, rel) {
let sha = accessor.ref;
const names = rel.split('/');
for (const [depth, name] of names.entries()) {
const rows = await fetchDirTree(accessor.account.transport, accessor.owner, accessor.repo, sha);
const found = rows.find((row) => row.path === name);
if (found === undefined)
return null;
if (depth === names.length - 1)
return rowOf(makeTreeEntry(found), name);
if (found.type !== 'tree')
return null;
sha = found.sha;
}
return null;
}
/**
* The version of the whole mount: the head commit its ref is at.
*
* Only the gate's `ListingCheckStore` asks for it, with one shallow request.
* Every other index (the mount's own, an undefined index) names no version
* and reads nothing, neither the index nor the backend, so a getattr of the
* root never pays a check or a store round trip: nothing reads a root
* fingerprint off a mount-view stat. Nothing here refills the index: a
* refused head names no version rather than falling into a lookup.
*
* Mirrors Python's `_root_version`.
*/
async function rootVersion(accessor, index) {
if (!(index instanceof ListingCheckStore))
return null;
try {
return await fetchHead(accessor.account.transport, accessor.owner, accessor.repo, accessor.ref);
}
catch (err) {
// An answer that refuses the head is no answer about the version, the
// way pointRow reads a refusal; anything else propagates.
if (!(err instanceof GitHubApiError))
throw err;
console.warn(`head of ${accessor.owner}/${accessor.repo} not answered: ${String(err)}`);
return null;
}
}
//# sourceMappingURL=stat.js.map