Skip to content

Commit 94f8740

Browse files
authored
Merge pull request #39 from thibaultyou/refactor/38-standardize-code-and-improve-cli
[REFACTOR] Standardize Code and Improve CLI Implementation
2 parents 0ad86bb + 2777211 commit 94f8740

32 files changed

+1191
-1096
lines changed

.github/workflows/update_views.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Update Views and Metadata
1+
name: Update Prompts and Views
22

33
on:
44
push:
@@ -64,12 +64,12 @@ jobs:
6464
echo "FORCE_REGENERATE=false" >> $GITHUB_ENV
6565
fi
6666
67-
- name: Generate metadata
67+
- name: Update metadata files
6868
env:
6969
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
7070
FORCE_REGENERATE: ${{ env.FORCE_REGENERATE }}
7171
CLI_ENV: ci
72-
run: npm run generate-metadata
72+
run: npm run update-metadata
7373

7474
- name: Update views
7575
run: npm run update-views

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ This project serves as a starting point for creating your own AI toolkit, demons
4747
## ⚡ Quick Start
4848

4949
1. Fork and clone the repository
50-
2. Set up Anthropic API key (GitHub Actions and CLI)
51-
3. Install dependencies: `npm install`
52-
4. Build and install CLI: `npm run build && npm install -g .`
53-
5. Initialize CLI: `prompt-library-cli`
50+
2. Install dependencies: `npm install`
51+
3. Build and install CLI: `npm run build && npm install -g .`
52+
4. Initialize CLI: `prompt-library-cli`
53+
5. Set up Anthropic API key
5454

5555
Detailed setup instructions in [Getting Started](#-getting-started).
5656

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
"build": "tsc",
1313
"dev": "ts-node src/cli/index.ts",
1414
"format": "npm run lint:fix && npm run prettify",
15-
"generate-metadata": "ts-node src/app/core/generate_metadata.ts",
1615
"lint": "eslint 'src/**/*.ts'",
1716
"lint:fix": "npm run lint -- --fix",
1817
"prettify": "prettier --write 'src/**/*.ts'",
@@ -22,6 +21,7 @@
2221
"toc": "doctoc README.md --github --notitle",
2322
"type-check": "tsc --noEmit",
2423
"update": "ncu -i",
24+
"update-metadata": "ts-node src/app/core/update_metadata.ts",
2525
"update-views": "ts-node src/app/core/update_views.ts",
2626
"validate-yaml": "yamllint '**/*.yml'"
2727
},

src/app/core/generate_metadata.ts renamed to src/app/core/update_metadata.ts

Lines changed: 70 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from '../../shared/utils/file_operations';
1818
import logger from '../../shared/utils/logger';
1919
import { appConfig } from '../config/app.config';
20-
import { processMetadataGeneration } from '../utils/prompt_operations';
20+
import { processMetadataGeneration } from '../utils/analyzer_operations';
2121
import { dumpYamlContent, sanitizeYamlContent } from '../utils/yaml_operations';
2222

2323
export async function generateMetadata(promptContent: string): Promise<Metadata> {
@@ -50,18 +50,18 @@ export async function shouldUpdateMetadata(promptFile: string, metadataFile: str
5050
const metadataContent = await readFileContent(metadataFile);
5151
const storedHashLine = metadataContent.split('\n').find((line) => line.trim().startsWith('content_hash:'));
5252

53-
if (storedHashLine) {
54-
const storedHash = storedHashLine.split(':')[1].trim();
55-
56-
if (promptHash !== storedHash) {
57-
logger.info(`Content hash mismatch for ${promptFile}. Update needed.`);
58-
return [true, promptHash];
59-
}
60-
} else {
53+
if (!storedHashLine) {
6154
logger.info(`No content hash found in ${metadataFile}. Update needed.`);
6255
return [true, promptHash];
6356
}
6457

58+
const storedHash = storedHashLine.split(':')[1].trim();
59+
60+
if (promptHash !== storedHash) {
61+
logger.info(`Content hash mismatch for ${promptFile}. Update needed.`);
62+
return [true, promptHash];
63+
}
64+
6565
logger.info(`Content hash match for ${promptFile}. No update needed.`);
6666
return [false, promptHash];
6767
} catch (error) {
@@ -74,17 +74,11 @@ export async function updateMetadataHash(metadataFile: string, newHash: string):
7474
try {
7575
const content = await readFileContent(metadataFile);
7676
const lines = content.split('\n');
77-
let hashUpdated = false;
78-
79-
for (let i = 0; i < lines.length; i++) {
80-
if (lines[i].trim().startsWith('content_hash:')) {
81-
lines[i] = `content_hash: ${newHash}`;
82-
hashUpdated = true;
83-
break;
84-
}
85-
}
77+
const hashIndex = lines.findIndex((line) => line.trim().startsWith('content_hash:'));
8678

87-
if (!hashUpdated) {
79+
if (hashIndex !== -1) {
80+
lines[hashIndex] = `content_hash: ${newHash}`;
81+
} else {
8882
lines.push(`content_hash: ${newHash}`);
8983
}
9084

@@ -98,27 +92,39 @@ export async function updateMetadataHash(metadataFile: string, newHash: string):
9892

9993
export async function updatePromptMetadata(): Promise<void> {
10094
logger.info('Starting update_prompt_metadata process');
101-
await processMainPrompt(appConfig.PROMPTS_DIR);
102-
await processPromptDirectories(appConfig.PROMPTS_DIR);
103-
logger.info('update_prompt_metadata process completed');
95+
96+
try {
97+
await processMainPrompt(appConfig.PROMPTS_DIR);
98+
await processPromptDirectories(appConfig.PROMPTS_DIR);
99+
logger.info('update_prompt_metadata process completed');
100+
} catch (error) {
101+
logger.error('Error in updatePromptMetadata:', error);
102+
throw error;
103+
}
104104
}
105105

106106
async function processMainPrompt(promptsDir: string): Promise<void> {
107107
const mainPromptFile = path.join(promptsDir, commonConfig.PROMPT_FILE_NAME);
108108

109109
if (await fileExists(mainPromptFile)) {
110110
logger.info('Processing main prompt.md file');
111-
const promptContent = await readFileContent(mainPromptFile);
112-
const metadata = await generateMetadata(promptContent);
113-
const newDirName = metadata.directory;
114-
const newDirPath = path.join(promptsDir, newDirName);
115-
await createDirectory(newDirPath);
116-
const newPromptFile = path.join(newDirPath, commonConfig.PROMPT_FILE_NAME);
117-
await renameFile(mainPromptFile, newPromptFile);
118-
const metadataPath = path.join(newDirPath, commonConfig.METADATA_FILE_NAME);
119-
await writeFileContent(metadataPath, sanitizeYamlContent(dumpYamlContent(metadata)));
120-
const newHash = crypto.createHash('md5').update(promptContent).digest('hex');
121-
await updateMetadataHash(metadataPath, newHash);
111+
112+
try {
113+
const promptContent = await readFileContent(mainPromptFile);
114+
const metadata = await generateMetadata(promptContent);
115+
const newDirName = metadata.directory;
116+
const newDirPath = path.join(promptsDir, newDirName);
117+
await createDirectory(newDirPath);
118+
const newPromptFile = path.join(newDirPath, commonConfig.PROMPT_FILE_NAME);
119+
await renameFile(mainPromptFile, newPromptFile);
120+
const metadataPath = path.join(newDirPath, commonConfig.METADATA_FILE_NAME);
121+
await writeFileContent(metadataPath, sanitizeYamlContent(dumpYamlContent(metadata)));
122+
const newHash = crypto.createHash('md5').update(promptContent).digest('hex');
123+
await updateMetadataHash(metadataPath, newHash);
124+
} catch (error) {
125+
logger.error('Error processing main prompt:', error);
126+
throw error;
127+
}
122128
}
123129
}
124130

@@ -150,13 +156,12 @@ async function processPromptFile(
150156
promptsDir: string,
151157
item: string
152158
): Promise<void> {
153-
const [shouldUpdate, newHash] = await shouldUpdateMetadata(promptFile, metadataFile);
154-
155-
if (shouldUpdate) {
156-
logger.info(`Updating metadata for ${item}`);
157-
const promptContent = await readFileContent(promptFile);
159+
try {
160+
const [shouldUpdate, newHash] = await shouldUpdateMetadata(promptFile, metadataFile);
158161

159-
try {
162+
if (shouldUpdate) {
163+
logger.info(`Updating metadata for ${item}`);
164+
const promptContent = await readFileContent(promptFile);
160165
const metadata = await generateMetadata(promptContent);
161166

162167
if (!metadata || Object.keys(metadata).length === 0) {
@@ -174,11 +179,12 @@ async function processPromptFile(
174179
const metadataPath = path.join(currentItemPath, commonConfig.METADATA_FILE_NAME);
175180
await writeFileContent(metadataPath, sanitizeYamlContent(dumpYamlContent(metadata)));
176181
await updateMetadataHash(metadataPath, newHash);
177-
} catch (error) {
178-
logger.error(`Error processing ${item}:`, error);
182+
} else {
183+
logger.info(`Metadata for ${item} is up to date`);
179184
}
180-
} else {
181-
logger.info(`Metadata for ${item} is up to date`);
185+
} catch (error) {
186+
logger.error(`Error processing ${item}:`, error);
187+
throw error;
182188
}
183189
}
184190

@@ -191,22 +197,27 @@ async function updatePromptDirectory(
191197
const newDirPath = path.join(promptsDir, newDirName);
192198
logger.info(`Renaming directory from ${oldDirName} to ${newDirName}`);
193199

194-
if (await fileExists(newDirPath)) {
195-
logger.warn(`Directory ${newDirName} already exists. Updating contents.`);
196-
const files = await readDirectory(currentItemPath);
197-
await Promise.all(
198-
files.map(async (file) => {
199-
const src = path.join(currentItemPath, file);
200-
const dst = path.join(newDirPath, file);
201-
202-
if (await isFile(src)) {
203-
await copyFile(src, dst);
204-
}
205-
})
206-
);
207-
await removeDirectory(currentItemPath);
208-
} else {
209-
await renameFile(currentItemPath, newDirPath);
200+
try {
201+
if (await fileExists(newDirPath)) {
202+
logger.warn(`Directory ${newDirName} already exists. Updating contents.`);
203+
const files = await readDirectory(currentItemPath);
204+
await Promise.all(
205+
files.map(async (file) => {
206+
const src = path.join(currentItemPath, file);
207+
const dst = path.join(newDirPath, file);
208+
209+
if (await isFile(src)) {
210+
await copyFile(src, dst);
211+
}
212+
})
213+
);
214+
await removeDirectory(currentItemPath);
215+
} else {
216+
await renameFile(currentItemPath, newDirPath);
217+
}
218+
} catch (error) {
219+
logger.error(`Error updating prompt directory from ${oldDirName} to ${newDirName}:`, error);
220+
throw error;
210221
}
211222
}
212223

src/app/core/update_views.ts

Lines changed: 66 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,6 @@ import { formatTitleCase } from '../../shared/utils/string_formatter';
1010
import { appConfig } from '../config/app.config';
1111
import { parseYamlContent } from '../utils/yaml_operations';
1212

13-
/**
14-
* Processes a single prompt directory.
15-
* @param {string} promptDir - The name of the prompt directory.
16-
* @param {Record<string, CategoryItem[]>} categories - The object to store categorized prompts.
17-
*/
1813
async function processPromptDirectory(promptDir: string, categories: Record<string, CategoryItem[]>): Promise<void> {
1914
const promptPath = path.join(appConfig.PROMPTS_DIR, promptDir);
2015

@@ -34,6 +29,15 @@ async function processPromptDirectory(promptDir: string, categories: Record<stri
3429
const metadata = parseYamlContent(metadataContent) as Metadata;
3530
logger.debug(`Read metadata from ${metadataFile}`);
3631

32+
await generateViewFile(promptPath, metadata, promptContent);
33+
addPromptToCategories(categories, promptDir, metadata);
34+
} catch (error) {
35+
logger.error(`Error processing ${promptDir}:`, error);
36+
}
37+
}
38+
39+
async function generateViewFile(promptPath: string, metadata: Metadata, promptContent: string): Promise<void> {
40+
try {
3741
const viewContent = nunjucks.render(appConfig.VIEW_TEMPLATE_NAME, {
3842
metadata,
3943
prompt_content: promptContent,
@@ -44,53 +48,72 @@ async function processPromptDirectory(promptDir: string, categories: Record<stri
4448
const viewPath = path.join(promptPath, appConfig.VIEW_FILE_NAME);
4549
await writeFileContent(viewPath, viewContent);
4650
logger.info(`Wrote view content to ${viewPath}`);
47-
48-
const primaryCategory = metadata.primary_category || appConfig.DEFAULT_CATEGORY;
49-
categories[primaryCategory] = categories[primaryCategory] || [];
50-
categories[primaryCategory].push({
51-
id: promptDir,
52-
title: metadata.title || 'Untitled',
53-
primary_category: primaryCategory,
54-
description: metadata.one_line_description || 'No description',
55-
path: `${appConfig.PROMPTS_DIR}/${promptDir}/${appConfig.VIEW_FILE_NAME}`,
56-
subcategories: metadata.subcategories || []
57-
});
58-
logger.debug(`Added prompt to category: ${primaryCategory}`);
5951
} catch (error) {
60-
logger.error(`Error processing ${promptDir}:`, error);
52+
logger.error(`Error generating view file for ${promptPath}:`, error);
53+
throw error;
6154
}
6255
}
6356

64-
/**
65-
* Updates views for all prompts and generates the README.
66-
* This function processes all prompt directories, generates view files,
67-
* and updates the main README with categorized prompts.
68-
*/
57+
function addPromptToCategories(
58+
categories: Record<string, CategoryItem[]>,
59+
promptDir: string,
60+
metadata: Metadata
61+
): void {
62+
const primaryCategory = metadata.primary_category || appConfig.DEFAULT_CATEGORY;
63+
categories[primaryCategory] = categories[primaryCategory] || [];
64+
categories[primaryCategory].push({
65+
id: promptDir,
66+
title: metadata.title || 'Untitled',
67+
primary_category: primaryCategory,
68+
description: metadata.one_line_description || 'No description',
69+
path: `${appConfig.PROMPTS_DIR}/${promptDir}/${appConfig.VIEW_FILE_NAME}`,
70+
subcategories: metadata.subcategories || []
71+
});
72+
logger.debug(`Added prompt to category: ${primaryCategory}`);
73+
}
74+
6975
export async function updateViews(): Promise<void> {
7076
logger.info('Starting update_views process');
7177
const categories: Record<string, CategoryItem[]> = {};
72-
logger.info('Setting up Nunjucks environment');
73-
nunjucks.configure(appConfig.TEMPLATES_DIR, { autoescape: false });
74-
logger.info('Nunjucks environment configured');
75-
logger.info(`Iterating through prompts in ${appConfig.PROMPTS_DIR}`);
76-
const promptDirs = await readDirectory(appConfig.PROMPTS_DIR);
77-
await Promise.all(promptDirs.map((promptDir) => processPromptDirectory(promptDir, categories)));
78-
const sortedCategories = Object.fromEntries(
79-
Object.entries(categories)
80-
.filter(([, v]) => v.length > 0)
81-
.sort(([a], [b]) => a.localeCompare(b))
82-
);
83-
logger.info('Generating README content');
84-
const readmeContent = nunjucks.render(appConfig.README_TEMPLATE_NAME, {
85-
categories: sortedCategories,
86-
format_string: formatTitleCase
87-
});
88-
await writeFileContent(appConfig.README_PATH, readmeContent.replace(/\n{3,}/g, '\n\n').trim() + '\n');
89-
logger.info(`Wrote README content to ${appConfig.README_PATH}`);
90-
logger.info('update_views process completed');
78+
79+
try {
80+
logger.info('Setting up Nunjucks environment');
81+
nunjucks.configure(appConfig.TEMPLATES_DIR, { autoescape: false });
82+
logger.info('Nunjucks environment configured');
83+
84+
logger.info(`Iterating through prompts in ${appConfig.PROMPTS_DIR}`);
85+
const promptDirs = await readDirectory(appConfig.PROMPTS_DIR);
86+
await Promise.all(promptDirs.map((promptDir) => processPromptDirectory(promptDir, categories)));
87+
88+
await generateReadme(categories);
89+
logger.info('update_views process completed');
90+
} catch (error) {
91+
logger.error('Error in updateViews:', error);
92+
throw error;
93+
}
94+
}
95+
96+
async function generateReadme(categories: Record<string, CategoryItem[]>): Promise<void> {
97+
try {
98+
const sortedCategories = Object.fromEntries(
99+
Object.entries(categories)
100+
.filter(([, v]) => v.length > 0)
101+
.sort(([a], [b]) => a.localeCompare(b))
102+
);
103+
logger.info('Generating README content');
104+
const readmeContent = nunjucks.render(appConfig.README_TEMPLATE_NAME, {
105+
categories: sortedCategories,
106+
format_string: formatTitleCase
107+
});
108+
const formattedContent = readmeContent.replace(/\n{3,}/g, '\n\n').trim() + '\n';
109+
await writeFileContent(appConfig.README_PATH, formattedContent);
110+
logger.info(`Wrote README content to ${appConfig.README_PATH}`);
111+
} catch (error) {
112+
logger.error('Error generating README:', error);
113+
throw error;
114+
}
91115
}
92116

93-
// Main execution
94117
if (require.main === module) {
95118
updateViews().catch((error) => {
96119
logger.error('Error in main execution:', error);

src/app/templates/main_readme.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ This project serves as a starting point for creating your own AI toolkit, demons
3030
## ⚡ Quick Start
3131

3232
1. Fork and clone the repository
33-
2. Set up Anthropic API key (GitHub Actions and CLI)
34-
3. Install dependencies: `npm install`
35-
4. Build and install CLI: `npm run build && npm install -g .`
36-
5. Initialize CLI: `prompt-library-cli`
33+
2. Install dependencies: `npm install`
34+
3. Build and install CLI: `npm run build && npm install -g .`
35+
4. Initialize CLI: `prompt-library-cli`
36+
5. Set up Anthropic API key
3737

3838
Detailed setup instructions in [Getting Started](#-getting-started).
3939

0 commit comments

Comments
 (0)