generate-next-route-constants
Version:
Generate route constants for Next.js App Directory
237 lines (235 loc) • 9.27 kB
JavaScript
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
;