mirror of
https://github.com/mvschwarz/openrig-studio.git
synced 2026-11-07 05:56:21 -10:00
An app shell and a contract, not a framework you link against. A rail of surfaces, a stage, an agent sidebar, and a protocol for how a surface talks to them. An app conforms to the contract; it never imports this package. - app/ the reference runtime and shell - contract/ the contract: shell protocol, manifest, row schema, runtime API - create-studio/ a scaffolder for a new studio - skills/ how to build an app on this SDK, written from experience - fixtures/ example rig state for the FLOOR surface - test/ 73 tests, including the consumer-surface seam and redeploy survival Consumers register surfaces from OUTSIDE the package via --surfaces, so nothing is written into node_modules and a copied install cannot carry another instance's rows. Licensed Apache-2.0.
47 lines
1.9 KiB
JavaScript
47 lines
1.9 KiB
JavaScript
// Emit — stage every file into a sibling temp directory, then ONE atomic
|
|
// rename into place.
|
|
//
|
|
// Why a sibling and not the system temp dir: rename() is only atomic within a
|
|
// filesystem. A sibling of the destination is guaranteed to be on the same
|
|
// filesystem; /tmp is not, and would silently degrade to a copy that can be
|
|
// interrupted halfway.
|
|
//
|
|
// The guarantee this buys: the destination either does not exist, or is the
|
|
// complete project. A crash mid-generation leaves a hidden staging directory
|
|
// that is removed on the way out — never a half-written project a developer
|
|
// might mistake for a working one.
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
export function emitProject(dest, files) {
|
|
const parent = path.dirname(dest);
|
|
const staging = fs.mkdtempSync(path.join(parent, ".create-studio-staging-"));
|
|
|
|
// Test affordance ONLY: throw after N files have been staged, to demonstrate
|
|
// that a mid-generation failure leaves no partial project. Inert when unset.
|
|
const failAfter = Number(process.env.OPENRIG_STUDIO_CREATE_FAIL_AFTER || 0);
|
|
|
|
let staged = 0;
|
|
try {
|
|
for (const [rel, content] of Object.entries(files)) {
|
|
const target = path.join(staging, rel);
|
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
fs.writeFileSync(target, content, "utf8");
|
|
staged += 1;
|
|
if (failAfter > 0 && staged >= failAfter) {
|
|
throw new Error(
|
|
`OPENRIG_STUDIO_CREATE_FAIL_AFTER=${failAfter} — simulated mid-generation failure after ${staged} file(s)`
|
|
);
|
|
}
|
|
}
|
|
// POSIX rename() onto an existing EMPTY directory succeeds; admission has
|
|
// already refused a non-empty destination.
|
|
fs.renameSync(staging, dest);
|
|
return { dest, files: Object.keys(files) };
|
|
} catch (error) {
|
|
try { fs.rmSync(staging, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
throw error;
|
|
}
|
|
}
|