mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
160 lines
4.0 KiB
TypeScript
160 lines
4.0 KiB
TypeScript
import path from "node:path";
|
|
import {
|
|
isCancel,
|
|
log,
|
|
autocompleteMultiselect,
|
|
spinner,
|
|
} from "@clack/prompts";
|
|
import { execa } from "execa";
|
|
import fs from "fs-extra";
|
|
import pc from "picocolors";
|
|
import { PKG_ROOT } from "../../constants";
|
|
import type { ProjectConfig } from "../../types";
|
|
import { exitCancelled } from "../../utils/errors";
|
|
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
|
import { processAndCopyFiles } from "../core/template-manager";
|
|
|
|
export async function setupVibeRules(config: ProjectConfig) {
|
|
const { packageManager, projectDir } = config;
|
|
|
|
try {
|
|
log.info("Setting up Ruler...");
|
|
|
|
const rulerDir = path.join(projectDir, ".ruler");
|
|
const rulerTemplateDir = path.join(
|
|
PKG_ROOT,
|
|
"templates",
|
|
"addons",
|
|
"ruler",
|
|
".ruler",
|
|
);
|
|
|
|
if (!(await fs.pathExists(rulerDir))) {
|
|
if (await fs.pathExists(rulerTemplateDir)) {
|
|
await processAndCopyFiles("**/*", rulerTemplateDir, rulerDir, config);
|
|
} else {
|
|
log.error(pc.red("Ruler template directory not found"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
const EDITORS = {
|
|
cursor: {
|
|
label: "Cursor",
|
|
},
|
|
windsurf: {
|
|
label: "Windsurf",
|
|
},
|
|
claude: { label: "Claude Code" },
|
|
copilot: {
|
|
label: "GitHub Copilot",
|
|
},
|
|
"gemini-cli": { label: "Gemini CLI" },
|
|
codex: { label: "OpenAI Codex CLI" },
|
|
jules: { label: "Jules" },
|
|
cline: { label: "Cline" },
|
|
aider: { label: "Aider" },
|
|
firebase: { label: "Firebase Studio" },
|
|
openhands: { label: "Open Hands" },
|
|
junie: { label: "Junie" },
|
|
augmentcode: {
|
|
label: "AugmentCode",
|
|
},
|
|
kilocode: {
|
|
label: "Kilo Code",
|
|
},
|
|
opencode: { label: "OpenCode" },
|
|
} as const;
|
|
|
|
const selectedEditors = await autocompleteMultiselect<keyof typeof EDITORS>(
|
|
{
|
|
message: "Select AI assistants for Ruler",
|
|
options: Object.entries(EDITORS).map(([key, v]) => ({
|
|
value: key as keyof typeof EDITORS,
|
|
label: v.label,
|
|
})),
|
|
required: false,
|
|
},
|
|
);
|
|
|
|
if (isCancel(selectedEditors)) return exitCancelled("Operation cancelled");
|
|
|
|
if (selectedEditors.length === 0) {
|
|
log.info("No AI assistants selected. To apply rules later, run:");
|
|
log.info(
|
|
pc.cyan(
|
|
`${getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only")}`,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
|
|
const configFile = path.join(rulerDir, "ruler.toml");
|
|
const currentConfig = await fs.readFile(configFile, "utf-8");
|
|
|
|
let updatedConfig = currentConfig;
|
|
|
|
const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
|
|
updatedConfig = updatedConfig.replace(
|
|
/default_agents = \[\]/,
|
|
defaultAgentsLine,
|
|
);
|
|
|
|
await fs.writeFile(configFile, updatedConfig);
|
|
|
|
await addRulerScriptToPackageJson(projectDir, packageManager);
|
|
|
|
const s = spinner();
|
|
s.start("Applying rules with Ruler...");
|
|
|
|
try {
|
|
const rulerApplyCmd = getPackageExecutionCommand(
|
|
packageManager,
|
|
`@intellectronica/ruler@latest apply --agents ${selectedEditors.join(",")} --local-only`,
|
|
);
|
|
await execa(rulerApplyCmd, {
|
|
cwd: projectDir,
|
|
env: { CI: "true" },
|
|
shell: true,
|
|
});
|
|
|
|
s.stop("Applied rules with Ruler");
|
|
} catch (_error) {
|
|
s.stop(pc.red("Failed to apply rules"));
|
|
}
|
|
} catch (error) {
|
|
log.error(pc.red("Failed to set up Ruler"));
|
|
if (error instanceof Error) {
|
|
console.error(pc.red(error.message));
|
|
}
|
|
}
|
|
}
|
|
|
|
async function addRulerScriptToPackageJson(
|
|
projectDir: string,
|
|
packageManager: ProjectConfig["packageManager"],
|
|
) {
|
|
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
|
|
|
if (!(await fs.pathExists(rootPackageJsonPath))) {
|
|
log.warn(
|
|
"Root package.json not found, skipping ruler:apply script addition",
|
|
);
|
|
return;
|
|
}
|
|
|
|
const packageJson = await fs.readJson(rootPackageJsonPath);
|
|
|
|
if (!packageJson.scripts) {
|
|
packageJson.scripts = {};
|
|
}
|
|
|
|
const rulerApplyCommand = getPackageExecutionCommand(
|
|
packageManager,
|
|
"@intellectronica/ruler@latest apply --local-only",
|
|
);
|
|
packageJson.scripts["ruler:apply"] = rulerApplyCommand;
|
|
|
|
await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
|
|
}
|