Files
cronmaster/app/_utils/snippets-utils.ts
2025-11-05 20:30:01 +00:00

170 lines
4.2 KiB
TypeScript

import { promises as fs } from "fs";
import path from "path";
import { isDocker } from "../_server/actions/global";
export interface BashSnippet {
id: string;
title: string;
description: string;
category: string;
template: string;
tags: string[];
source: "builtin" | "user";
filePath: string;
}
interface SnippetMetadata {
id?: string;
title?: string;
description?: string;
category?: string;
tags?: string[];
}
const parseMetadata = (content: string): SnippetMetadata => {
const metadata: SnippetMetadata = {};
const lines = content.split("\n");
for (const line of lines) {
const trimmed = line.trim();
const match = trimmed.match(/^#\s*@(\w+):\s*(.+)$/);
if (match) {
const [, key, value] = match;
switch (key) {
case "id":
metadata.id = value.trim();
break;
case "title":
metadata.title = value.trim();
break;
case "description":
metadata.description = value.trim();
break;
case "category":
metadata.category = value.trim();
break;
case "tags":
metadata.tags = value.split(",").map((tag) => tag.trim());
break;
}
}
}
return metadata;
}
const extractTemplate = (content: string): string => {
const lines = content.split("\n");
const templateLines: string[] = [];
let inTemplate = false;
for (const line of lines) {
if (line.trim().match(/^#\s*@\w+:/)) {
continue;
}
if (!inTemplate && line.trim() && !line.trim().startsWith("# @")) {
inTemplate = true;
}
if (inTemplate) {
templateLines.push(line);
}
}
return templateLines.join("\n").trim();
}
const scanSnippetDirectory = async (
dirPath: string,
source: "builtin" | "user"
): Promise<BashSnippet[]> => {
const snippets: BashSnippet[] = [];
try {
const files = await fs.readdir(dirPath);
for (const file of files) {
if (file.endsWith(".sh")) {
const filePath = path.join(dirPath, file);
const content = await fs.readFile(filePath, "utf-8");
const metadata = parseMetadata(content);
const template = extractTemplate(content);
if (
metadata.id &&
metadata.title &&
metadata.description &&
metadata.category
) {
snippets.push({
id: metadata.id,
title: metadata.title,
description: metadata.description,
category: metadata.category,
template,
tags: metadata.tags || [],
source,
filePath,
});
}
}
}
} catch (error) {
console.warn(`Warning: Could not scan directory ${dirPath}:`, error);
}
return snippets;
}
export const loadAllSnippets = async (): Promise<BashSnippet[]> => {
const docker = await isDocker();
let builtinSnippetsPath: string;
let userSnippetsPath: string;
if (docker) {
builtinSnippetsPath = "/app/app/_utils/snippets";
userSnippetsPath = "/app/snippets";
} else {
builtinSnippetsPath = path.join(process.cwd(), "app", "_utils", "snippets");
userSnippetsPath = path.join(process.cwd(), "snippets");
}
const builtinSnippets = await scanSnippetDirectory(
builtinSnippetsPath,
"builtin"
);
const userSnippets = await scanSnippetDirectory(userSnippetsPath, "user");
return [...builtinSnippets, ...userSnippets];
}
export const searchBashSnippets = (
snippets: BashSnippet[],
query: string
): BashSnippet[] => {
const lowercaseQuery = query.toLowerCase();
return snippets.filter(
(snippet) =>
snippet.title.toLowerCase().includes(lowercaseQuery) ||
snippet.description.toLowerCase().includes(lowercaseQuery) ||
snippet.tags.some((tag) => tag.toLowerCase().includes(lowercaseQuery)) ||
snippet.category.toLowerCase().includes(lowercaseQuery)
);
}
export const getSnippetCategories = (snippets: BashSnippet[]): string[] => {
const categories = new Set(snippets.map((snippet) => snippet.category));
return Array.from(categories).sort();
}
export const getSnippetById = (
snippets: BashSnippet[],
id: string
): BashSnippet | undefined => {
return snippets.find((snippet) => snippet.id === id);
}