Complete Collections feature implementation with validation, tooling, and documentation
Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>
This commit is contained in:
parent
406b613cf8
commit
4476bf37cb
4
.github/workflows/validate-readme.yml
vendored
4
.github/workflows/validate-readme.yml
vendored
@ -7,6 +7,7 @@ on:
|
||||
- "instructions/**"
|
||||
- "prompts/**"
|
||||
- "chatmodes/**"
|
||||
- "collections/**"
|
||||
- "*.js"
|
||||
|
||||
jobs:
|
||||
@ -26,6 +27,9 @@ jobs:
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Validate collections
|
||||
run: node validate-collections.js
|
||||
|
||||
- name: Update README.md
|
||||
run: node update-readme.js
|
||||
|
||||
|
||||
25
.vscode/tasks.json
vendored
25
.vscode/tasks.json
vendored
@ -11,6 +11,31 @@
|
||||
"isDefault": true
|
||||
},
|
||||
"detail": "Generates the README.md file using update-readme.js script."
|
||||
},
|
||||
{
|
||||
"label": "validate-collections",
|
||||
"type": "shell",
|
||||
"command": "node validate-collections.js",
|
||||
"problemMatcher": [],
|
||||
"group": "build",
|
||||
"detail": "Validates all collection manifest files."
|
||||
},
|
||||
{
|
||||
"label": "create-collection",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/create-collection.js",
|
||||
"args": ["${input:collectionId}"],
|
||||
"problemMatcher": [],
|
||||
"group": "build",
|
||||
"detail": "Creates a new collection manifest template."
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "collectionId",
|
||||
"description": "Collection ID (lowercase, hyphen-separated)",
|
||||
"default": "my-collection",
|
||||
"type": "promptString"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -100,6 +100,48 @@ You are an expert [domain/role] with deep knowledge in [specific areas].
|
||||
- [Best practices to follow]
|
||||
```
|
||||
|
||||
### Adding Collections
|
||||
|
||||
Collections group related prompts, instructions, and chat modes around specific themes or workflows, making it easier for users to discover and adopt comprehensive toolkits.
|
||||
|
||||
1. **Create your collection manifest**: Add a new `.collection.yml` file in the `collections/` directory
|
||||
2. **Follow the naming convention**: Use descriptive, lowercase filenames with hyphens (e.g., `python-web-development.collection.yml`)
|
||||
3. **Reference existing items**: Collections should only reference files that already exist in the repository
|
||||
4. **Test your collection**: Verify all referenced files exist and work well together
|
||||
|
||||
#### Creating a collection:
|
||||
```bash
|
||||
# Using the creation script
|
||||
node create-collection.js my-collection-id
|
||||
|
||||
# Or using VS Code Task: Ctrl+Shift+P > "Tasks: Run Task" > "create-collection"
|
||||
```
|
||||
|
||||
#### Example collection format:
|
||||
```yaml
|
||||
id: my-collection-id
|
||||
name: My Collection Name
|
||||
description: A brief description of what this collection provides and who should use it.
|
||||
tags: [tag1, tag2, tag3] # Optional discovery tags
|
||||
items:
|
||||
- path: prompts/my-prompt.prompt.md
|
||||
kind: prompt
|
||||
- path: instructions/my-instructions.instructions.md
|
||||
kind: instruction
|
||||
- path: chatmodes/my-chatmode.chatmode.md
|
||||
kind: chat-mode
|
||||
display:
|
||||
ordering: alpha # or "manual" to preserve order above
|
||||
show_badge: false # set to true to show collection badge
|
||||
```
|
||||
|
||||
#### Collection Guidelines:
|
||||
- **Focus on workflows**: Group items that work together for specific use cases
|
||||
- **Reasonable size**: Typically 3-10 items work well
|
||||
- **Test combinations**: Ensure the items complement each other effectively
|
||||
- **Clear purpose**: The collection should solve a specific problem or workflow
|
||||
- **Validate before submitting**: Run `node validate-collections.js` to ensure your manifest is valid
|
||||
|
||||
## Submitting Your Contribution
|
||||
|
||||
1. **Fork this repository**
|
||||
|
||||
81
collections/TEMPLATE.md
Normal file
81
collections/TEMPLATE.md
Normal file
@ -0,0 +1,81 @@
|
||||
# Collections Template
|
||||
|
||||
Use this template to create a new collection of related prompts, instructions, and chat modes.
|
||||
|
||||
## Basic Template
|
||||
|
||||
```yaml
|
||||
id: my-collection-id
|
||||
name: My Collection Name
|
||||
description: A brief description of what this collection provides and who should use it.
|
||||
tags: [tag1, tag2, tag3] # Optional discovery tags
|
||||
items:
|
||||
- path: prompts/my-prompt.prompt.md
|
||||
kind: prompt
|
||||
- path: instructions/my-instructions.instructions.md
|
||||
kind: instruction
|
||||
- path: chatmodes/my-chatmode.chatmode.md
|
||||
kind: chat-mode
|
||||
display:
|
||||
ordering: alpha # or "manual" to preserve order above
|
||||
show_badge: false # set to true to show collection badge
|
||||
```
|
||||
|
||||
## Field Descriptions
|
||||
|
||||
- **id**: Unique identifier using lowercase letters, numbers, and hyphens only
|
||||
- **name**: Display name for the collection
|
||||
- **description**: Brief explanation of the collection's purpose (1-500 characters)
|
||||
- **tags**: Optional array of discovery tags (max 10, each 1-30 characters)
|
||||
- **items**: Array of items in the collection (1-50 items)
|
||||
- **path**: Relative path from repository root to the file
|
||||
- **kind**: Must be `prompt`, `instruction`, or `chat-mode`
|
||||
- **display**: Optional display settings
|
||||
- **ordering**: `alpha` (alphabetical) or `manual` (preserve order)
|
||||
- **show_badge**: Show collection badge on items (true/false)
|
||||
|
||||
## Creating a New Collection
|
||||
|
||||
### Using VS Code Tasks
|
||||
1. Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac)
|
||||
2. Type "Tasks: Run Task"
|
||||
3. Select "create-collection"
|
||||
4. Enter your collection ID when prompted
|
||||
|
||||
### Using Command Line
|
||||
```bash
|
||||
node create-collection.js my-collection-id
|
||||
```
|
||||
|
||||
### Manual Creation
|
||||
1. Create `collections/my-collection-id.collection.yml`
|
||||
2. Use the template above as starting point
|
||||
3. Add your items and customize settings
|
||||
4. Run `node validate-collections.js` to validate
|
||||
5. Run `node update-readme.js` to generate documentation
|
||||
|
||||
## Validation
|
||||
|
||||
Collections are automatically validated to ensure:
|
||||
- Required fields are present and valid
|
||||
- File paths exist and match the item kind
|
||||
- IDs are unique across collections
|
||||
- Tags and display settings follow the schema
|
||||
|
||||
Run validation manually:
|
||||
```bash
|
||||
node validate-collections.js
|
||||
```
|
||||
|
||||
## File Organization
|
||||
|
||||
Collections don't require reorganizing existing files. Items can be located anywhere in the repository as long as the paths are correct in the manifest.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Meaningful Collections**: Group items that work well together for a specific workflow or use case
|
||||
2. **Clear Naming**: Use descriptive names and IDs that reflect the collection's purpose
|
||||
3. **Good Descriptions**: Explain who should use the collection and what benefit it provides
|
||||
4. **Relevant Tags**: Add discovery tags that help users find related collections
|
||||
5. **Reasonable Size**: Keep collections focused - typically 3-10 items work well
|
||||
6. **Test Items**: Ensure all referenced files exist and are functional before adding to a collection
|
||||
76
create-collection.js
Normal file
76
create-collection.js
Normal file
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
function createCollectionTemplate(collectionId) {
|
||||
if (!collectionId) {
|
||||
console.error("Collection ID is required");
|
||||
console.log("Usage: node create-collection.js <collection-id>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate collection ID format
|
||||
if (!/^[a-z0-9-]+$/.test(collectionId)) {
|
||||
console.error("Collection ID must contain only lowercase letters, numbers, and hyphens");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const collectionsDir = path.join(__dirname, "collections");
|
||||
const filePath = path.join(collectionsDir, `${collectionId}.collection.yml`);
|
||||
|
||||
// Check if file already exists
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.error(`Collection ${collectionId} already exists at ${filePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Ensure collections directory exists
|
||||
if (!fs.existsSync(collectionsDir)) {
|
||||
fs.mkdirSync(collectionsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Create a friendly name from the ID
|
||||
const friendlyName = collectionId
|
||||
.split("-")
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
|
||||
// Template content
|
||||
const template = `id: ${collectionId}
|
||||
name: ${friendlyName}
|
||||
description: A collection of related prompts, instructions, and chat modes for ${friendlyName.toLowerCase()}.
|
||||
tags: [${collectionId.split("-").slice(0, 3).join(", ")}] # Add relevant tags
|
||||
items:
|
||||
# Add your collection items here
|
||||
# Example:
|
||||
# - path: prompts/example.prompt.md
|
||||
# kind: prompt
|
||||
# - path: instructions/example.instructions.md
|
||||
# kind: instruction
|
||||
# - path: chatmodes/example.chatmode.md
|
||||
# kind: chat-mode
|
||||
display:
|
||||
ordering: alpha # or "manual" to preserve the order above
|
||||
show_badge: false # set to true to show collection badge on items
|
||||
`;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(filePath, template);
|
||||
console.log(`✅ Created collection template: ${filePath}`);
|
||||
console.log("\nNext steps:");
|
||||
console.log("1. Edit the collection manifest to add your items");
|
||||
console.log("2. Update the name, description, and tags as needed");
|
||||
console.log("3. Run 'node validate-collections.js' to validate");
|
||||
console.log("4. Run 'node update-readme.js' to generate documentation");
|
||||
console.log("\nCollection template contents:");
|
||||
console.log(template);
|
||||
} catch (error) {
|
||||
console.error(`Error creating collection template: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Get collection ID from command line arguments
|
||||
const collectionId = process.argv[2];
|
||||
createCollectionTemplate(collectionId);
|
||||
54
instructions/collections.instructions.md
Normal file
54
instructions/collections.instructions.md
Normal file
@ -0,0 +1,54 @@
|
||||
---
|
||||
description: 'Guidelines for creating and managing awesome-copilot collections'
|
||||
applyTo: 'collections/*.collection.yml'
|
||||
---
|
||||
|
||||
# Collections Development
|
||||
|
||||
## Collection Instructions
|
||||
|
||||
When working with collections in the awesome-copilot repository:
|
||||
|
||||
- Always validate collections using `node validate-collections.js` before committing
|
||||
- Follow the established YAML schema for collection manifests
|
||||
- Reference only existing files in the repository
|
||||
- Use descriptive collection IDs with lowercase letters, numbers, and hyphens
|
||||
- Keep collections focused on specific workflows or themes
|
||||
- Test that all referenced items work well together
|
||||
|
||||
## Collection Structure
|
||||
|
||||
- **Required fields**: id, name, description, items
|
||||
- **Optional fields**: tags, display
|
||||
- **Item requirements**: path must exist, kind must match file extension
|
||||
- **Display options**: ordering (alpha/manual), show_badge (true/false)
|
||||
|
||||
## Validation Rules
|
||||
|
||||
- Collection IDs must be unique across all collections
|
||||
- File paths must exist and match the item kind
|
||||
- Tags must use lowercase letters, numbers, and hyphens only
|
||||
- Collections must contain 1-50 items
|
||||
- Descriptions must be 1-500 characters
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Group 3-10 related items for optimal usability
|
||||
- Use clear, descriptive names and descriptions
|
||||
- Add relevant tags for discoverability
|
||||
- Test the complete workflow the collection enables
|
||||
- Ensure items complement each other effectively
|
||||
|
||||
## File Organization
|
||||
|
||||
- Collections don't require file reorganization
|
||||
- Items can be located anywhere in the repository
|
||||
- Use relative paths from repository root
|
||||
- Maintain existing directory structure (prompts/, instructions/, chatmodes/)
|
||||
|
||||
## Generation Process
|
||||
|
||||
- Collections automatically generate README files via `update-readme.js`
|
||||
- Individual collection pages are created in collections/ directory
|
||||
- Main collections overview is generated as README.collections.md
|
||||
- VS Code install badges are automatically created for each item
|
||||
332
validate-collections.js
Normal file
332
validate-collections.js
Normal file
@ -0,0 +1,332 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Simple YAML parser (same as in update-readme.js)
|
||||
function safeFileOperation(operation, filePath, defaultValue = null) {
|
||||
try {
|
||||
return operation();
|
||||
} catch (error) {
|
||||
console.error(`Error processing file ${filePath}: ${error.message}`);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
function parseCollectionYaml(filePath) {
|
||||
return safeFileOperation(
|
||||
() => {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
const lines = content.split("\n");
|
||||
const result = {};
|
||||
let currentKey = null;
|
||||
let currentArray = null;
|
||||
let currentObject = null;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
|
||||
const leadingSpaces = line.length - line.trimLeft().length;
|
||||
|
||||
// Handle array items starting with -
|
||||
if (trimmed.startsWith("- ")) {
|
||||
if (currentKey === "items") {
|
||||
if (!currentArray) {
|
||||
currentArray = [];
|
||||
result[currentKey] = currentArray;
|
||||
}
|
||||
|
||||
// Parse item object
|
||||
const item = {};
|
||||
currentArray.push(item);
|
||||
currentObject = item;
|
||||
|
||||
// Handle inline properties on same line as -
|
||||
const restOfLine = trimmed.substring(2).trim();
|
||||
if (restOfLine) {
|
||||
const colonIndex = restOfLine.indexOf(":");
|
||||
if (colonIndex > -1) {
|
||||
const key = restOfLine.substring(0, colonIndex).trim();
|
||||
const value = restOfLine.substring(colonIndex + 1).trim();
|
||||
item[key] = value;
|
||||
}
|
||||
}
|
||||
} else if (currentKey === "tags") {
|
||||
if (!currentArray) {
|
||||
currentArray = [];
|
||||
result[currentKey] = currentArray;
|
||||
}
|
||||
const value = trimmed.substring(2).trim();
|
||||
currentArray.push(value);
|
||||
}
|
||||
}
|
||||
// Handle key-value pairs
|
||||
else if (trimmed.includes(":")) {
|
||||
const colonIndex = trimmed.indexOf(":");
|
||||
const key = trimmed.substring(0, colonIndex).trim();
|
||||
let value = trimmed.substring(colonIndex + 1).trim();
|
||||
|
||||
if (leadingSpaces === 0) {
|
||||
// Top-level property
|
||||
currentKey = key;
|
||||
currentArray = null;
|
||||
currentObject = null;
|
||||
|
||||
if (value) {
|
||||
// Handle array format [item1, item2, item3]
|
||||
if (value.startsWith("[") && value.endsWith("]")) {
|
||||
const arrayContent = value.slice(1, -1);
|
||||
if (arrayContent.trim()) {
|
||||
result[key] = arrayContent.split(",").map(item => item.trim());
|
||||
} else {
|
||||
result[key] = [];
|
||||
}
|
||||
currentKey = null; // Reset since we handled the array
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
} else if (key === "items" || key === "tags") {
|
||||
// Will be populated by array items
|
||||
result[key] = [];
|
||||
currentArray = result[key];
|
||||
} else if (key === "display") {
|
||||
result[key] = {};
|
||||
currentObject = result[key];
|
||||
}
|
||||
} else if (currentObject && leadingSpaces > 0) {
|
||||
// Property of current object (e.g., display properties)
|
||||
currentObject[key] = value === "true" ? true : value === "false" ? false : value;
|
||||
} else if (currentArray && currentObject && leadingSpaces > leadingSpaces) {
|
||||
// Property of array item object
|
||||
currentObject[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
filePath,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
// Validation functions
|
||||
function validateCollectionId(id) {
|
||||
if (!id || typeof id !== "string") {
|
||||
return "ID is required and must be a string";
|
||||
}
|
||||
if (!/^[a-z0-9-]+$/.test(id)) {
|
||||
return "ID must contain only lowercase letters, numbers, and hyphens";
|
||||
}
|
||||
if (id.length < 1 || id.length > 50) {
|
||||
return "ID must be between 1 and 50 characters";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateCollectionName(name) {
|
||||
if (!name || typeof name !== "string") {
|
||||
return "Name is required and must be a string";
|
||||
}
|
||||
if (name.length < 1 || name.length > 100) {
|
||||
return "Name must be between 1 and 100 characters";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateCollectionDescription(description) {
|
||||
if (!description || typeof description !== "string") {
|
||||
return "Description is required and must be a string";
|
||||
}
|
||||
if (description.length < 1 || description.length > 500) {
|
||||
return "Description must be between 1 and 500 characters";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateCollectionTags(tags) {
|
||||
if (tags && !Array.isArray(tags)) {
|
||||
return "Tags must be an array";
|
||||
}
|
||||
if (tags && tags.length > 10) {
|
||||
return "Maximum 10 tags allowed";
|
||||
}
|
||||
if (tags) {
|
||||
for (const tag of tags) {
|
||||
if (typeof tag !== "string") {
|
||||
return "All tags must be strings";
|
||||
}
|
||||
if (!/^[a-z0-9-]+$/.test(tag)) {
|
||||
return `Tag "${tag}" must contain only lowercase letters, numbers, and hyphens`;
|
||||
}
|
||||
if (tag.length < 1 || tag.length > 30) {
|
||||
return `Tag "${tag}" must be between 1 and 30 characters`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateCollectionItems(items) {
|
||||
if (!items || !Array.isArray(items)) {
|
||||
return "Items is required and must be an array";
|
||||
}
|
||||
if (items.length < 1) {
|
||||
return "At least one item is required";
|
||||
}
|
||||
if (items.length > 50) {
|
||||
return "Maximum 50 items allowed";
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (!item || typeof item !== "object") {
|
||||
return `Item ${i + 1} must be an object`;
|
||||
}
|
||||
if (!item.path || typeof item.path !== "string") {
|
||||
return `Item ${i + 1} must have a path string`;
|
||||
}
|
||||
if (!item.kind || typeof item.kind !== "string") {
|
||||
return `Item ${i + 1} must have a kind string`;
|
||||
}
|
||||
if (!["prompt", "instruction", "chat-mode"].includes(item.kind)) {
|
||||
return `Item ${i + 1} kind must be one of: prompt, instruction, chat-mode`;
|
||||
}
|
||||
|
||||
// Validate file path exists
|
||||
const filePath = path.join(__dirname, item.path);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return `Item ${i + 1} file does not exist: ${item.path}`;
|
||||
}
|
||||
|
||||
// Validate path pattern matches kind
|
||||
if (item.kind === "prompt" && !item.path.endsWith(".prompt.md")) {
|
||||
return `Item ${i + 1} kind is "prompt" but path doesn't end with .prompt.md`;
|
||||
}
|
||||
if (item.kind === "instruction" && !item.path.endsWith(".instructions.md")) {
|
||||
return `Item ${i + 1} kind is "instruction" but path doesn't end with .instructions.md`;
|
||||
}
|
||||
if (item.kind === "chat-mode" && !item.path.endsWith(".chatmode.md")) {
|
||||
return `Item ${i + 1} kind is "chat-mode" but path doesn't end with .chatmode.md`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateCollectionDisplay(display) {
|
||||
if (display && typeof display !== "object") {
|
||||
return "Display must be an object";
|
||||
}
|
||||
if (display) {
|
||||
if (display.ordering && !["manual", "alpha"].includes(display.ordering)) {
|
||||
return "Display ordering must be 'manual' or 'alpha'";
|
||||
}
|
||||
if (display.show_badge && typeof display.show_badge !== "boolean") {
|
||||
return "Display show_badge must be boolean";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateCollectionManifest(collection, filePath) {
|
||||
const errors = [];
|
||||
|
||||
const idError = validateCollectionId(collection.id);
|
||||
if (idError) errors.push(`ID: ${idError}`);
|
||||
|
||||
const nameError = validateCollectionName(collection.name);
|
||||
if (nameError) errors.push(`Name: ${nameError}`);
|
||||
|
||||
const descError = validateCollectionDescription(collection.description);
|
||||
if (descError) errors.push(`Description: ${descError}`);
|
||||
|
||||
const tagsError = validateCollectionTags(collection.tags);
|
||||
if (tagsError) errors.push(`Tags: ${tagsError}`);
|
||||
|
||||
const itemsError = validateCollectionItems(collection.items);
|
||||
if (itemsError) errors.push(`Items: ${itemsError}`);
|
||||
|
||||
const displayError = validateCollectionDisplay(collection.display);
|
||||
if (displayError) errors.push(`Display: ${displayError}`);
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
// Main validation function
|
||||
function validateCollections() {
|
||||
const collectionsDir = path.join(__dirname, "collections");
|
||||
|
||||
if (!fs.existsSync(collectionsDir)) {
|
||||
console.log("No collections directory found - validation skipped");
|
||||
return true;
|
||||
}
|
||||
|
||||
const collectionFiles = fs
|
||||
.readdirSync(collectionsDir)
|
||||
.filter((file) => file.endsWith(".collection.yml"));
|
||||
|
||||
if (collectionFiles.length === 0) {
|
||||
console.log("No collection files found - validation skipped");
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(`Validating ${collectionFiles.length} collection files...`);
|
||||
|
||||
let hasErrors = false;
|
||||
const usedIds = new Set();
|
||||
|
||||
for (const file of collectionFiles) {
|
||||
const filePath = path.join(collectionsDir, file);
|
||||
console.log(`\nValidating ${file}...`);
|
||||
|
||||
const collection = parseCollectionYaml(filePath);
|
||||
if (!collection) {
|
||||
console.error(`❌ Failed to parse ${file}`);
|
||||
hasErrors = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate the collection structure
|
||||
const errors = validateCollectionManifest(collection, filePath);
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error(`❌ Validation errors in ${file}:`);
|
||||
errors.forEach(error => console.error(` - ${error}`));
|
||||
hasErrors = true;
|
||||
} else {
|
||||
console.log(`✅ ${file} is valid`);
|
||||
}
|
||||
|
||||
// Check for duplicate IDs
|
||||
if (collection.id) {
|
||||
if (usedIds.has(collection.id)) {
|
||||
console.error(`❌ Duplicate collection ID "${collection.id}" found in ${file}`);
|
||||
hasErrors = true;
|
||||
} else {
|
||||
usedIds.add(collection.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasErrors) {
|
||||
console.log(`\n✅ All ${collectionFiles.length} collections are valid`);
|
||||
}
|
||||
|
||||
return !hasErrors;
|
||||
}
|
||||
|
||||
// Run validation
|
||||
try {
|
||||
const isValid = validateCollections();
|
||||
if (!isValid) {
|
||||
console.error("\n❌ Collection validation failed");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("\n🎉 Collection validation passed");
|
||||
} catch (error) {
|
||||
console.error(`Error during validation: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user