286 lines
7.8 KiB
JavaScript
286 lines
7.8 KiB
JavaScript
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const { parseConfigYamlContent } = require("./apply-config");
|
|
const { objectToYaml, generateConfigHeader, getAvailableItems } = require("./generate-config");
|
|
const { parseCollectionYaml } = require("./yaml-parser");
|
|
|
|
const DEFAULT_CONFIG_PATH = "awesome-copilot.config.yml";
|
|
const SECTION_METADATA = {
|
|
prompts: { dir: "prompts", ext: ".prompt.md", label: "Prompts", singular: "prompt" },
|
|
instructions: { dir: "instructions", ext: ".instructions.md", label: "Instructions", singular: "instruction" },
|
|
chatmodes: { dir: "chatmodes", ext: ".chatmode.md", label: "Chat Modes", singular: "chat mode" },
|
|
collections: { dir: "collections", ext: ".collection.yml", label: "Collections", singular: "collection" }
|
|
};
|
|
const CONFIG_SECTIONS = Object.keys(SECTION_METADATA);
|
|
|
|
function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
|
|
if (!fs.existsSync(configPath)) {
|
|
throw new Error(`Configuration file not found: ${configPath}`);
|
|
}
|
|
|
|
const rawContent = fs.readFileSync(configPath, "utf8");
|
|
const { header, body } = splitHeaderAndBody(rawContent);
|
|
const parsed = parseConfigYamlContent(body || "");
|
|
const config = ensureConfigStructure(parsed || {});
|
|
|
|
return { config, header };
|
|
}
|
|
|
|
function saveConfig(configPath, config, header) {
|
|
const ensuredConfig = ensureConfigStructure(config || {});
|
|
const sortedConfig = sortConfigSections(ensuredConfig);
|
|
const yamlContent = objectToYaml(sortedConfig);
|
|
const headerContent = formatHeader(header);
|
|
|
|
fs.writeFileSync(configPath, headerContent + yamlContent);
|
|
}
|
|
|
|
function splitHeaderAndBody(content) {
|
|
const lines = content.split("\n");
|
|
const headerLines = [];
|
|
let firstBodyIndex = 0;
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const trimmed = lines[i].trim();
|
|
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
headerLines.push(lines[i]);
|
|
firstBodyIndex = i + 1;
|
|
} else {
|
|
firstBodyIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
const header = headerLines.join("\n");
|
|
const body = lines.slice(firstBodyIndex).join("\n");
|
|
|
|
return { header, body };
|
|
}
|
|
|
|
function ensureConfigStructure(config) {
|
|
const sanitized = typeof config === "object" && config !== null ? { ...config } : {};
|
|
|
|
if (!sanitized.version) {
|
|
sanitized.version = "1.0";
|
|
}
|
|
|
|
const project = typeof sanitized.project === "object" && sanitized.project !== null ? { ...sanitized.project } : {};
|
|
if (project.output_directory === undefined) {
|
|
project.output_directory = ".awesome-copilot";
|
|
}
|
|
sanitized.project = project;
|
|
|
|
CONFIG_SECTIONS.forEach(section => {
|
|
sanitized[section] = sanitizeSection(sanitized[section]);
|
|
});
|
|
|
|
return sanitized;
|
|
}
|
|
|
|
function sanitizeSection(section) {
|
|
if (!section || typeof section !== "object") {
|
|
return {};
|
|
}
|
|
|
|
const sanitized = {};
|
|
for (const [key, value] of Object.entries(section)) {
|
|
sanitized[key] = toBoolean(value);
|
|
}
|
|
|
|
return sanitized;
|
|
}
|
|
|
|
function toBoolean(value) {
|
|
if (typeof value === "boolean") {
|
|
return value;
|
|
}
|
|
|
|
if (typeof value === "string") {
|
|
const normalized = value.trim().toLowerCase();
|
|
if (normalized === "true") return true;
|
|
if (normalized === "false") return false;
|
|
}
|
|
|
|
return Boolean(value);
|
|
}
|
|
|
|
function sortConfigSections(config) {
|
|
const sorted = { ...config };
|
|
|
|
CONFIG_SECTIONS.forEach(section => {
|
|
sorted[section] = sortObjectKeys(sorted[section]);
|
|
});
|
|
|
|
return sorted;
|
|
}
|
|
|
|
function sortObjectKeys(obj) {
|
|
if (!obj || typeof obj !== "object") {
|
|
return {};
|
|
}
|
|
|
|
return Object.keys(obj)
|
|
.sort((a, b) => a.localeCompare(b))
|
|
.reduce((acc, key) => {
|
|
acc[key] = obj[key];
|
|
return acc;
|
|
}, {});
|
|
}
|
|
|
|
function formatHeader(existingHeader) {
|
|
const header = existingHeader && existingHeader.trim().length > 0
|
|
? existingHeader
|
|
: generateConfigHeader();
|
|
|
|
let normalized = header;
|
|
|
|
if (!normalized.endsWith("\n")) {
|
|
normalized += "\n";
|
|
}
|
|
if (!normalized.endsWith("\n\n")) {
|
|
normalized += "\n";
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
function countEnabledItems(section = {}) {
|
|
return Object.values(section).filter(Boolean).length;
|
|
}
|
|
|
|
function getAllAvailableItems(type) {
|
|
const meta = SECTION_METADATA[type];
|
|
|
|
if (!meta) {
|
|
return [];
|
|
}
|
|
|
|
return getAvailableItems(path.join(__dirname, meta.dir), meta.ext);
|
|
}
|
|
|
|
/**
|
|
* Get individual items from a collection
|
|
*/
|
|
function getCollectionItems(collectionName) {
|
|
const collectionPath = path.join(__dirname, "collections", `${collectionName}.collection.yml`);
|
|
if (!fs.existsSync(collectionPath)) {
|
|
return { prompts: [], instructions: [], chatmodes: [] };
|
|
}
|
|
|
|
const collection = parseCollectionYaml(collectionPath);
|
|
if (!collection || !collection.items) {
|
|
return { prompts: [], instructions: [], chatmodes: [] };
|
|
}
|
|
|
|
const result = { prompts: [], instructions: [], chatmodes: [] };
|
|
|
|
collection.items.forEach(item => {
|
|
const filePath = item.path;
|
|
const filename = path.basename(filePath);
|
|
|
|
if (filename.endsWith('.prompt.md')) {
|
|
const name = filename.replace('.prompt.md', '');
|
|
result.prompts.push(name);
|
|
} else if (filename.endsWith('.instructions.md')) {
|
|
const name = filename.replace('.instructions.md', '');
|
|
result.instructions.push(name);
|
|
} else if (filename.endsWith('.chatmode.md')) {
|
|
const name = filename.replace('.chatmode.md', '');
|
|
result.chatmodes.push(name);
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Update individual items when a collection is toggled
|
|
*/
|
|
function updateCollectionItems(config, collectionName, enabled) {
|
|
const items = getCollectionItems(collectionName);
|
|
const configCopy = { ...config };
|
|
|
|
// Ensure sections exist
|
|
CONFIG_SECTIONS.forEach(section => {
|
|
if (!configCopy[section]) {
|
|
configCopy[section] = {};
|
|
}
|
|
});
|
|
|
|
// Update individual items
|
|
items.prompts.forEach(prompt => {
|
|
configCopy.prompts[prompt] = enabled;
|
|
});
|
|
|
|
items.instructions.forEach(instruction => {
|
|
configCopy.instructions[instruction] = enabled;
|
|
});
|
|
|
|
items.chatmodes.forEach(chatmode => {
|
|
configCopy.chatmodes[chatmode] = enabled;
|
|
});
|
|
|
|
return configCopy;
|
|
}
|
|
|
|
/**
|
|
* Get which collections contain specific items
|
|
*/
|
|
function getItemToCollectionsMap() {
|
|
const map = {};
|
|
const collectionsDir = path.join(__dirname, "collections");
|
|
|
|
if (!fs.existsSync(collectionsDir)) {
|
|
return map;
|
|
}
|
|
|
|
const collectionFiles = fs.readdirSync(collectionsDir)
|
|
.filter(file => file.endsWith('.collection.yml'));
|
|
|
|
collectionFiles.forEach(file => {
|
|
const collectionName = file.replace('.collection.yml', '');
|
|
const collectionPath = path.join(collectionsDir, file);
|
|
const collection = parseCollectionYaml(collectionPath);
|
|
|
|
if (collection && collection.items) {
|
|
collection.items.forEach(item => {
|
|
const filename = path.basename(item.path);
|
|
let itemName;
|
|
|
|
if (filename.endsWith('.prompt.md')) {
|
|
itemName = filename.replace('.prompt.md', '');
|
|
if (!map[itemName]) map[itemName] = { section: 'prompts', collections: [] };
|
|
map[itemName].collections.push(collectionName);
|
|
} else if (filename.endsWith('.instructions.md')) {
|
|
itemName = filename.replace('.instructions.md', '');
|
|
if (!map[itemName]) map[itemName] = { section: 'instructions', collections: [] };
|
|
map[itemName].collections.push(collectionName);
|
|
} else if (filename.endsWith('.chatmode.md')) {
|
|
itemName = filename.replace('.chatmode.md', '');
|
|
if (!map[itemName]) map[itemName] = { section: 'chatmodes', collections: [] };
|
|
map[itemName].collections.push(collectionName);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
return map;
|
|
}
|
|
|
|
module.exports = {
|
|
DEFAULT_CONFIG_PATH,
|
|
CONFIG_SECTIONS,
|
|
SECTION_METADATA,
|
|
loadConfig,
|
|
saveConfig,
|
|
splitHeaderAndBody,
|
|
ensureConfigStructure,
|
|
sortObjectKeys,
|
|
countEnabledItems,
|
|
getAllAvailableItems,
|
|
getCollectionItems,
|
|
updateCollectionItems,
|
|
getItemToCollectionsMap
|
|
};
|