diff --git a/_dropin-enrichments/merchant-blocks/REQUIREMENTS-README.md b/_dropin-enrichments/merchant-blocks/REQUIREMENTS-README.md new file mode 100644 index 000000000..4d9afb618 --- /dev/null +++ b/_dropin-enrichments/merchant-blocks/REQUIREMENTS-README.md @@ -0,0 +1,91 @@ +# Merchant Block Requirements - Documentation + +## Overview + +The Requirements section provides merchants with immediate actionable information about what customers can do with each block and what needs to be configured. + +## Format + +**Customer-capability first:** +``` +[Customer capability]. Enable [feature] in your Adobe Commerce admin panel before using this block. +``` + +### Example: +``` +Company administrators add team members, assign roles, and manage user access through this block. Enable company features in your Adobe Commerce admin panel before using this block. +``` + +## Priority System + +The generator uses a 2-tier priority system: + +1. **Priority 1**: Enriched requirements from `requirements.json` (ALWAYS used if present) +2. **Priority 2**: Auto-extracted from README files (fallback only) + +## File: `requirements.json` + +### Structure: +```json +{ + "metadata": { + "description": "Merchant-focused requirements for B2B commerce blocks", + "format": "[Customer capability]. Enable [feature] in your Adobe Commerce admin panel before using this block.", + "last_updated": "2025-12-07", + "verified": true + }, + "requirements": { + "block-name": "Requirement text following the format above..." + } +} +``` + +### Maintenance: + +When updating requirements: +1. Edit `requirements.json` with the new requirement text +2. Follow the customer-capability-first format +3. Update the `last_updated` date in metadata +4. Regenerate merchant documentation + +Requirements in `requirements.json` will **never be overwritten** by README extraction. + +## Writing Guidelines + +### ✅ DO: +- Lead with what customers can do +- Use active voice +- Be specific about capabilities +- Keep the admin reference simple ("Enable X in your Adobe Commerce admin panel") +- Focus on merchant value + +### ❌ DON'T: +- Use passive voice ("This block requires...") +- List technical prerequisites without context +- Provide complex admin paths +- Focus on technical details + +## Example Transformations + +### Before (Technical, Generic): +> Users must authenticate and have purchase order permissions to view purchase order history. + +### After (Customer-Capability First): +> Users view audit trails showing who approved orders, when actions occurred, and status changes for compliance through this block. Enable purchase orders in your Adobe Commerce admin panel before using this block. + +--- + +## Generator Integration + +**File:** `scripts/@generate-merchant-block-docs.js` + +**Function:** `generateRequirementsSection()` (lines 828-870) + +The function: +1. Checks `requirements.json` first +2. If enriched requirement exists, uses it +3. Otherwise falls back to README extraction +4. Formats output for MDX + +**This ensures requirements are never overwritten during regeneration.** + diff --git a/_dropin-enrichments/merchant-blocks/descriptions.json b/_dropin-enrichments/merchant-blocks/descriptions.json new file mode 100644 index 000000000..7f6af653e --- /dev/null +++ b/_dropin-enrichments/merchant-blocks/descriptions.json @@ -0,0 +1,306 @@ +{ + "$schema": "merchant-block-descriptions", + "description": "Merchant-friendly descriptions for commerce blocks. Outcome-focused and written for busy, action-oriented merchants.", + "metadata": { + "last_verified_commit": "df12f2eb08204b84f9a71a345f5da2c5b2e831bf", + "last_verified_date": "2025-12-08", + "boilerplate_branch": "b2b-suite-release1", + "total_blocks": 42, + "verified_blocks": 58, + "verification_method": "source-code-first" + }, + "blocks": { + "cart": { + "description": "Customers review items, update quantities, apply coupons, and proceed to checkout through this block. Place it on your cart page.", + "verified": true, + "source": "README: comprehensive shopping cart with product management, order summary, gift options, wishlist integration, and B2B quote request functionality" + }, + "mini-cart": { + "description": "Customers view cart contents and proceed to checkout through this dropdown. Place it in your site header for quick access.", + "verified": true, + "source": "README: provides a compact cart interface with product management, notifications, and modal integration" + }, + "checkout": { + "description": "Customers enter shipping details, select payment methods, and complete purchases through this block. Place it on your checkout page.", + "verified": true, + "source": "README: provides comprehensive one-page checkout experience with dynamic form handling, payment processing, address management" + }, + "checkout-success": { + "description": "Customers see order confirmation and order numbers after completing checkout through this block. Place it on your order confirmation page.", + "verified": true, + "source": "README: renders post-purchase order confirmation experience with order details, status, shipping, customer information" + }, + "gift-options": { + "description": "Customers view gift wrapping, gift messages, and gift receipt options through this block. Place it on order detail pages.", + "verified": true, + "source": "README: renders gift options functionality with read-only display for order contexts with secondary view styling" + }, + "product-details": { + "description": "Customers view product information, select options, and add items to cart through this block. Place it on product detail pages.", + "verified": true, + "source": "README: provides comprehensive product detail page functionality using multiple @dropins/storefront-pdp containers" + }, + "product-list-page": { + "description": "Customers browse products, filter selections, and sort results through this block. Place it on category and search results pages.", + "verified": true, + "source": "No README Overview section - using reasonable inference", + "note": "README file has no Overview section" + }, + "product-recommendations": { + "description": "Customers discover relevant products based on browsing history and preferences through this block. Place it on any page to increase cross-selling.", + "verified": true, + "source": "README: provides personalized product recommendations with intersection observer, context tracking, and dynamic reloading" + }, + "targeted-block": { + "description": "Customers see personalized content based on their customer group or segment through this block. Use it for targeted promotions and messaging.", + "verified": true, + "source": "No README file exists", + "note": "No README file - using reasonable inference based on block name and purpose" + }, + "login": { + "description": "Customers sign in to access their accounts and personalized features through this block. Place it on your login page.", + "verified": true, + "source": "README: provides user authentication functionality using SignIn container with forgot password integration" + }, + "create-account": { + "description": "Customers create new accounts to save addresses and track orders through this block. Place it on your registration page.", + "verified": true, + "source": "README: provides user registration functionality with sign-up form, email confirmation, and privacy policy consent" + }, + "create-password": { + "description": "Customers change their account passwords through this block. Place it on account settings pages.", + "verified": true, + "source": "README: handles password update functionality for authenticated users with success notifications" + }, + "forgot-password": { + "description": "Customers reset forgotten passwords through this block. Place it on your password reset page.", + "verified": true, + "source": "README: provides password reset functionality using ResetPassword container" + }, + "confirm-account": { + "description": "Customers verify their email addresses and sign in through this block. Place it on your account confirmation page.", + "verified": true, + "source": "README: handles account confirmation and sign-in functionality with email confirmation support" + }, + "account-header": { + "description": "Customers see consistent page headers across account pages through this block. Place it at the top of account-related pages.", + "verified": true, + "source": "README: renders a standardized header component for account-related pages with configurable title text" + }, + "account-nav": { + "description": "Customers navigate between account sections through this block. Place it on account dashboard pages.", + "verified": true, + "source": "README: renders navigation menu with icons, titles, descriptions, and permission-based visibility" + }, + "account-sidebar": { + "description": "Customers navigate account sections through this sidebar. Place it on account pages for easy navigation.", + "verified": true, + "source": "README: creates dynamic navigation sidebar by loading configuration from a fragment with interactive menu items" + }, + "customer-information": { + "description": "Customers edit their profile details and update contact information through this block. Place it on account information pages.", + "verified": true, + "source": "README: provides customer information management functionality" + }, + "customer-details": { + "description": "Customers view their contact information on order pages through this block. Place it on order detail pages.", + "verified": true, + "source": "README: renders customer information display using CustomerDetails container" + }, + "addresses": { + "description": "Customers add, edit, and delete saved addresses through this block. Place it on account address management pages.", + "verified": true, + "source": "README: renders customer address management interface with viewing, adding, editing, and deleting addresses" + }, + "wishlist": { + "description": "Customers save products for later and move items to cart through this block. Place it on wishlist pages.", + "verified": true, + "source": "README: provides wishlist management functionality with authentication modal integration, product data fetching, and cart integration" + }, + "orders-list": { + "description": "Customers view past orders and track shipments through this block. Place it on order history pages.", + "verified": true, + "source": "README: renders list of customer orders with product images, tracking links, and navigation" + }, + "order-header": { + "description": "Customers see order numbers and navigate back to order lists through this block. Place it at the top of order detail pages.", + "verified": true, + "source": "README: renders dynamic header for order-related pages with conditional back navigation and order number display" + }, + "order-status": { + "description": "Customers view order status and create returns through this block. Place it on order detail pages.", + "verified": true, + "source": "README: renders order status information with return creation functionality and authentication-aware routing" + }, + "order-product-list": { + "description": "Customers see products included in orders through this block. Place it on order detail pages.", + "verified": true, + "source": "README: renders list of products from an order with image rendering, gift options, and product detail navigation" + }, + "order-cost-summary": { + "description": "Customers view order totals including tax, shipping, and discounts through this block. Place it on order detail pages.", + "verified": true, + "source": "README: renders order cost information using OrderCostSummary container" + }, + "order-returns": { + "description": "Customers view return information and track return shipments through this block. Place it on order detail pages with returns.", + "verified": true, + "source": "README: renders return information for orders with return tracking, product images, and navigation" + }, + "shipping-status": { + "description": "Customers track shipments and view delivery status through this block. Place it on order detail pages.", + "verified": true, + "source": "README: renders shipping status information with UPS-specific tracking integration" + }, + "search-order": { + "description": "Customers find orders by order number and email through this block. Place it on guest order lookup pages.", + "verified": true, + "source": "README: provides order search functionality for authenticated and guest customers with dynamic sign-in form rendering" + }, + "create-return": { + "description": "Customers request returns for purchased items through this block. Place it on return request pages.", + "verified": true, + "source": "README: provides return request functionality for orders with product image rendering and success redirection" + }, + "returns-list": { + "description": "Customers view return requests and track return status through this block. Place it on return history pages.", + "verified": true, + "source": "README: renders list of customer returns with product images, tracking links, and navigation" + }, + "return-header": { + "description": "Customers see return numbers and navigate back to return lists through this block. Place it at the top of return detail pages.", + "verified": true, + "source": "README: renders dynamic header for return-related pages with conditional back navigation and return number display" + }, + "company-profile": { + "description": "Company administrators update business details and contact information through this block. Place it on company profile pages.", + "verified": true, + "source": "README: provides company profile management functionality for B2B customers" + }, + "company-structure": { + "description": "Company administrators organize teams and departments through this block. Place it on company structure pages.", + "verified": true, + "source": "README: displays company organizational structure and management interface for B2B customers" + }, + "company-users": { + "description": "Company administrators add team members and manage user access through this block. Place it on company user management pages.", + "verified": true, + "source": "README: provides company user management functionality for B2B customers" + }, + "company-roles-permissions": { + "description": "Company administrators control who can approve orders and access features through this block. Place it on roles and permissions pages.", + "verified": true, + "source": "README: displays company role management interface to show roles, manage permissions, and provide administrative controls" + }, + "company-credit": { + "description": "Company buyers view credit balances and transaction history through this block. Place it on company credit pages.", + "verified": true, + "source": "README: renders company credit display for viewing credit information" + }, + "company-create": { + "description": "Customers register new companies to access B2B purchasing features through this block. Place it on company registration pages.", + "verified": true, + "source": "README: provides company registration form to handle company registration workflows" + }, + "company-accept-invitation": { + "description": "Invited users join companies through this block. Place it on invitation acceptance pages.", + "verified": true, + "source": "README: handles acceptance of company invitations sent via email to automatically process invitation links" + }, + "customer-company": { + "description": "Company users view their company affiliation and job title through this block. Place it on account information pages.", + "verified": true, + "source": "README: displays company-related information for B2B customers showing company name, job title, work phone, and user role" + }, + "b2b-requisition-list": { + "description": "Buyers create shopping lists for recurring orders through this block. Place it on requisition list pages.", + "verified": true, + "source": "README: surfaces buyer's requisition lists rendering a grid of lists with navigation to detail view" + }, + "b2b-requisition-list-view": { + "description": "Buyers review and add items to cart from saved lists through this block. Place it on requisition list detail pages.", + "verified": true, + "source": "README: surfaces buyer's requisition list rendering the contents with route back to grid" + }, + "b2b-negotiable-quote": { + "description": "Buyers request custom pricing and negotiate terms with your sales team through this block. Place it on quote pages.", + "verified": true, + "source": "README: provides two views for managing negotiable quotes - list view displays all quotes, manage view shows individual quote details" + }, + "b2b-negotiable-quote-template": { + "description": "Buyers save frequently ordered items as templates for faster reordering through this block. Place it on quote template pages.", + "verified": true, + "source": "README: provides comprehensive interface for managing quote templates with pagination and detailed template management" + }, + "b2b-quote-checkout": { + "description": "Buyers complete negotiated quote purchases with approved pricing through this block. Place it on quote checkout pages.", + "verified": true, + "source": "README: provides comprehensive one-page checkout experience for negotiable quotes with payment processing and billing address management" + }, + "b2b-po-customer-purchase-orders": { + "description": "Buyers track their own purchase orders and monitor approval status through this block. Place it on my purchase orders pages.", + "verified": true, + "source": "README: renders list of customer's own purchase orders with authentication protection and permission-based access" + }, + "b2b-po-company-purchase-orders": { + "description": "Managers monitor all company spending and track pending approvals through this block. Place it on company purchase orders pages.", + "verified": true, + "source": "README: renders list of company-wide purchase orders with authentication protection and permission-based access" + }, + "b2b-po-require-approval-purchase-orders": { + "description": "Approvers see orders awaiting their review and prioritize approval processing through this block. Place it on orders for approval pages.", + "verified": true, + "source": "README: renders list of purchase orders that require approval with authentication protection and permission-based access" + }, + "b2b-po-header": { + "description": "Users navigate purchase order details with contextual page headers through this block. Place it at the top of purchase order pages.", + "verified": true, + "source": "README: renders dynamic header for purchase order pages providing contextual navigation and title updates" + }, + "b2b-po-status": { + "description": "Users view order status and take actions like approve, reject, or cancel through this block. Place it on purchase order detail pages.", + "verified": true, + "source": "README: renders current status and available actions providing status display, action buttons (approve, reject, cancel, place order)" + }, + "b2b-po-history-log": { + "description": "Users view audit trails showing who approved orders and when actions occurred through this block. Place it on purchase order detail pages.", + "verified": true, + "source": "README: renders chronological history of status changes and activities providing activity tracking with pagination" + }, + "b2b-po-comments-list": { + "description": "Team members view order discussions and approval reasons through this block. Place it on purchase order detail pages.", + "verified": true, + "source": "README: renders list of comments for a purchase order providing comment viewing functionality with pagination" + }, + "b2b-po-comment-form": { + "description": "Team members communicate about orders and document decisions through this block. Place it on purchase order detail pages.", + "verified": true, + "source": "README: renders form for adding comments to a purchase order providing comment submission functionality" + }, + "b2b-po-checkout-success": { + "description": "Buyers receive purchase order confirmation and order numbers through this block. Place it on purchase order confirmation pages.", + "verified": true, + "source": "README: renders post-purchase order confirmation experience providing purchase order confirmation display with navigation" + }, + "b2b-po-approval-flow": { + "description": "Users view workflow status and who approved or rejected orders through this block. Place it on purchase order detail pages.", + "verified": true, + "source": "README: renders approval workflow for a purchase order displaying approval rules, statuses, and associated events" + }, + "b2b-po-approval-rules-list": { + "description": "Administrators view all approval rules and which orders they affect through this block. Place it on approval rules pages.", + "verified": true, + "source": "README: renders list of purchase order approval rules with authentication protection and permission-based access" + }, + "b2b-po-approval-rule-form": { + "description": "Administrators create spending limits and set approval hierarchies through this block. Place it on create approval rule pages.", + "verified": true, + "source": "README: renders form for creating new or editing existing purchase order approval rules with navigation for saving or canceling" + }, + "b2b-po-approval-rule-details": { + "description": "Administrators review rule conditions and approval requirements through this block. Place it on approval rule detail pages.", + "verified": true, + "source": "README: renders detailed information about a specific purchase order approval rule with navigation capabilities" + } + } +} diff --git a/_dropin-enrichments/merchant-blocks/requirements.json b/_dropin-enrichments/merchant-blocks/requirements.json new file mode 100644 index 000000000..4fc6975f3 --- /dev/null +++ b/_dropin-enrichments/merchant-blocks/requirements.json @@ -0,0 +1 @@ +fatal: path '_dropin-enrichments/merchant-blocks/requirements.json' exists on disk, but not in 'b2b-documentation' diff --git a/astro.config.mjs b/astro.config.mjs index c27264ffd..00a2192c3 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -905,17 +905,25 @@ async function config() { }, ], }, + { + label: 'Setup guides', + collapsed: true, + items: [ + { label: 'Personalization', link: '/merchants/commerce-blocks/personalization/' }, + { label: 'Product Recommendations', link: '/merchants/commerce-blocks/product-recommendations/' }, + ], + }, { label: 'Commerce blocks', items: [ { label: 'Overview', link: '/merchants/commerce-blocks/' }, - { label: 'Personalization', link: '/merchants/commerce-blocks/personalization/' }, - { label: 'Product recommendations', link: '/merchants/commerce-blocks/product-recommendations/' }, { label: 'Account Header', link: '/merchants/blocks/commerce-account-header/' }, + { label: 'Account Navigation', link: '/merchants/blocks/commerce-account-nav/' }, { label: 'Account Sidebar', link: '/merchants/blocks/commerce-account-sidebar/' }, { label: 'Addresses', link: '/merchants/blocks/commerce-addresses/' }, { label: 'Cart', link: '/merchants/blocks/commerce-cart/' }, { label: 'Checkout', link: '/merchants/blocks/commerce-checkout/' }, + { label: 'Checkout Success', link: '/merchants/blocks/commerce-checkout-success/' }, { label: 'Confirm Account', link: '/merchants/blocks/commerce-confirm-account/' }, { label: 'Create Account', link: '/merchants/blocks/commerce-create-account/' }, { label: 'Create Password', link: '/merchants/blocks/commerce-create-password/' }, @@ -936,6 +944,7 @@ async function config() { { label: 'Returns List', link: '/merchants/blocks/commerce-returns-list/' }, { label: 'Search Order', link: '/merchants/blocks/commerce-search-order/' }, { label: 'Shipping Status', link: '/merchants/blocks/commerce-shipping-status/' }, + { label: 'Personalization', link: '/merchants/blocks/targeted-block/' }, { label: 'Wishlist', link: '/merchants/blocks/commerce-wishlist/' }, { label: 'Product Details', link: '/merchants/blocks/product-details/' }, { label: 'Product List Page', link: '/merchants/blocks/product-list-page/' }, diff --git a/public/images/placeholder.webp b/public/images/placeholder.webp index b689383ca..d470dd67f 100644 Binary files a/public/images/placeholder.webp and b/public/images/placeholder.webp differ diff --git a/scripts/@generate-merchant-block-docs.js b/scripts/@generate-merchant-block-docs.js index 64c9bd737..6352faa16 100644 --- a/scripts/@generate-merchant-block-docs.js +++ b/scripts/@generate-merchant-block-docs.js @@ -19,7 +19,7 @@ */ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'fs'; -import { join } from 'path'; +import { join, basename } from 'path'; import { execSync } from 'child_process'; // Import shared utilities @@ -29,6 +29,140 @@ import { cloneOrUpdateBoilerplate } from './lib/repository.js'; const projectRoot = getProjectRoot(); +// ============================================================================ +// CHANGE DETECTION AND TRACKING +// ============================================================================ + +/** + * Get current commit hash from boilerplate repository + */ +function getBoilerplateCommitHash(boilerplatePath) { + try { + return execSync('git rev-parse HEAD', { + cwd: boilerplatePath, + encoding: 'utf8' + }).trim(); + } catch (error) { + console.warn(' ⚠️ Could not get commit hash'); + return null; + } +} + +/** + * Load enrichment metadata + */ +function loadEnrichmentMetadata() { + const enrichmentPath = join(projectRoot, '_dropin-enrichments', 'merchant-blocks', 'descriptions.json'); + if (!existsSync(enrichmentPath)) { + return null; + } + + try { + const content = readFileSync(enrichmentPath, 'utf8'); + const data = JSON.parse(content); + return data.metadata || null; + } catch (error) { + console.warn(` ⚠️ Could not load enrichment metadata: ${error.message}`); + return null; + } +} + +/** + * Detect changes in source code and README files + * Returns report of what changed since last verification + */ +function detectChanges(boilerplatePath, blocks) { + const currentCommit = getBoilerplateCommitHash(boilerplatePath); + const metadata = loadEnrichmentMetadata(); + + const changes = { + hasChanges: false, + currentCommit, + lastVerifiedCommit: metadata?.last_verified_commit || null, + sourceCodeChanges: [], + readmeChanges: [], + newBlocks: [], + removedBlocks: [] + }; + + if (!metadata || !metadata.last_verified_commit) { + console.log('\n⚠️ No previous verification found - first run'); + changes.hasChanges = true; + return changes; + } + + if (currentCommit !== metadata.last_verified_commit) { + console.log(`\n📋 Checking for changes since last verification...`); + console.log(` Last verified: ${metadata.last_verified_commit.substring(0, 8)}`); + console.log(` Current commit: ${currentCommit.substring(0, 8)}`); + + try { + // Get list of changed files + const changedFiles = execSync( + `git diff --name-only ${metadata.last_verified_commit} HEAD`, + { cwd: boilerplatePath, encoding: 'utf8' } + ).trim().split('\n').filter(f => f); + + // Track source code changes + const sourceChanges = changedFiles.filter(f => f.endsWith('.js') && f.startsWith('blocks/')); + if (sourceChanges.length > 0) { + changes.hasChanges = true; + changes.sourceCodeChanges = sourceChanges; + console.log(`\n 📝 Source code files changed: ${sourceChanges.length}`); + sourceChanges.forEach(f => console.log(` - ${f}`)); + } + + // Track README changes + const readmeChanges = changedFiles.filter(f => f.endsWith('README.md') && f.startsWith('blocks/')); + if (readmeChanges.length > 0) { + changes.hasChanges = true; + changes.readmeChanges = readmeChanges; + console.log(`\n 📖 README files changed: ${readmeChanges.length}`); + readmeChanges.forEach(f => console.log(` - ${f}`)); + } + + } catch (error) { + console.warn(` ⚠️ Could not detect changes: ${error.message}`); + changes.hasChanges = true; // Assume changes if we can't detect + } + } else { + console.log(`\n✅ No changes detected since last verification (${metadata.last_verified_date})`); + } + + return changes; +} + +/** + * Update enrichment metadata after successful generation + */ +function updateEnrichmentMetadata(boilerplatePath, blockCount) { + const enrichmentPath = join(projectRoot, '_dropin-enrichments', 'merchant-blocks', 'descriptions.json'); + + if (!existsSync(enrichmentPath)) { + console.warn(' ⚠️ Enrichment file not found - skipping metadata update'); + return; + } + + try { + const content = readFileSync(enrichmentPath, 'utf8'); + const data = JSON.parse(content); + + data.metadata = { + last_verified_commit: getBoilerplateCommitHash(boilerplatePath), + last_verified_date: new Date().toISOString().split('T')[0], + boilerplate_branch: 'b2b-suite-release1', + total_blocks: blockCount, + verified_blocks: Object.values(data.blocks || {}).filter(b => b.verified).length, + verification_method: 'source-code-first' + }; + + writeFileSync(enrichmentPath, JSON.stringify(data, null, 2) + '\n', 'utf8'); + console.log(' ✅ Updated enrichment metadata'); + } catch (error) { + console.warn(` ⚠️ Could not update enrichment metadata: ${error.message}`); + } +} + // ============================================================================ // REPOSITORY MANAGEMENT // ============================================================================ @@ -37,321 +171,735 @@ const projectRoot = getProjectRoot(); // CONFIGURATION EXTRACTION // ============================================================================ + /** - * Extract configuration from block JavaScript source code - * Parses multiple patterns: - * 1. Direct destructuring: const { 'key': var = 'default' } = readBlockConfig(block) - * 2. Variable assignment: const config = readBlockConfig(block); then access config.key - * 3. Nested destructuring: const config = readBlockConfig(block); const { key } = config + * Extract configuration from source code (PRIMARY SOURCE OF TRUTH) + * Scans .js file for readBlockConfig() calls and extracts destructured keys + * This is the ONLY way to know what configurations actually exist */ -function extractConfigFromSource(blockPath, blockName) { - const jsPath = join(blockPath, `${blockName}.js`); +function extractConfigFromSource(blockPath) { + const jsFile = join(blockPath, `${basename(blockPath)}.js`); - if (!existsSync(jsPath)) { - console.log(` ⚠️ JavaScript file not found: ${jsPath}`); + if (!existsSync(jsFile)) { return []; } - const source = readFileSync(jsPath, 'utf8'); + const sourceCode = readFileSync(jsFile, 'utf8'); const configs = []; - const configKeys = new Set(); - // Pattern 1: Direct destructuring from readBlockConfig - // const { 'key': var = 'default', 'key2': var2 } = readBlockConfig(block) - const directDestructurePattern = /const\s*\{([^}]+)\}\s*=\s*readBlockConfig\s*\(/s; - const directMatch = source.match(directDestructurePattern); + // Look for readBlockConfig pattern: + // const { 'config-key': varName = 'default', ... } = readBlockConfig(block); + const readBlockConfigPattern = /const\s*\{([^}]+)\}\s*=\s*readBlockConfig\([^)]+\)/g; + const matches = sourceCode.matchAll(readBlockConfigPattern); + + for (const match of matches) { + const destructuredContent = match[1]; - if (directMatch) { - const destructuring = directMatch[1]; + // Parse each destructured property + // Pattern: 'config-key': varName = 'defaultValue' + const propertyPattern = /['"]([^'"]+)['"]\s*:\s*(\w+)(?:\s*=\s*([^,}]+))?/g; + const propertyMatches = destructuredContent.matchAll(propertyPattern); - // Parse each property in the destructuring - // Pattern 1: 'config-key': variableName = 'default' (quoted keys) - // Pattern 2: variableName = 'default' (unquoted keys, variable name is the key) - // Pattern 3: variableName (no default) + for (const propMatch of propertyMatches) { + const key = propMatch[1].trim(); + const variable = propMatch[2].trim(); + let defaultValue = propMatch[3]?.trim() || 'undefined'; - // First try quoted keys: 'key': var = 'default' - const quotedPropertyPattern = /'([^']+)':\s*(\w+)\s*=?\s*([^,}]+)?/g; - let propMatch; + // Clean up default value + defaultValue = defaultValue + .replace(/^['"`]|['"`]$/g, '') + .replace(/,\s*$/, '') + .trim(); - while ((propMatch = quotedPropertyPattern.exec(destructuring)) !== null) { - const configKey = propMatch[1]; - const variableName = propMatch[2]; - let defaultValue = propMatch[3] ? propMatch[3].trim() : undefined; + if (defaultValue === '') { + defaultValue = 'undefined'; + } - if (!configKeys.has(configKey)) { - configKeys.add(configKey); - configs.push(extractConfigProperty(configKey, variableName, defaultValue)); + // Infer type from default value in source code + let type = 'string'; + if (defaultValue === 'true' || defaultValue === 'false') { + type = 'boolean'; + } else if (!isNaN(defaultValue) && defaultValue !== '' && defaultValue !== 'undefined') { + type = 'number'; + } else if (defaultValue.startsWith('[') || defaultValue.startsWith('{')) { + type = 'object'; } + + configs.push({ + key: key, + variable: variable, + type: type, + default: defaultValue, + description: '', // Will be enriched from README + required: 'No', + sideEffects: '', + source: 'source-code' + }); } + } + + return configs; +} - // Then try unquoted keys: variableName = 'default' or just variableName - // Only if we haven't already processed this key - const unquotedPropertyPattern = /(\w+)\s*=?\s*([^,}]+)?/g; - let unquotedMatch; +/** + * Extract configuration from README file (ENRICHMENT ONLY) + * Used ONLY to add descriptions and type annotations to configs found in source code + * README files should NOT be used to discover what configurations exist + */ +function extractConfigFromReadme(readmePath) { + if (!existsSync(readmePath)) { + return []; + } + + const readme = readFileSync(readmePath, 'utf8'); + let configs = []; + + // Method 1: Try to extract from markdown table first + const blockConfigPattern = /(?:##\s+Block\s+Configuration|###\s+Block\s+Configuration)[\s\S]*?\|\s*Configuration\s+Key[^|]*\|[^|]*\|[^|]*\|[^|]*\|[^|]*\|[^|]*\|([\s\S]*?)(?=\n\n|\n##|\n###|$)/i; + const tableMatch = readme.match(blockConfigPattern); + + if (tableMatch) { + const tableContent = tableMatch[1]; + const rows = tableContent.split('\n').filter(line => { + const trimmed = line.trim(); + return trimmed.startsWith('|') && + !trimmed.match(/^\|\s*-+\s*\|/) && // Skip separator rows + trimmed !== '|'; // Skip empty rows + }); - // Reset regex lastIndex to start from beginning - unquotedPropertyPattern.lastIndex = 0; + for (const row of rows) { + const cells = row.split('|').map(cell => cell.trim()).filter(cell => cell); + + // Expected columns: Configuration Key | Type | Default | Description | Required | Side Effects + if (cells.length >= 4) { + const key = cells[0].replace(/`/g, '').trim(); + const type = cells[1]?.trim() || 'string'; + const defaultValue = cells[2]?.trim() || 'undefined'; + const description = cells[3]?.trim() || ''; + const required = cells[4]?.replace(/`/g, '').trim() || 'No'; + const sideEffects = cells[5]?.trim() || ''; + + // Skip header rows, "no config" entries, and invalid keys + if (key && + key !== 'Configuration Key' && + key !== '–' && + !key.toLowerCase().includes('configuration') && + !key.match(/^-+$/) && + !description.toLowerCase().includes('no authorable configuration')) { + + // Clean default value + let cleanDefault = defaultValue.replace(/`/g, '').trim(); + if (cleanDefault === '' || cleanDefault === 'undefined' || cleanDefault === '–') { + cleanDefault = 'undefined'; + } - while ((unquotedMatch = unquotedPropertyPattern.exec(destructuring)) !== null) { - const variableName = unquotedMatch[1]; - let defaultValue = unquotedMatch[2] ? unquotedMatch[2].trim() : undefined; + // Infer type if not specified + let inferredType = type.toLowerCase(); + if (cleanDefault === 'true' || cleanDefault === 'false') { + inferredType = 'boolean'; + } else if (cleanDefault && !isNaN(cleanDefault) && cleanDefault !== 'undefined') { + inferredType = 'number'; + } else if (!inferredType || inferredType === 'string' || inferredType === '' || inferredType === '–') { + inferredType = 'string'; + } - // Skip if this looks like a quoted key pattern (already processed) - // Check if the previous character was a quote - const matchIndex = unquotedMatch.index; - if (matchIndex > 0 && destructuring[matchIndex - 1] === "'") { - continue; + configs.push({ + key: key, + variable: key.replace(/-/g, ''), // Convert kebab-case to variable name + type: inferredType, + default: cleanDefault, + description: description, + required: required === 'Yes' || required === 'Required' ? 'Yes' : 'No', + sideEffects: sideEffects + }); + } } + } + } - // Use variable name as config key (convert camelCase to kebab-case) - const configKey = variableName.replace(/([A-Z])/g, '-$1').toLowerCase(); + // Method 2: If no table configs found, try bullet point format (common in B2B blocks) + if (configs.length === 0) { + const bulletConfigPattern = /(?:##\s+Block\s+Configuration|###\s+Block\s+Configuration)([\s\S]*?)(?=\n##|\n###|Example configuration:|$)/i; + const bulletMatch = readme.match(bulletConfigPattern); + + if (bulletMatch) { + const configSection = bulletMatch[1]; + // Match bullet points like: - **className** (string, optional): Description + const bulletPattern = /^-\s+\*\*([^*]+)\*\*\s*\(([^,)]+)(?:,\s*(optional|required))?(?:,\s*default:\s*([^)]+))?\):\s*(.+)$/gm; + let match; + + while ((match = bulletPattern.exec(configSection)) !== null) { + const key = match[1].trim(); + const type = match[2].trim(); + const isRequired = match[3]?.trim() === 'required' ? 'Yes' : 'No'; + const defaultValue = match[4]?.trim() || 'undefined'; + const description = match[5].trim(); + + // Clean up types and defaults + let cleanDefault = defaultValue; + let inferredType = type.toLowerCase(); + + if (inferredType === 'boolean') { + cleanDefault = cleanDefault === 'true' ? 'true' : cleanDefault === 'false' ? 'false' : cleanDefault; + } - if (!configKeys.has(configKey) && !configKeys.has(variableName)) { - configKeys.add(configKey); - configKeys.add(variableName); // Also track variable name to avoid duplicates - configs.push(extractConfigProperty(configKey, variableName, defaultValue)); + configs.push({ + key: key, + variable: key.replace(/-/g, ''), + type: inferredType, + default: cleanDefault, + description: description, + required: isRequired, + sideEffects: '' + }); } } } - // Pattern 2: Variable assignment then property access - // const config = readBlockConfig(block); - // Then find: config['key'] or config.key or const { key } = config - const configVarPattern = /const\s+(\w+)\s*=\s*readBlockConfig\s*\([^)]*\)/; - const configVarMatch = source.match(configVarPattern); + return configs; +} + +/** + * Enrich configuration with descriptions from README (legacy function, kept for compatibility) + */ +function enrichConfigFromReadme(configs, readmePath) { + // This function is now deprecated - use extractConfigFromReadme instead + // Keeping for backward compatibility but it should not be called + return configs; +} + +/** + * Extract merchant-friendly description from README overview + * Simplifies technical README overview for merchant consumption + */ +function extractDescriptionFromReadme(readmePath) { + if (!existsSync(readmePath)) { + return null; + } + + try { + const content = readFileSync(readmePath, 'utf8'); + + // Extract the Overview section + const overviewMatch = content.match(/## Overview\s*\n\s*\n(.*?)(?=\n\n##|\n\n