Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 134 additions & 3 deletions scripts/update-config-page.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import https from 'node:https';
import fs from 'node:fs/promises';
import { execSync } from 'node:child_process';

/**
* Resolves a reference to an internal schema - external references are not supported
Expand Down Expand Up @@ -62,6 +63,50 @@ function parseValue(type, value) {
}
}

/**
* Extracts configuration constants from the schema
* @param properties
* @param jsonData
* @param parentKey
* @returns {Promise<Object>}
*/
async function extractConstants(properties, jsonData, parentKey = '') {
let constants = {};

for (const [key, value] of Object.entries(properties)) {
if (value.$ref) {
const refSchema = await resolveRef(value.$ref, jsonData);
const nestedConstants = await extractConstants(refSchema.properties, jsonData, parentKey ? `${parentKey}.${key}` : key);
constants = { ...constants, ...nestedConstants };
} else {
const fullKey = parentKey ? `${parentKey}.${key}` : key;

// Only include constants that have default values or are arrays (which can default to empty array)
if (value.type !== 'object' && (value.default !== undefined || value.type === 'array')) {
// Convert config key to constant name (e.g., maxTuplesPerWrite -> MAX_TUPLES_PER_WRITE)
const constantName = fullKey
.replace(/\./g, '_')
.replace(/([a-z])([A-Z])/g, '$1_$2')
.toUpperCase();

constants[constantName] = {
value: value.default !== undefined ? value.default : (value.type === 'array' ? [] : null),
type: value.type,
description: value.description || '',
configKey: fullKey
};
}

if (value.type === 'object' && value.properties) {
const nestedConstants = await extractConstants(value.properties, jsonData, fullKey);
constants = { ...constants, ...nestedConstants };
}
}
}

return constants;
}

/**
* Processes the properties of a schema and returns the MDX content
* @param properties
Expand Down Expand Up @@ -97,6 +142,72 @@ async function processProperties(properties, jsonData, parentKey = '') {
return mdxContent;
}

/**
* Generates a TypeScript constants file from the extracted constants
* @param constants
* @param releaseData
* @returns {string}
*/
function generateConstantsFile(constants, releaseData) {
let content = `// Auto-generated product configuration constants
// Generated from ${releaseData.release} config schema
// Do not edit this file manually - it will be overwritten

/**
* Product Configuration Default Values
*
* These constants are extracted from the ${releaseData.release} configuration schema.
* They represent the default values for various configuration options.
*/

`;

// Sort constants by name for consistency
const sortedConstants = Object.entries(constants).sort(([a], [b]) => a.localeCompare(b));

for (const [constantName, config] of sortedConstants) {
// Add JSDoc comment with description and config key
content += `/**\n`;
content += ` * ${config.description || 'Configuration option'}\n`;
content += ` * Config key: ${config.configKey}\n`;
content += ` * Type: ${config.type}\n`;
content += ` */\n`;

// Generate the constant declaration
if (config.type === 'string') {
content += `export const ${constantName} = '${config.value}';\n\n`;
} else if (config.type === 'boolean') {
content += `export const ${constantName} = ${config.value};\n\n`;
} else if (config.type === 'array') {
// Handle array values - use empty array if no default
const arrayValue = Array.isArray(config.value) ? JSON.stringify(config.value) : '[]';
content += `export const ${constantName} = ${arrayValue};\n\n`;
} else {
content += `export const ${constantName} = ${config.value};\n\n`;
}
}

// Add a convenience object with all constants
content += `/**\n`;
content += ` * All product configuration default values in a single object\n`;
content += ` */\n`;
content += `export const PRODUCT_CONFIG_DEFAULTS = {\n`;

for (const [constantName] of sortedConstants) {
content += ` ${constantName},\n`;
}

content += `} as const;\n\n`;

// Add type definitions
content += `/**\n`;
content += ` * Type representing all available product configuration constants\n`;
content += ` */\n`;
content += `export type ProductConfigConstants = typeof PRODUCT_CONFIG_DEFAULTS;\n`;

return content;
}

/**
* Generates the MDX content for the .config-schema.json file
* @param releaseData {release: string, url: string, releaseUrl: string}
Expand Down Expand Up @@ -295,21 +406,41 @@ async function getFileData() {
}

const OUTPUT_FILE = 'docs/content/getting-started/setup-openfga/configuration.mdx';
const CONSTANTS_OUTPUT_FILE = 'src/constants/product-config.ts';

/**
* Downloads the .config-schema for the latest release of OpenFGA and generates the MDX content for it
* Downloads the .config-schema for the latest release of OpenFGA and generates the MDX content and constants file
* @returns {Promise<{release: string, url: string, releaseUrl: string}>}
*/
async function generateMdxForLatestRelease() {
const releaseData = await getFileData();
const jsonData = await performHttpRequest(releaseData.url);

// Generate the MDX content
const mdxContent = await getMdxContent(releaseData, jsonData);

await fs.writeFile(OUTPUT_FILE, mdxContent, 'utf8');

// Extract constants and generate constants file
if (jsonData.properties) {
const constants = await extractConstants(jsonData.properties, jsonData);
const constantsContent = generateConstantsFile(constants, releaseData);

// Ensure the constants directory exists
await fs.mkdir('src/constants', { recursive: true });
await fs.writeFile(CONSTANTS_OUTPUT_FILE, constantsContent, 'utf8');

// Format the generated constants file with Prettier
try {
execSync(`npx prettier --write ${CONSTANTS_OUTPUT_FILE}`, { stdio: 'inherit' });
} catch (error) {
console.warn(`Warning: Could not format ${CONSTANTS_OUTPUT_FILE} with Prettier:`, error.message);
}
}

return releaseData;
}


generateMdxForLatestRelease()
.then(({ release, url }) => console.log(`Downloaded .config-schema.json file for ${release} from ${url} and saved the generated file at ${OUTPUT_FILE}`))
.then(({ release, url }) => console.log(`Downloaded .config-schema.json file for ${release} from ${url} and saved the generated files at ${OUTPUT_FILE} and ${CONSTANTS_OUTPUT_FILE}`))
.catch(console.error);
49 changes: 49 additions & 0 deletions src/components/Docs/ConfigValue/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import * as ProductConfig from '../../../constants/product-config';

// Type for config constants
type ConfigConstants = keyof typeof ProductConfig.PRODUCT_CONFIG_DEFAULTS;

interface ConfigValueProps {
/**
* The name of the configuration constant to display
* Should match the exported constant name from product-config.ts
*/
name: ConfigConstants;

/**
* Optional format for the value display
*/
format?: 'code' | 'text';
}

/**
* Component to display product configuration default values
*
* This component fetches configuration values from the product-config.ts file,
* which can be auto-generated (for OpenFGA) or manually maintained (for other products).
* This ensures the documentation stays synchronized with the actual configuration schema.
*
* @example
* ```mdx
* The Write API allows up to <ConfigValue name="MAX_TUPLES_PER_WRITE" /> tuples per request.
* ```
*/
export const ConfigValue: React.FC<ConfigValueProps> = ({ name, format = 'code' }) => {
const value = ProductConfig.PRODUCT_CONFIG_DEFAULTS[name];

if (value === undefined) {
console.warn(`ConfigValue: Unknown configuration constant "${String(name)}"`);
return <span style={{ color: 'red' }}>[Unknown config: {String(name)}]</span>;
}

const displayValue = String(value);

if (format === 'code') {
return <code>{displayValue}</code>;
}

return <span>{displayValue}</span>;
};

export default ConfigValue;
1 change: 1 addition & 0 deletions src/components/Docs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './AuthorizationModel';
export * from './Banner';
export * from './CardBox';
export * from './Column';
export * from './ConfigValue';
export * from './DocumentationNotice';
export * from './Feedback';
export * from './Overview';
Expand Down
Loading
Loading