Automate Route Creation: Drafts To Dynamic Routes

by Dimemap Team 50 views

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 for gpx2json and capture: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!