fix(cli): init creates its files where hard links are refused, with no Developer Mode needed (#6463)

This commit is contained in:
Miguel Ángel
2026-10-26 18:50:41 +00:00
committed by GitHub
parent 306f21a1ba
commit 6dee9d144c
2 changed files with 21 additions and 13 deletions
@@ -12,7 +12,11 @@ import {
vi.mock("node:fs", async (importOriginal) => {
const original = await importOriginal<typeof fs>();
return { ...original, readFileSync: vi.fn(original.readFileSync) };
return {
...original,
readFileSync: vi.fn(original.readFileSync),
linkSync: vi.fn(original.linkSync),
};
});
const dirs: string[] = [];
@@ -43,6 +47,18 @@ describe("project config creation", () => {
expect(JSON.parse(fs.readFileSync(path, "utf-8"))).toEqual(updated);
});
it("creates the config on a volume or sandbox that refuses hard links (EPERM)", () => {
const dir = project();
vi.mocked(fs.linkSync).mockImplementationOnce(() => {
throw Object.assign(new Error("operation not permitted"), { code: "EPERM" });
});
createProjectConfig(dir);
expect(JSON.parse(fs.readFileSync(projectConfigPath(dir), "utf-8"))).toEqual(
DEFAULT_PROJECT_CONFIG,
);
expect(fs.readdirSync(dir)).toEqual(["hyperframes.json"]);
});
it("preserves a config created after the skill seed reads ENOENT", () => {
const dir = project();
const path = projectConfigPath(dir);
+4 -12
View File
@@ -1,18 +1,10 @@
import { linkSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { createFileAtomically } from "@hyperframes/core/atomic-file";
/** Publish complete content without replacing or following an existing destination entry. */
export function writeNewFileSync(filePath: string, content: string): void {
const stagingDir = mkdtempSync(join(dirname(filePath), ".hf-create-"));
try {
const stagedPath = join(stagingDir, "content");
writeFileSync(stagedPath, content, { encoding: "utf-8", flag: "wx" });
try {
linkSync(stagedPath, filePath);
} catch (err) {
if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
}
} finally {
rmSync(stagingDir, { recursive: true, force: true });
createFileAtomically(filePath, content);
} catch (err) {
if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
}
}