framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
74 lines • 3.31 kB
JavaScript
import { join } from 'node:path';
import { THE_FRAMEWORK_DIR } from './framework-dir.js';
import { sanitizeCustomPresets } from './registry.js';
import { nodeStoreFs } from './store/index.js';
/**
* Project-scoped custom presets (#1025).
*
* The user tier (#626) saves a custom preset to the user's home file, so it follows the person
* across every project and stays private. The project tier saves it into the repo instead, so it
* travels with the code and everyone who clones the repo gets it — the "share your presets" half
* of the issue.
*
* The store is a plain JSON array of {@link CustomPreset}, the exact shape the user tier uses, so
* the dashboard renders both from one type and the same sanitizer guards both files.
*/
/** The committed file holding a project's shared custom presets, under `.the-framework/`. */
export const PROJECT_PRESETS_FILE = `${THE_FRAMEWORK_DIR}/custom-presets.json`;
/** The `.the-framework/.gitignore` line that un-ignores the presets file so git tracks it. */
const GITIGNORE_NEGATION = '!custom-presets.json';
function gitignorePath(cwd) {
return join(cwd, THE_FRAMEWORK_DIR, '.gitignore');
}
/**
* Read a project's shared custom presets. Forgiving like every other read here: a missing,
* unreadable or malformed file yields `[]`, never throws, and the same sanitizer that guards
* the home file drops any hand-edited or hostile entry.
*/
export async function readProjectPresets(cwd, fs = nodeStoreFs()) {
let raw;
try {
raw = await fs.read(join(cwd, PROJECT_PRESETS_FILE));
}
catch {
return [];
}
let parsed;
try {
parsed = JSON.parse(raw);
}
catch {
return [];
}
return sanitizeCustomPresets(parsed);
}
/**
* Write a project's shared custom presets, sanitizing first so the committed file is always
* well-formed. Also ensures `.the-framework/.gitignore` un-ignores the file: the dir's ignore is
* `*` + a short allowlist (only the agent archive is committed by default), so without a negation git would
* never see the presets and they could not be shared. Removing every preset writes an empty array
* rather than deleting the file, so the negation stays in place for the next save.
*/
export async function writeProjectPresets(cwd, presets, fs = nodeStoreFs()) {
await fs.mkdir(join(cwd, THE_FRAMEWORK_DIR));
await ensureGitignoreNegation(cwd, fs);
const sanitized = sanitizeCustomPresets(presets);
await fs.write(join(cwd, PROJECT_PRESETS_FILE), `${JSON.stringify(sanitized, null, 2)}\n`);
}
/** Append the un-ignore line to `.the-framework/.gitignore` unless it is already there. */
async function ensureGitignoreNegation(cwd, fs) {
const path = gitignorePath(cwd);
let current = '';
try {
current = await fs.read(path);
}
catch {
// No .gitignore yet (a pre-install dir): the negation only bites once the `*` ignore exists,
// so writing a bare negation is harmless and self-heals when install adds the rest.
}
if (current.split('\n').some(line => line.trim() === GITIGNORE_NEGATION))
return;
const prefix = current && !current.endsWith('\n') ? `${current}\n` : current;
await fs.write(path, `${prefix}${GITIGNORE_NEGATION}\n`);
}
//# sourceMappingURL=project-presets.js.map