UNPKG

generate-next-route-constants

Version:

Generate route constants for Next.js App Directory

237 lines (235 loc) 9.27 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.withRouteConstants = withRouteConstants; exports.generateRouteConstants = generateRouteConstants; const fs = __importStar(require("node:fs")); const path = __importStar(require("node:path")); const glob_1 = require("glob"); function withRouteConstants(config = {}) { return config; } async function generateRouteConstants(cliConfig, configPath) { // CLI 옵션이 최우선, 그 다음 설정 파일, 마지막으로 기본값 const fileConfig = await loadConfig(configPath); const config = { outputDir: cliConfig?.outputDir || fileConfig.outputDir || './src/constants', filename: cliConfig?.filename || fileConfig.filename || 'routes.ts', inputDir: cliConfig?.inputDir || fileConfig.inputDir || './src/app' }; // Find the actual app directory (handle monorepo structure) const actualInputDir = await findAppDirectory(config.inputDir); if (!actualInputDir) { throw new Error(`App directory not found. Searched in: ${config.inputDir}`); } console.log(`Scanning directory: ${actualInputDir}`); // Find all directories with page.tsx files const pageFiles = await (0, glob_1.glob)('**/page.{tsx,ts,jsx,js}', { cwd: actualInputDir, ignore: ['node_modules/**'] }); console.log(`Found ${pageFiles.length} page files`); const routes = await buildRouteTree(pageFiles, actualInputDir); // Check if any routes were found if (Object.keys(routes).length === 0) { console.warn('No routes with route.json configuration found!'); console.log('Make sure to add route.json files in your page directories with:'); console.log('{ "name": "YourRouteName", "groupName": "OptionalGroupName" }'); } // Ensure output directory exists if (!fs.existsSync(config.outputDir)) { fs.mkdirSync(config.outputDir, { recursive: true }); } const outputPath = path.join(config.outputDir, config.filename); const content = generateRouteConstantsFile(routes); fs.writeFileSync(outputPath, content, 'utf-8'); console.log(`Route constants generated at: ${outputPath}`); } async function loadConfig(configPath) { if (!configPath) { configPath = 'route-constants.config.js'; } const configFiles = [ configPath, path.resolve(process.cwd(), configPath), path.resolve(process.cwd(), 'route-constants.config.js'), path.resolve(process.cwd(), 'route-constants.config.ts') ]; for (const configFile of configFiles) { if (fs.existsSync(configFile)) { try { const config = require(configFile); return config.default || config; } catch (error) { console.warn(`Failed to load config from ${configFile}:`, error); } } } // Try to load from next.config.js if available try { const nextConfigPath = path.resolve(process.cwd(), 'next.config.js'); if (fs.existsSync(nextConfigPath)) { const nextConfig = require(nextConfigPath); const config = {}; // Extract relevant config from next.config.js if (nextConfig.experimental?.appDir !== undefined) { config.inputDir = './src/app'; } return config; } } catch (error) { // Ignore next.config.js parsing errors } return {}; } async function findAppDirectory(inputDir) { const possiblePaths = [ path.resolve(process.cwd(), inputDir), path.resolve(process.cwd(), 'src/app'), path.resolve(process.cwd(), 'app'), // Monorepo support - look in parent directories path.resolve(process.cwd(), '../src/app'), path.resolve(process.cwd(), '../../src/app'), ]; for (const dirPath of possiblePaths) { if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) { return dirPath; } } return null; } async function buildRouteTree(pageFiles, appDir) { const routes = {}; for (const pageFile of pageFiles) { const routePath = path.dirname(pageFile); const routeConfigPath = path.join(appDir, routePath, 'route.json'); // Check if route.json exists if (!fs.existsSync(routeConfigPath)) { continue; } // Get route config from route.json const routeConfig = await getRouteConfigFromFile(routeConfigPath); // Skip if no valid config found if (!routeConfig || !routeConfig.name) { continue; } // Check for inherited groupName from parent directories const inheritedGroupName = await getInheritedGroupName(routePath, appDir); const finalGroupName = routeConfig.groupName || inheritedGroupName; const segments = routePath === '.' ? [] : routePath.split(path.sep); // Build the actual route path const urlPath = buildUrlPath(segments); // Build nested object structure based on final groupName const finalConfig = { name: routeConfig.name, groupName: finalGroupName }; buildNestedRoute(routes, segments, urlPath, finalConfig); } return routes; } function buildUrlPath(segments) { const cleanSegments = segments .filter(segment => { // Skip route groups (segments wrapped in parentheses) return !segment.startsWith('(') || !segment.endsWith(')'); }) .map(segment => { // Handle dynamic routes - keep as is for now return segment; }); return '/' + cleanSegments.join('/'); } function buildNestedRoute(routes, segments, urlPath, routeConfig) { // If groupName is specified, use it for grouping if (routeConfig.groupName) { // Create nested structure with groupName if (!routes[routeConfig.groupName]) { routes[routeConfig.groupName] = {}; } if (typeof routes[routeConfig.groupName] === 'object') { routes[routeConfig.groupName][routeConfig.name] = urlPath; } } else { // No grouping, add directly to root routes[routeConfig.name] = urlPath; } } async function getInheritedGroupName(routePath, appDir) { if (routePath === '.') { return undefined; } const segments = routePath.split(path.sep); // Check parent directories from immediate parent up to root for (let i = segments.length - 1; i >= 0; i--) { const parentPath = segments.slice(0, i).join(path.sep) || '.'; const parentConfigPath = path.join(appDir, parentPath, 'route.json'); if (fs.existsSync(parentConfigPath)) { const parentConfig = await getRouteConfigFromFile(parentConfigPath); if (parentConfig?.groupName) { return parentConfig.groupName; } } } return undefined; } async function getRouteConfigFromFile(configPath) { try { const content = fs.readFileSync(configPath, 'utf-8'); const config = JSON.parse(content); // Validate required fields if (!config.name || typeof config.name !== 'string') { console.warn(`Invalid route.json at ${configPath}: name field is required`); return null; } return config; } catch (error) { console.warn(`Failed to read route.json at ${configPath}:`, error); return null; } } function generateRouteConstantsFile(routes) { return `// This file is auto-generated. Do not edit manually. // Generated on ${new Date().toISOString()} // Routes are configured via route.json files in each page directory export const ROUTES = ${JSON.stringify(routes, null, 2)} as const; export type RouteKeys = keyof typeof ROUTES; `; } //# sourceMappingURL=index.js.map