338 lines
14 KiB
JavaScript
338 lines
14 KiB
JavaScript
const fs = require("fs");
|
|
const path = require("path");
|
|
const csv = require("csv-parser");
|
|
const React = require("react");
|
|
const dotenv = require("dotenv");
|
|
const ReactDOMServer = require("react-dom/server");
|
|
const TemplatePageModule = require("./build/components/jamtracks/JKJamTracksLandingTemplatePage");
|
|
const ArtistTemplatePageModule = require("./build/components/jamtracks/JKJamTracksArtistLandingTemplatePage");
|
|
|
|
var csvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui.${process.env.USER}.csv`
|
|
var artistCsvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui_artists.${process.env.USER}.csv`
|
|
|
|
var sitemapPath = path.join(__dirname, "..", "public", "sitemap.xml");
|
|
const clear_sitemap = () => {
|
|
fs.writeFileSync(sitemapPath, "");
|
|
|
|
// Add the root element
|
|
fs.writeFileSync(sitemapPath, `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n`, { flag: 'a' });
|
|
|
|
// Add the root url
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}</loc></url>\n`, { flag: 'a' });
|
|
|
|
// Add standard URLs specific to this site, such as:
|
|
// All prefix with /public
|
|
|
|
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/</loc></url>\n`, { flag: 'a' });
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/privacy</loc></url>\n`, { flag: 'a' } );
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/help</loc></url>\n`, { flag: 'a' } );
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/knowledge-base</loc></url>\n`, { flag: 'a' } );
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/help-desk</loc></url>\n`, { flag: 'a' } );
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/forum</loc></url>\n`, { flag: 'a' } );
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/unsubscribe</loc></url>\n`, { flag: 'a' } );
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/downloads</loc></url>\n`, { flag: 'a' } );
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/downloads-legacy</loc></url>\n`, { flag: 'a' } );
|
|
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/login</loc></url>\n`, { flag: 'a' });
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/signup</loc></url>\n`, { flag: 'a' } );
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/forget-password</loc></url>\n`, { flag: 'a' } );
|
|
|
|
// Add the closing root element
|
|
}
|
|
|
|
const add_song_to_sitemap = (artistSlug, songSlug) => {
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/backing-tracks/${artistSlug}/${songSlug}</loc></url>\n`, { flag: 'a' });
|
|
}
|
|
|
|
const add_artist_to_sitemap = (artistSlug) => {
|
|
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/backing-tracks/${artistSlug}</loc></url>\n`, { flag: 'a' });
|
|
}
|
|
|
|
const close_sitemap = () => {
|
|
fs.writeFileSync(sitemapPath, "</urlset>", { flag: 'a' } );
|
|
}
|
|
|
|
/**
|
|
* Loads a CSV file into an array of objects.
|
|
* @param {string} csvPath - The path to the CSV file.
|
|
* @returns {Promise<Array<Object>>} - A promise that resolves with the parsed CSV data.
|
|
*/
|
|
const load_csv = (csvPath) => {
|
|
return new Promise((resolve, reject) => {
|
|
const results = [];
|
|
fs.createReadStream(csvPath)
|
|
.pipe(csv())
|
|
.on('data', (data) => results.push(data))
|
|
.on('end', () => resolve(results))
|
|
.on('error', (error) => reject(error));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Finds all songs for a given artist based on the original_artist_slug
|
|
* and sorts them alphabetically by the `slug` field.
|
|
* @param {string} artistSlug - The original_artist_slug to match.
|
|
* @param {Array<Object>} songsCsv - The songs CSV data.
|
|
* @returns {Array<Object>} - A sorted array of matching song objects.
|
|
*/
|
|
const collect_songs_for_artist = (artistSlug, songsCsv) => {
|
|
return songsCsv
|
|
.filter((song) => song.original_artist_slug === artistSlug)
|
|
.sort((a, b) => a.slug.localeCompare(b.slug)); // Sort alphabetically by slug
|
|
};
|
|
|
|
|
|
const init = () => {
|
|
const node_env = process.env.NODE_ENV || 'development';
|
|
const environment = process.env.ENVIRONMENT || 'development';
|
|
console.log(`environment=${environment} node_env=${node_env}`);
|
|
console.log(dotenv.config({ path: `../.env.${environment}` }));
|
|
|
|
if (environment === "production" || environment === "staging") {
|
|
csvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui.${environment}.csv`;
|
|
artistCsvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui_artists.${environment}.csv`;
|
|
}
|
|
|
|
console.log("Song csv file", csvFilePath);
|
|
console.log("Artist csv file", artistCsvFilePath);
|
|
|
|
if (!process.env.PUBLIC_URL) {
|
|
console.log("setting public url", process.env.REACT_APP_BASE_URL);
|
|
process.env.PUBLIC_URL = process.env.REACT_APP_BASE_URL;
|
|
}
|
|
|
|
clear_sitemap();
|
|
|
|
|
|
//const __dirname = path.resolve(path.dirname(''));
|
|
|
|
console.log("init done successfully")
|
|
}
|
|
|
|
const generateSongPages = async (render) => {
|
|
const rows = [];
|
|
|
|
const OUTPUT_DIR = path.join(__dirname, "..", "public", "backing-tracks");
|
|
|
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
}
|
|
|
|
console.log("generatPages starting")
|
|
|
|
|
|
const TemplatePage = TemplatePageModule.default;
|
|
|
|
fs.createReadStream(csvFilePath)
|
|
.pipe(csv())
|
|
.on("data", (row) => rows.push(row))
|
|
.on("end", async () => {
|
|
console.log(`Processing ${rows.length} rows...`);
|
|
|
|
for (const row of rows) {
|
|
// id, original_artist, name, original_artist_slug, name_slug, plan_code, slug, URL, licensor, vendor_id
|
|
const { id, original_artist, name, original_artist_slug, name_slug, plan_code, slug, allow_free } = row;
|
|
const artist = original_artist;
|
|
const song = name;
|
|
const location = `/backing-tracks/${original_artist_slug}/${name_slug}`;
|
|
const fullPath = process.env.REACT_APP_BASE_URL + location;
|
|
const logoPath = process.env.REACT_APP_BASE_URL + "/favicon.svg";
|
|
|
|
add_song_to_sitemap(original_artist_slug, name_slug);
|
|
|
|
console.log(`Generating ${artist} - ${song}`);
|
|
|
|
const html = render
|
|
? ReactDOMServer.renderToStaticMarkup(
|
|
React.createElement(TemplatePage, { id, plan_code, slug, artist, song, location })
|
|
)
|
|
: "";
|
|
const fullHtml = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<link rel="shortcut icon" href="/favicon.svg">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>${artist} - ${song} - Free Backing Track</title>
|
|
<link rel="stylesheet" href="${process.env.REACT_APP_BASE_URL}/css/theme.css">
|
|
<meta name="description" content="Get free ${song} by ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
|
|
<meta name="keywords" content="Backing Track, ${artist}, ${song}, Instrumental">
|
|
<meta name="author" content="JamKazam">
|
|
|
|
<!-- Open Graph (Facebook, LinkedIn, etc.) -->
|
|
<meta property="og:title" content="${artist} - ${song} | Free Backing Track">
|
|
<meta property="og:description" content="Get free ${song} by ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
|
|
<meta property="og:image" content="${logoPath}">
|
|
<meta property="og:url" content="${fullPath}">
|
|
<meta property="og:type" content="music.song">
|
|
|
|
<!-- Twitter Cards -->
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="${artist} - ${song} | Free Backing Track">
|
|
<meta name="twitter:description" content="Get free ${song} by ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
|
|
<meta name="twitter:image" content="${logoPath}">
|
|
|
|
<!-- Canonical URL -->
|
|
<link rel="canonical" href="${fullPath}" />
|
|
|
|
<!-- Structured Data (Schema.org) -->
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "MusicRecording",
|
|
"name": "${song}",
|
|
"byArtist": {
|
|
"@type": "MusicGroup",
|
|
"name": "${artist}"
|
|
},
|
|
"url": "${fullPath}",
|
|
"image": "${logoPath}"
|
|
}
|
|
</script>
|
|
<script>
|
|
window.jamtrack_data = {
|
|
id: "${id}",
|
|
plan_code: "${plan_code}",
|
|
slug: "${slug}",
|
|
artist: "${artist}",
|
|
song: "${song}",
|
|
location: "${location}"
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div id="root">${html}</div>
|
|
<script src="/js/client-hydrate.bundle.js"></script>
|
|
</body>
|
|
</html>`;
|
|
|
|
const ARTIST_DIR = path.join(OUTPUT_DIR, original_artist_slug);
|
|
|
|
if (!fs.existsSync(ARTIST_DIR)) {
|
|
fs.mkdirSync(ARTIST_DIR, { recursive: false });
|
|
}
|
|
|
|
const finalOutputPath = process.env.NODE_ENV === "development" ? `${name_slug}.html` : `${name_slug}.html`;
|
|
const outputFilePath = path.join(ARTIST_DIR, finalOutputPath);
|
|
fs.writeFileSync(outputFilePath, fullHtml);
|
|
console.log(`Generated: ${outputFilePath}`);
|
|
}
|
|
|
|
console.log("All pages generated!");
|
|
});
|
|
};
|
|
|
|
const generateArtistPages = async (render) => {
|
|
const rows = [];
|
|
|
|
const OUTPUT_DIR = path.join(__dirname, "..", "public", "backing-tracks");
|
|
|
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
}
|
|
|
|
console.log("generatPages starting")
|
|
|
|
|
|
const TemplatePage = TemplatePageModule.default;
|
|
|
|
const songs_csv = await load_csv(csvFilePath);
|
|
|
|
fs.createReadStream(artistCsvFilePath)
|
|
.pipe(csv())
|
|
.on("data", (row) => rows.push(row))
|
|
.on("end", async () => {
|
|
console.log(`Processing ${rows.length} rows...`);
|
|
|
|
for (const row of rows) {
|
|
const { original_artist, original_artist_slug, url } = row;
|
|
const artist = original_artist;
|
|
|
|
const matchingSongs = collect_songs_for_artist(original_artist_slug, songs_csv);
|
|
console.log(`Found ${matchingSongs.length} songs for ${artist}`);
|
|
|
|
const location = `/backing-tracks/${original_artist_slug}`;
|
|
const fullPath = process.env.REACT_APP_BASE_URL + location;
|
|
const logoPath = process.env.REACT_APP_BASE_URL + "/favicon.svg";
|
|
|
|
add_artist_to_sitemap(original_artist_slug);
|
|
console.log(`Generating ${artist}`);
|
|
|
|
const songs = matchingSongs.map((song) => {
|
|
return {
|
|
name: song.name,
|
|
plan_code: song.plan_code,
|
|
url: process.env.REACT_APP_BASE_URL + "/backing-tracks/" + song.original_artist_slug + "/" + song.name_slug
|
|
}
|
|
});
|
|
const html = render
|
|
? ReactDOMServer.renderToStaticMarkup(
|
|
React.createElement(ArtistTemplatePage, { artist, original_artist_slug, location })
|
|
)
|
|
: "";
|
|
const fullHtml = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<link rel="shortcut icon" href="/favicon.svg">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>${artist} - Free Backing Track</title>
|
|
<link rel="stylesheet" href="${process.env.REACT_APP_BASE_URL}/css/theme.css">
|
|
<meta name="description" content="Get a free ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
|
|
<meta name="keywords" content="Backing Track, ${artist}, Instrumental">
|
|
<meta name="author" content="JamKazam">
|
|
|
|
<!-- Open Graph (Facebook, LinkedIn, etc.) -->
|
|
<meta property="og:title" content="${artist} | Free Backing Track">
|
|
<meta property="og:description" content="Get a free ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
|
|
<meta property="og:image" content="${logoPath}">
|
|
<meta property="og:url" content="${fullPath}">
|
|
<meta property="og:type" content="music.song">
|
|
|
|
<!-- Twitter Cards -->
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="${artist} | Free Backing Track">
|
|
<meta name="twitter:description" content="Get a free ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
|
|
<meta name="twitter:image" content="${logoPath}">
|
|
|
|
<!-- Canonical URL -->
|
|
<link rel="canonical" href="${fullPath}" />
|
|
|
|
<script>
|
|
window.jamtrack_data = {
|
|
artist: "${artist}",
|
|
original_artist_slug: "${original_artist_slug}",
|
|
location: "${location}",
|
|
songs: ${JSON.stringify(songs)}
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div id="root">${html}</div>
|
|
<script src="/js/client-hydrate.bundle.js"></script>
|
|
</body>
|
|
</html>`;
|
|
|
|
const finalOutputPath = process.env.NODE_ENV === "development" ? `${original_artist_slug}.html` : `${original_artist_slug}.html`;
|
|
const outputFilePath = path.join(OUTPUT_DIR, finalOutputPath);
|
|
fs.writeFileSync(outputFilePath, fullHtml);
|
|
console.log(`Generated: ${outputFilePath}`);
|
|
}
|
|
|
|
close_sitemap();
|
|
|
|
console.log("All pages generated!");
|
|
});
|
|
};
|
|
|
|
let render = false;
|
|
if (process.argv.length > 2) {
|
|
render = process.argv[2] === "true" || process.argv[2] === "yes" || process.argv[2] === "1";
|
|
}
|
|
|
|
init()
|
|
generateSongPages(render);
|
|
|
|
generateArtistPages(render);
|