Automate Route Creation: Drafts To Dynamic Routes
Hey guys! Ever wish you could streamline the process of turning your hiking or biking drafts into actual, shareable routes? Well, buckle up, because we're about to dive into creating a script that does just that. This script will automatically pick up drafts, generate routes, and even handle all the behind-the-scenes stuff like creating folders, converting files, and generating previews. This will not only save you a ton of time but also help you keep your content organized and consistent. Let's get started and turn those drafts into beautiful, dynamic routes!
Setting the Stage: Project Setup and Dependencies
Before we get our hands dirty with the code, let's make sure we have everything set up correctly. First off, you'll need to have Node.js and npm (or yarn/pnpm) installed on your system. These are the foundations for running our JavaScript script and managing project dependencies. If you don't have them already, head over to the Node.js website and download the latest version.
Next, you'll want to create a new project or navigate to your existing project directory. In your project, you'll need to have the following directories:
src/content/drafts
: This is where your draft files will live. Each draft should be a Markdown or MDX file with metadata, including the route name.src/content/routes
: This is where your finalized route files and associated assets will be stored.scripts
: This is where our main script will reside.
You'll also need to install a few dependencies. We'll be using fs
(file system) and path
modules from Node.js, so you don't need to install anything extra for those. However, you'll need inquirer
for the CLI UI to let users pick drafts. Run the following command to install the required dependencies:
npm install inquirer
Finally, make sure you have the pnpm gpx2json
and pnpm capture:preview
scripts set up in your package.json
. These are crucial for converting GPX files to JSON and generating previews, respectively. Your package.json
should have scripts like these:
{
"scripts": {
"gpx2json": "your-gpx2json-command",
"capture:preview": "your-capture-preview-command"
}
}
With these dependencies installed and project structure in place, we're ready to get to the good stuff: the script itself!
Crafting the Script: Core Logic and Functionality
Alright, let's dive into the heart of our script. We'll break down the code into manageable chunks, making it easier to understand and customize. First, create a file named create-route.js
inside your scripts
directory. This will be the main entry point of our script.
// scripts/create-route.js
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const inquirer = require('inquirer');
const draftsDir = path.join(process.cwd(), 'src/content/drafts');
const routesDir = path.join(process.cwd(), 'src/content/routes');
async function getDrafts() {
// Implement logic to read draft files and extract metadata here
}
async function generateSlug(routeName) {
// Implement logic to generate a slug from the route name here
}
async function checkIfRouteExists(slug) {
// Implement logic to check if a route already exists here
}
async function processDraft(draft, routeName, slug) {
// Implement logic to process a selected draft and create a new route here
}
async function main() {
// Implement the main function to orchestrate the process here
}
main();
This is a basic structure. Now, let's fill in the functions with their specific responsibilities. The getDrafts
function is responsible for reading the drafts directory and extracting the route names from the <metadata>
tags within each file. You'll need to use the fs
module to read the files and potentially a regular expression or a simple parsing library to extract the route name. Be sure to handle potential errors gracefully. This part should get the drafts from the src/content/drafts
directory and extract route names from each draft. The draft files are expected to be Markdown or MDX files. You can read the file contents using fs.readFile
and parse the metadata. The metadata should be enclosed in a specific format, such as frontmatter, so that it can be easily parsed. Return an array of draft objects, each containing the file path and the extracted route name.
async function getDrafts() {
try {
const draftFiles = fs.readdirSync(draftsDir);
const drafts = [];
for (const file of draftFiles) {
if (!file.endsWith('.md') && !file.endsWith('.mdx')) continue;
const filePath = path.join(draftsDir, file);
const fileContent = fs.readFileSync(filePath, 'utf-8');
const match = fileContent.match(/<metadata>\s*<route>(.*?)<\/route>\s*<\/metadata>/s);
if (match && match[1]) {
drafts.push({
filePath,
routeName: match[1].trim(),
});
}
}
return drafts;
} catch (error) {
console.error('Error reading drafts:', error);
return [];
}
}
Next, the generateSlug
function will take a route name as input and generate a URL-friendly slug. The slug should consist of lowercase letters and hyphens instead of spaces. This can be easily achieved using regular expressions and the toLowerCase
method. Be careful to handle edge cases like special characters. Implement the generateSlug
function to convert a route name into a URL-friendly slug. This function should convert the route name to lowercase and replace spaces with hyphens. Remove any special characters to ensure the slug is valid.
async function generateSlug(routeName) {
return routeName
.toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/[^a-z0-9-]/g, ''); // Remove special characters
}
The checkIfRouteExists
function checks if a route with the given slug already exists in the src/content/routes
directory. This is essential to prevent overwriting existing content. It simply checks for the existence of a directory with the given slug name. Return true
if the route already exists, and false
otherwise.
async function checkIfRouteExists(slug) {
const routePath = path.join(routesDir, slug);
try {
fs.accessSync(routePath);
return true;
} catch (error) {
return false;
}
}
The processDraft
function will be the workhorse of our script. It takes a draft file, a route name, and a slug as input. It will:
- Create a new directory for the route inside
src/content/routes
. - Copy the GPX file from the draft directory to the new route directory.
- Generate a new MDX file with the route content.
- Run the
pnpm gpx2json
script to convert the GPX file to a JSON file. - Run the
pnpm capture:preview
script to generate a preview image.
This function utilizes the exec
function to run the pnpm
commands in separate processes, ensuring that the main script doesn't get blocked. It also handles file operations using the fs
module. Create a new directory for the route, copy the GPX file, generate a new MDX file, convert the GPX file to JSON, and generate a preview image. Make sure to use the correct paths and file names for the operations.
async function processDraft(draft, routeName, slug) {
const routeDir = path.join(routesDir, slug);
fs.mkdirSync(routeDir, { recursive: true });
// Copy GPX file - assuming GPX has the same name as the draft
const gpxFileName = draft.filePath.replace(/\.mdx?$/, '.gpx');
const gpxFilePath = path.join(draftsDir, gpxFileName);
try {
fs.accessSync(gpxFilePath);
fs.copyFileSync(gpxFilePath, path.join(routeDir, `${slug}.gpx`));
} catch (error) {
console.warn(`GPX file not found for ${routeName}. Skipping GPX copy.`);
}
// Generate MDX file
const mdxContent = `---\ntitle: ${routeName}\nslug: ${slug}\n---\n\n<!-- Your route content here -->`;
fs.writeFileSync(path.join(routeDir, `${slug}.mdx`), mdxContent);
// Run gpx2json
await new Promise((resolve, reject) => {
exec(`pnpm gpx2json ${path.join(routeDir, `${slug}.gpx`)}`, (error, stdout, stderr) => {
if (error) {
console.error(`gpx2json error: ${stderr}`);
reject(error);
return;
}
console.log(`gpx2json output: ${stdout}`);
resolve();
});
});
// Run capture:preview
await new Promise((resolve, reject) => {
exec(`pnpm capture:preview ${slug}`, { cwd: routeDir }, (error, stdout, stderr) => {
if (error) {
console.error(`capture:preview error: ${stderr}`);
reject(error);
return;
}
console.log(`capture:preview output: ${stdout}`);
resolve();
});
});
console.log(`Route '${routeName}' processed successfully!`);
}
The main
function is the orchestrator. It orchestrates the whole process, including getting the drafts, generating slugs, checking for existing routes, and, if needed, prompting the user to select a draft to process. It is the entry point of the script.
async function main() {
const drafts = await getDrafts();
if (!drafts.length) {
console.log('No drafts found.');
return;
}
const routesToProcess = [];
for (const draft of drafts) {
const slug = await generateSlug(draft.routeName);
const routeExists = await checkIfRouteExists(slug);
if (routeExists) {
console.log(`Route '${draft.routeName}' (slug: ${slug}) already exists. Skipping.`);
} else {
routesToProcess.push({ ...draft, slug });
}
}
if (!routesToProcess.length) {
console.log('All drafts already have existing routes.');
return;
}
if (routesToProcess.length === 1) {
const draft = routesToProcess[0];
await processDraft(draft, draft.routeName, draft.slug);
return;
}
const choices = routesToProcess.map((draft) => ({ name: draft.routeName, value: draft }));
const answers = await inquirer.prompt([
{ type: 'list', name: 'selectedDraft', message: 'Select a draft to process:', choices },
]);
const selectedDraft = answers.selectedDraft;
await processDraft(selectedDraft, selectedDraft.routeName, selectedDraft.slug);
}
Finally, call the main function at the end of the script to start the process. This structure gives you a solid foundation to build upon. Remember to handle errors gracefully and provide informative console messages to keep the user informed about the progress. After running the script, your new route should be created, complete with a GPX file, MDX content, a JSON file, and a preview image, all ready to be shared!
Running the Script: Execution and Testing
Now that you've got your script ready, it's time to run it and see the magic happen. Open your terminal, navigate to your project directory, and execute the following command:
node scripts/create-route.js
Make sure you have a few draft files in your src/content/drafts
directory with the <metadata>
tags, including route names. When you run the script, it will parse these drafts, check for existing routes, and ask you to select one to process if there are drafts that don't have corresponding routes. After selecting a draft, the script will then create the new route. If everything goes well, you should see the new route directory and all associated files created in your src/content/routes
directory. Congratulations, you've successfully automated the creation of new routes!
Enhancements and Customization: Taking it Further
Once you have the base script working, you can expand it with all sorts of additional features. Here are some ideas to spark your creativity:
- Error Handling: Implement more robust error handling throughout the script. Catch potential errors during file reading, writing, and the execution of the
pnpm
commands. Provide helpful error messages to the user to aid debugging. - Configuration: Add a configuration file (e.g.,
config.json
) to store settings like the input and output directories, the commands forgpx2json
andcapture:preview
, and any other customizable parameters. This would make the script more flexible and easier to adapt to different projects. - User Input: Enhance the user interface by incorporating more interactive elements. For example, add the option to preview the route information before creating the route, or to select which files to copy (e.g., GPX, images). You can use the
inquirer
library to create dynamic prompts, offering more control to the user. - File Format Support: Extend the script to handle different file formats, such as GeoJSON or KML for routes, and support for additional file types like images associated with the route. You could also parse different metadata formats.
- Batch Processing: Add a feature to automatically process all eligible drafts without requiring manual selection. You could add a command-line flag or a configuration option to enable this. This will further improve the efficiency of your workflow.
- Notifications: Implement notifications (e.g., using a library like
node-notifier
) to alert the user about the process's progress and outcome. - Testing: Add unit tests to ensure all functions operate correctly. This will help you keep the code reliable as you add features and make changes.
- Logging: Implement proper logging using a logging library to keep a record of all events. This will help you identify issues faster and track the performance of your scripts.
By adding these enhancements, you can create a highly customizable and efficient script that perfectly fits your specific needs. The possibilities are endless, so start experimenting and create a tool that makes your life easier.
Conclusion: Automate, Optimize, and Explore
And there you have it, guys! We've successfully created a script to automate the transformation of your hiking or biking drafts into dynamic routes. Now you can focus on what matters most: exploring the outdoors and sharing your adventures. Remember to adapt the script to your specific project and needs. Experiment with the enhancements we've discussed, and you'll be well on your way to a smoother, more efficient content creation workflow. Happy coding, and happy trails!
I hope this helps you get started! Let me know if you have any other questions. Have fun creating your routes! Remember to back up your code regularly, and always test your changes before deploying them to a live environment. By automating your workflow, you can save valuable time and effort, letting you spend more time doing what you love. Embrace the power of automation, and enjoy the journey!