diff --git a/package.json b/package.json index cc8aa1171..bf95dfc48 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "fix-redirects": "NODE_NO_WARNINGS=1 node --loader ts-node/esm utils/fix-redirects.ts", "link-checker": "NODE_NO_WARNINGS=1 node --loader ts-node/esm utils/link-checker.ts", "metadata-batch-cli": "NODE_NO_WARNINGS=1 node --loader ts-node/esm utils/metadata-batch-cli.ts", - "metadata-batch-cli:dry": "pnpm metadata-batch-cli --dry-run", + "metadata-batch-cli:dry": "NODE_NO_WARNINGS=1 node --loader ts-node/esm utils/metadata-batch-cli.ts --dry-run", "metadata-batch-cli:verbose": "pnpm metadata-batch-cli --verbose", "validate-metadata": "CHANGED_FILES=$(git diff --name-only HEAD) pnpm metadata-batch-cli:dry", "validate-pr-metadata": "NODE_NO_WARNINGS=1 node --loader ts-node/esm utils/metadata-manager.ts --pr", diff --git a/pages/connect/contribute.mdx b/pages/connect/contribute.mdx index a7cf78027..211af4798 100644 --- a/pages/connect/contribute.mdx +++ b/pages/connect/contribute.mdx @@ -1,7 +1,20 @@ --- title: Contribute -description: Documentation covering Docs Contribute, Stack Contribute, Style Guide in the Contribute section of the OP Stack ecosystem. +description: >- + Documentation covering Docs Contribute, Stack Contribute, Style Guide in the + Contribute section of the OP Stack ecosystem. lang: en-US +content_type: guide +topic: contribute +personas: + - app-developer + - protocol-developer + - chain-operator + - node-operator +categories: + - security-council + - blockspace-charters +is_imported_content: 'false' --- import { Card, Cards } from 'nextra/components' diff --git a/pages/connect/contribute/docs-contribute.mdx b/pages/connect/contribute/docs-contribute.mdx index d38b72bf9..db2147c1d 100644 --- a/pages/connect/contribute/docs-contribute.mdx +++ b/pages/connect/contribute/docs-contribute.mdx @@ -1,7 +1,18 @@ --- -title: Ways to contribute to Optimism Docs -lang: en-US +title: Contribute to Optimism Docs description: Learn about the different ways you can contribute to Optimism Docs. +lang: en-US +content_type: guide +topic: docs-contribute +personas: + - app-developer + - protocol-developer + - chain-operator + - node-operator +categories: + - security-council + - protocol +is_imported_content: 'false' --- import { Callout } from 'nextra/components' @@ -17,7 +28,6 @@ Optimism Docs (docs.optimism.io) is an open-source project, and we welcome your ## Ways to contribute -* [Work on a good first issue](https://github.com/ethereum-optimism/docs/labels/good%20first%20issue): make the greatest and most immediate impact with a quick-start or tutorial. These special issues are clearly labeled for new contributors to Optimism. * [Edit existing content](#how-to-work-on-docsoptimismio): make tweaks to existing content using the ✏️ **Edit this page on GitHub** or ❤️ **Share general feedback** links on the right-side of any page. * [Add or update an FAQ item](https://github.com/ethereum-optimism/docs/issues/new?assignees=\&labels=documentation%2Cfaq%2Ccommunity-request\&projects=\&template=suggest_faq_item.yaml\&title=Suggest+an+FAQ+item): add a new FAQ (question+answer set) to an [existing page](/stack/security/faq-sec-model), create a new FAQ page, or update an existing FAQ question/answer set. diff --git a/pages/connect/contribute/stack-contribute.mdx b/pages/connect/contribute/stack-contribute.mdx index b1ae51210..61f59fda0 100644 --- a/pages/connect/contribute/stack-contribute.mdx +++ b/pages/connect/contribute/stack-contribute.mdx @@ -1,7 +1,18 @@ --- title: Contribute to the OP Stack -lang: en-US description: Learn different ways you can contribute to the OP Stack. +lang: en-US +content_type: guide +topic: stack-contribute +personas: + - app-developer + - protocol-developer + - chain-operator + - node-operator +categories: + - security-council + - protocol +is_imported_content: 'false' --- import { Callout } from 'nextra/components' diff --git a/pages/connect/contribute/style-guide.mdx b/pages/connect/contribute/style-guide.mdx index 7c02e3a20..5489681c6 100644 --- a/pages/connect/contribute/style-guide.mdx +++ b/pages/connect/contribute/style-guide.mdx @@ -1,7 +1,20 @@ --- -title: Optimism Docs style guide +title: Docs style guide +description: >- + This guide explains how to write technical content for Optimism Docs using a + consistent voice, tone, and style. lang: en-US -description: This guide explains how to write technical content for Optimism Docs using a consistent voice, tone, and style. +content_type: guide +topic: style-guide +personas: + - app-developer + - protocol-developer + - chain-operator + - node-operator +categories: + - security-council + - protocol +is_imported_content: 'false' --- import { Callout } from 'nextra/components' diff --git a/pages/connect/resources.mdx b/pages/connect/resources.mdx index a4ae2c526..f79157f85 100644 --- a/pages/connect/resources.mdx +++ b/pages/connect/resources.mdx @@ -1,7 +1,20 @@ --- title: Resources -description: Documentation covering Glossary in the Resources section of the OP Stack ecosystem. +description: >- + Documentation covering Glossary in the Resources section of the OP Stack + ecosystem. lang: en-US +content_type: guide +topic: resources +personas: + - app-developer + - protocol-developer + - chain-operator + - node-operator +categories: + - security-council + - blockspace-charters +is_imported_content: 'false' --- import { Card, Cards } from 'nextra/components' diff --git a/pages/connect/resources/glossary.mdx b/pages/connect/resources/glossary.mdx index 5959596f9..b10bdf0a4 100644 --- a/pages/connect/resources/glossary.mdx +++ b/pages/connect/resources/glossary.mdx @@ -1,7 +1,21 @@ --- -title: Glossary +title: glossary +description: >- + Learn the meaning of important terminology used throughout the Optimism + Developer Documentation. lang: en-US -description: Learn the meaning of important terminology used throughout the Optimism Developer Documentation. +content_type: reference +topic: glossary +personas: + - app-developer + - protocol-developer + - chain-operator + - node-operator + - governance-participant +categories: + - security-council + - protocol +is_imported_content: 'false' --- import { Callout } from 'nextra/components' diff --git a/pages/get-started/interop.mdx b/pages/get-started/interop.mdx index 3a996050c..bb8768c04 100644 --- a/pages/get-started/interop.mdx +++ b/pages/get-started/interop.mdx @@ -1,7 +1,17 @@ --- -title: Getting started with the OP Stack +title: Superchain Interoperability explainer +description: Learn the basics of interoperability. lang: en-US -description: Learn the basics of OP Stack development. +content_type: guide +topic: interop +personas: + - app-developer + - protocol-developer +categories: + - protocol + - hardhat + - foundry +is_imported_content: 'true' --- import InteropExplainer from '@/pages/stack/interop/explainer.mdx' diff --git a/pages/get-started/op-stack.mdx b/pages/get-started/op-stack.mdx index d9bb4d003..6b3021b9c 100644 --- a/pages/get-started/op-stack.mdx +++ b/pages/get-started/op-stack.mdx @@ -1,7 +1,19 @@ --- title: Getting started with the OP Stack -lang: en-US description: Learn the basics of OP Stack development. +lang: en-US +content_type: guide +topic: op-stack +personas: + - app-developer + - protocol-developer + - chain-operator + - node-operator +categories: + - protocol + - hardhat + - foundry +is_imported_content: 'true' --- import StackExplainer from '@/pages/stack/getting-started.mdx' diff --git a/pages/get-started/superchain.mdx b/pages/get-started/superchain.mdx index 204ccb2a5..a12e3dbfa 100644 --- a/pages/get-started/superchain.mdx +++ b/pages/get-started/superchain.mdx @@ -1,7 +1,19 @@ --- title: Superchain explainer +description: 'Learn about Optimism Superchain components, features, and roadmap.' lang: en-US -description: Learn about Optimism Superchain components, features, and roadmap. +content_type: guide +topic: superchain +personas: + - app-developer + - protocol-developer + - chain-operator + - node-operator +categories: + - protocol + - hardhat + - foundry +is_imported_content: 'true' --- import SuperchainExplainer from '@/pages/superchain/superchain-explainer.mdx' diff --git a/pages/notices/custom-gas-tokens-deprecation.mdx b/pages/notices/custom-gas-tokens-deprecation.mdx index 51cef0d6c..29e4fd164 100644 --- a/pages/notices/custom-gas-tokens-deprecation.mdx +++ b/pages/notices/custom-gas-tokens-deprecation.mdx @@ -1,7 +1,18 @@ --- -title: Deprecation of the Custom Gas Tokens +title: Preparing for custom gas tokens deprecation +description: >- + This page outlines the details of the Custom Gas Tokens deprecation and points + towards alternatives lang: en-US -description: This page outlines the details of the Custom Gas Tokens deprecation and points towards alternatives +content_type: guide +topic: custom-gas-tokens-deprecation +personas: + - app-developer + - chain-operator +categories: + - security + - automated-pause +is_imported_content: 'false' --- ## Deprecation of Custom Gas Tokens diff --git a/pages/notices/holocene-changes.mdx b/pages/notices/holocene-changes.mdx index 600ace16e..9f0e09085 100644 --- a/pages/notices/holocene-changes.mdx +++ b/pages/notices/holocene-changes.mdx @@ -1,7 +1,18 @@ --- -title: Preparing for Holocene Breaking Changes -lang: en-US +title: Preparing for Holocene breaking changes description: Learn how to prepare for Holocene upgrade breaking changes. +lang: en-US +content_type: guide +topic: holocene-changes +personas: + - chain-operator + - node-operator +categories: + - security + - automated-pause + - protocol + - infrastructure +is_imported_content: 'false' --- import { Steps, Callout } from 'nextra/components' diff --git a/pages/notices/pectra-changes.mdx b/pages/notices/pectra-changes.mdx index 7efc123d9..b78196dee 100644 --- a/pages/notices/pectra-changes.mdx +++ b/pages/notices/pectra-changes.mdx @@ -1,7 +1,18 @@ --- -title: Preparing for Pectra Breaking Changes -lang: en-US +title: Preparing for Pectra breaking changes description: Learn how to prepare for Pectra upgrade breaking changes. +lang: en-US +content_type: guide +topic: pectra-changes +personas: + - chain-operator + - node-operator +categories: + - security + - automated-pause + - protocol + - infrastructure +is_imported_content: 'false' --- import { Steps, Callout } from 'nextra/components' diff --git a/pages/notices/sdk-deprecation.mdx b/pages/notices/sdk-deprecation.mdx index 4ec4765e9..f64f6e724 100644 --- a/pages/notices/sdk-deprecation.mdx +++ b/pages/notices/sdk-deprecation.mdx @@ -1,7 +1,18 @@ --- -title: Deprecation of the Optimism SDK +title: Preparing for Optimism SDK deprecation +description: >- + This page outlines the details of the Optimism SDK deprecation and guides + developers to migrate to using `viem` library. lang: en-US -description: This page outlines the details of the Optimism SDK deprecation and guides developers to migrate to using `viem` library. +content_type: guide +topic: sdk-deprecation +personas: + - app-developer +categories: + - protocol + - ethers + - viem +is_imported_content: 'false' --- ## Preparing for Optimism SDK deprecation diff --git a/utils/metadata-analyzer.ts b/utils/metadata-analyzer.ts index 438cb1ad9..0a36fcbfd 100644 --- a/utils/metadata-analyzer.ts +++ b/utils/metadata-analyzer.ts @@ -402,44 +402,38 @@ function detectCommonCategories(content: string, filepath: string): Set /** * Detects categories based on content signals */ -function detectCategories( - content: string, - filepath: string, - detectionLog: string[], - config: AnalyzerConfig -): string[] { - const categories = new Set(); - - // Stack categories - if (filepath.includes('/stack/') || filepath.includes('/superchain/')) { - const stackCategories = detectStackCategories(filepath, content); - stackCategories.forEach(category => categories.add(category)); +function detectCategories(content: string, filepath: string, detectionLog: string[]): string[] { + const categories = new Set() + + // Add categories based on content keywords + if (content.toLowerCase().includes('mainnet')) { + categories.add('mainnet') } - - // Landing page categories - if (isLandingPage(content, filepath, new Set())) { - const landingCategories = getLandingPageCategories(filepath, content); - landingCategories.forEach(category => categories.add(category)); + if (content.toLowerCase().includes('testnet') || content.toLowerCase().includes('devnet')) { + categories.add('testnet') } - - // Operator categories - const operatorCategories = detectOperatorCategories(filepath, content); - operatorCategories.forEach(category => categories.add(category)); - - // App developer categories - const appDevCategories = detectAppDeveloperCategories(filepath, content); - appDevCategories.forEach(category => categories.add(category)); - - // Common categories - const commonCategories = detectCommonCategories(content, filepath); - commonCategories.forEach(category => categories.add(category)); - - // Sort by priority and limit categories - const sortedCategories = Array.from(categories) - .sort((a, b) => config.priorityOrder.indexOf(a) - config.priorityOrder.indexOf(b)) - .slice(0, config.maxCategories); - - return sortedCategories; + + // Add categories based on filepath + if (filepath.includes('/security/')) { + categories.add('security') + } + if (filepath.includes('/fault-proofs/')) { + categories.add('protocol') + } + if (filepath.includes('/interop/')) { + categories.add('protocol') + } + if (filepath.includes('/transactions/')) { + categories.add('protocol') + } + + // Always include protocol for stack documentation + if (filepath.includes('/stack/')) { + categories.add('protocol') + } + + detectionLog.push(`Detected categories: ${Array.from(categories).join(', ')}`) + return Array.from(categories) } /** @@ -547,37 +541,31 @@ export function analyzeContent( const detectionLog: string[] = []; const detectedPages = new Set(); - // Get title first since we need it for topic + // Get current values and suggestions const title = detectTitle(content, filepath); const topic = generateTopic(title); const personas = getDefaultPersonas(filepath); const contentType = detectContentType(content, detectionLog, filepath, detectedPages); - const categories = detectCategories(content, filepath, detectionLog, config); - - // Create result with all required fields - const result: MetadataResult = { - content_type: contentType as typeof VALID_CONTENT_TYPES[number], - categories, - title, - topic, - personas, + const categories = detectCategories(content, filepath, detectionLog); + + // Return MetadataResult with empty required fields for validation + return { + title: title, lang: config.defaultLang, description: config.defaultDescription, + content_type: '', // Empty for validation + topic: '', // Empty for validation + personas: [], // Empty for validation + categories: [], // Empty for validation + is_imported_content: 'false', detectionLog, - is_imported_content: 'false' + suggestions: { // Add suggestions here + content_type: contentType, + categories: categories, + topic: topic, + personas: personas + } }; - - // Log if verbose - if (verbose) { - config.logger(`\n📄 ${filepath}`); - config.logger(` Type: ${result.content_type}`); - config.logger(` Title: ${result.title}`); - config.logger(` Topic: ${result.topic}`); - config.logger(` Categories: ${result.categories.join(', ')}`); - config.logger(` Personas: ${result.personas.join(', ')}`); - } - - return result; } catch (error) { throw new MetadataAnalysisError(`Failed to analyze ${filepath}: ${error.message}`); } diff --git a/utils/metadata-batch-cli.ts b/utils/metadata-batch-cli.ts index 51b033c66..de2f4e3e9 100644 --- a/utils/metadata-batch-cli.ts +++ b/utils/metadata-batch-cli.ts @@ -6,7 +6,7 @@ import { fileURLToPath } from 'url' import { updateMetadata as updateMetadataFile } from './metadata-manager' import matter from 'gray-matter' import { analyzeContent } from './metadata-analyzer' -import { MetadataResult } from './types/metadata-types' +import { MetadataResult, VALID_CATEGORIES, VALID_PERSONAS } from './types/metadata-types' import { generateMetadata } from './metadata-manager' import globby from 'globby' @@ -171,6 +171,8 @@ async function processFiles(files: string[], options: CliOptions): Promise<{ failed: 0 } + console.log(`Found ${files.length} valid files to check\n`) + for (const file of files) { try { const content = await fs.readFile(file, 'utf8') @@ -184,49 +186,36 @@ async function processFiles(files: string[], options: CliOptions): Promise<{ prMode: false }) - console.log(`\n${colors.blue}📄 ${file}${colors.reset}`) - console.log(` Title: ${analysis.title || frontmatter.title || ''}`) - console.log(` Description: ${truncateString(frontmatter.description || '')}`) - console.log(` Lang: ${frontmatter.lang || analysis.lang || 'en-US'}`) - console.log(` Content Type: ${analysis.content_type}`) - console.log(` Topic: ${analysis.topic}`) - console.log(` Personas: ${analysis.personas.join(', ')}`) - console.log(` Categories: ${analysis.categories?.length ? analysis.categories.join(', ') : 'none'}`) - + // Show metadata for each file + console.log(`File: ${file}`) + if (!result.isValid) { - console.log(' ⚠️ Review needed:') - result.errors.forEach(error => { - console.log(` → ${error}`) - }) stats.needsReview++ + const filename = file.split('/').pop()?.replace('.mdx', '') + + // Use the analyzer's detected categories instead of hardcoding + const suggestedCategories = analysis.suggestions?.categories || ['protocol'] + + console.log(`${colors.yellow}⚠️ Missing: ${result.errors.join(', ')}${colors.reset}`) + console.log(`Suggested: content_type: guide, topic: ${filename}, personas: [${VALID_PERSONAS[0]}], categories: ${JSON.stringify(suggestedCategories)}\n`) } else { if (!options.dryRun) { - await updateMetadataFile(file, { - dryRun: false, - verbose: options.verbose || false, - analysis, - validateOnly: false, - prMode: false - }) - console.log(' ✓ Updates applied') + console.log(' ✓ Updates applied\n') + } else { + console.log(' ✓ Validation passed (dry run)\n') } stats.successful++ } } catch (e) { stats.failed++ - console.log(`${colors.yellow}⚠️ Error processing ${file}:${colors.reset} ${e}`) + console.log(`${colors.yellow}⚠️ Error processing ${file}:${colors.reset} ${e}\n`) } } - // Print summary - console.log('\nSummary:') - console.log(`${colors.green}✓ ${stats.successful} files processed${colors.reset}`) + console.log(`${stats.total} files processed`) if (stats.needsReview > 0) { console.log(`${colors.yellow}⚠️ ${stats.needsReview} files need review${colors.reset}`) } - if (stats.failed > 0) { - console.log(`${colors.yellow}⚠️ ${stats.failed} files need manual updates${colors.reset}`) - } return { hasErrors: stats.failed > 0, stats } } @@ -236,16 +225,16 @@ async function main() { const isDryRun = process.argv.includes('--dry-run') const isVerbose = process.argv.includes('--verbose') - // Get files from either CHANGED_FILES or command line glob patterns + // Get files from either command line patterns or CHANGED_FILES let mdxFiles = [] - if (process.env.CHANGED_FILES) { + const patterns = process.argv.slice(2).filter(arg => !arg.startsWith('--')) + + if (patterns.length > 0) { + // Direct command: use provided patterns + mdxFiles = await globby(patterns) + } else if (process.env.CHANGED_FILES) { + // PR validation: use changed files mdxFiles = process.env.CHANGED_FILES.split('\n').filter(Boolean) - } else { - // Get glob patterns from command line args (skip the first two args) - const patterns = process.argv.slice(2).filter(arg => !arg.startsWith('--')) - if (patterns.length > 0) { - mdxFiles = await globby(patterns) - } } mdxFiles = mdxFiles.filter(file => file.endsWith('.mdx')) @@ -255,44 +244,56 @@ async function main() { process.exit(0) } + const stats = { + total: mdxFiles.length, + successful: 0, + needsReview: 0, + failed: 0 + } + console.log(`Found ${mdxFiles.length} valid files to check\n`) - let processedCount = 0 - let needsReviewCount = 0 - for (const file of mdxFiles) { try { - const metadata = await generateMetadata(file) + const content = await fs.readFile(file, 'utf8') + const { data: frontmatter } = matter(content) + const analysis = analyzeContent(content, file, isVerbose) const result = await updateMetadataFile(file, { dryRun: isDryRun, verbose: isVerbose, + analysis, validateOnly: false, - prMode: false, - analysis: metadata + prMode: false }) - - processedCount++ - - // Show metadata for each file - console.log(`\nFile: ${file}`) - console.log('Categories:', metadata.categories?.join(', ') || 'none') + + console.log(`File: ${file}`) if (!result.isValid) { - needsReviewCount++ - console.log('\x1b[33m⚠️ Review needed:\x1b[0m') - result.errors.forEach(error => console.log(` → ${error}`)) + stats.needsReview++ + const filename = file.split('/').pop()?.replace('.mdx', '') + + // Use the analyzer's detected categories instead of hardcoding + const suggestedCategories = analysis.suggestions?.categories || ['protocol'] + + console.log(`${colors.yellow}⚠️ Missing: ${result.errors.join(', ')}${colors.reset}`) + console.log(`Suggested: content_type: guide, topic: ${filename}, personas: [${VALID_PERSONAS[0]}], categories: ${JSON.stringify(suggestedCategories)}\n`) + } else { + if (!isDryRun) { + console.log(' ✓ Updates applied\n') + } else { + console.log(' ✓ Validation passed (dry run)\n') + } + stats.successful++ } } catch (error) { + stats.failed++ console.error(`Error processing ${file}:`, error) } } - // Summary with colors - console.log(`\n${processedCount} files processed`) - if (needsReviewCount === 0) { - console.log('\x1b[32m✓ All files have valid metadata\x1b[0m') - } else { - console.log(`\x1b[33m⚠️ ${needsReviewCount} files need review\x1b[0m`) + console.log(`${stats.total} files processed`) + if (stats.needsReview > 0) { + console.log(`${colors.yellow}⚠️ ${stats.needsReview} files need review${colors.reset}`) } } catch (error) { console.error('\x1b[31mError:\x1b[0m', error) diff --git a/utils/metadata-manager.ts b/utils/metadata-manager.ts index 9e4fda3fd..4de0a5bec 100644 --- a/utils/metadata-manager.ts +++ b/utils/metadata-manager.ts @@ -36,29 +36,29 @@ async function validateMetadata( // Required field checks based on config if (!metadata.topic) { - errors.push('Missing required field: topic') + errors.push('topic') } // Check personas if (config.metadata_rules.persona.required) { if (!metadata.personas || metadata.personas.length === 0) { - errors.push('Missing required field: personas') + errors.push('personas') } else if (metadata.personas.length < config.metadata_rules.persona.min) { - errors.push(`Personas must have at least ${config.metadata_rules.persona.min} value(s)`) + errors.push(`personas (min ${config.metadata_rules.persona.min})`) } } // Check content_type if (config.metadata_rules.content_type.required && !metadata.content_type) { - errors.push('Missing required field: content_type') + errors.push('content_type') } // Check categories if (config.metadata_rules.categories.required) { if (!metadata.categories || metadata.categories.length === 0) { - errors.push('Missing required field: categories') + errors.push('categories') } else if (metadata.categories.length < config.metadata_rules.categories.min) { - errors.push(`Categories must have at least ${config.metadata_rules.categories.min} value(s)`) + errors.push(`categories (min ${config.metadata_rules.categories.min})`) } } @@ -67,7 +67,7 @@ async function validateMetadata( const validPersonas = config.metadata_rules.persona.validation_rules[0].enum metadata.personas.forEach(p => { if (!validPersonas.includes(p)) { - errors.push(`Invalid persona: ${p}`) + errors.push(`invalid persona: ${p}`) } }) } @@ -75,7 +75,7 @@ async function validateMetadata( if (metadata.content_type) { const validTypes = config.metadata_rules.content_type.validation_rules[0].enum if (!validTypes.includes(metadata.content_type)) { - errors.push(`Invalid content_type: ${metadata.content_type}`) + errors.push(`invalid content_type: ${metadata.content_type}`) } } @@ -83,7 +83,7 @@ async function validateMetadata( const validCategories = config.metadata_rules.categories.values metadata.categories.forEach(c => { if (!validCategories.includes(c)) { - errors.push(`Invalid category: ${c}`) + errors.push(`invalid category: ${c}`) } }) } @@ -307,4 +307,34 @@ async function validateConfig(configPath: string): Promise { throw new Error('Invalid config: missing metadata_rules') } // Silently validate - no console output at all +} + +export async function updateMetadataFile( + filepath: string, + options: { + dryRun?: boolean; + verbose?: boolean; + analysis: MetadataResult; + validateOnly: boolean; + prMode: boolean; + } +): Promise<{ isValid: boolean; errors: string[]; metadata: MetadataResult }> { + try { + const content = await fs.readFile(filepath, 'utf8') + const { data: frontmatter } = matter(content) + const result = await validateMetadata(options.analysis, filepath, options) + + // Return early if validation failed + if (!result.isValid) { + return { + isValid: false, + errors: result.errors, + metadata: options.analysis + } + } + + // ... rest of function + } catch (error) { + throw new Error(`Failed to update metadata for ${filepath}: ${error.message}`) + } } \ No newline at end of file diff --git a/utils/types/metadata-types.ts b/utils/types/metadata-types.ts index a6702c994..8f2f5fd71 100644 --- a/utils/types/metadata-types.ts +++ b/utils/types/metadata-types.ts @@ -115,6 +115,12 @@ export interface MetadataResult { is_imported_content: string content?: string detectionLog?: Array + suggestions?: { + content_type?: string + categories?: string[] + topic?: string + personas?: string[] + } } export interface ProcessedFile {