diff --git a/deploy.sh b/deploy.sh index 36a596a..9ee27f3 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,42 +1,22 @@ #!/bin/bash # Edit these variables before deploying -DOMAIN="os.prozilla.dev" COMMIT_MESSAGE="Deployed build to GitHub Pages" REPO_URL="https://github.com/Prozilla/ProzillaOS" # ------- You don't need to edit anything below this line ------- -echo -e "Domain: \e[0;36m$DOMAIN\e[0m" +echo -e "\e[0;33mDeploying to GitHub Pages...\e[0m" + +domain=$(cat dist/CNAME) + +echo -e "Domain: \e[0;36m$domain\e[0m" echo -e "Commit message: \e[0;36m$COMMIT_MESSAGE\e[0m" -echo -e "Repository: \e[0;36m$REPO_URL\e[0m\n" - -echo -e "\e[0;33mConfiguring routing...\e[0m" -echo $DOMAIN > dist/CNAME -cp dist/index.html dist/404.html - -# Use template and pages data to generate index.html files -input="tmp/pages.csv" -template="tmp/template.html" -while IFS=";" read -r app_id app_name app_description -do - path="dist/$app_id/index.html" - echo -e "- dist/\e[0;36m$app_id/index.html\e[0m" - - mkdir -p "dist/$app_id" - cp $template $path - - # Fill template - sed -i "s/_APP_NAME/$app_name/g" $path - sed -i "s/_APP_DESCRIPTION/$app_description/g" $path - sed -i "s/_APP_ID/$app_id/g" $path -done < <(tail -n +2 $input) -rm -r tmp +echo -e "Repository: \e[0;36m$REPO_URL\e[0m" echo "" -echo -e "\e[0;33mDeploying to GitHub Pages...\e[0m" -if gh-pages -d dist -m $COMMIT_MESSAGE -r $REPO_URL ; then - echo -e "\e[0;32m✓ Successfully deployed to \e[0;36mhttps://$DOMAIN/\e[0m" +if gh-pages -x -d dist -m $COMMIT_MESSAGE -r $REPO_URL ; then + echo -e "\e[0;32m✓ Successfully deployed to \e[0;36mhttps://$domain/\e[0m" else echo -e "\e[0;31mFailed to deploy\e[0m" fi \ No newline at end of file diff --git a/index.html b/index.html index 3d27366..5267eb2 100644 --- a/index.html +++ b/index.html @@ -18,7 +18,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -53,7 +53,7 @@ "mainEntity": [ { "@type": "Question", - "name": "Is ProzillaOS open source?", + "name": "Is ProzillaOS open-source?", "acceptedAnswer": { "@type": "Answer", "text": "Yes, the source code for ProzillaOS is hosted on GitHub." @@ -70,6 +70,8 @@ ] } + + diff --git a/package.json b/package.json index 46165b6..ae68dbb 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,10 @@ "scripts": { "start": "vite --port 3000 --host", "build": "tsc && vite build", - "serve": "vite preview", - "predeploy": "npm run build && npm run prep", - "deploy": "sh deploy.sh", - "prep": "node --no-warnings --loader ts-node/esm ./src/tools/prep" + "serve": "vite preview --port 8080 --host", + "predeploy": "npm run build && npm run stage", + "stage": "node --no-warnings --loader ts-node/esm ./src/tools/stage", + "deploy": "sh deploy.sh" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.4.0", diff --git a/public/documents/info.md b/public/documents/info.md index b6099bc..9f8ad5b 100644 --- a/public/documents/info.md +++ b/public/documents/info.md @@ -4,9 +4,9 @@ This is ProzillaOS, a web-based operating system made with React.js by [Prozilla](https://prozilla.dev/). -## Open Source +## Open-source -ProzillaOS is [open source](https://github.com/Prozilla/ProzillaOS)! Feel free to fork this project and create your own OS or share feedback by creating an issue on the GitHub page. +ProzillaOS is [open-source](https://github.com/Prozilla/ProzillaOS)! Feel free to fork this project and create your own OS or share feedback by creating an issue on the GitHub page. ## Support ProzillaOS diff --git a/public/robots.txt b/public/robots.txt deleted file mode 100644 index b8f395a..0000000 --- a/public/robots.txt +++ /dev/null @@ -1,4 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: -Sitemap: https://os.prozilla.dev/sitemap.xml \ No newline at end of file diff --git a/src/config/branding.config.ts b/src/config/branding.config.ts index f5ad4b5..b131482 100644 --- a/src/config/branding.config.ts +++ b/src/config/branding.config.ts @@ -2,7 +2,8 @@ import { ANSI } from "./apps/terminal.config"; export const NAME = "ProzillaOS"; export const TAG_LINE = "Web-based Operating System"; -export const BASE_URL = "https://os.prozilla.dev/"; +export const DOMAIN = "os.prozilla.dev"; +export const BASE_URL = `https://${DOMAIN}/`; export const ASCII_LOGO = ` :. diff --git a/src/tools/prep.ts b/src/tools/prep.ts deleted file mode 100644 index a3ea698..0000000 --- a/src/tools/prep.ts +++ /dev/null @@ -1,95 +0,0 @@ -import fs from "node:fs"; -import { APP_DESCRIPTIONS, APP_NAMES, APPS } from "../config/apps.config"; -import { ANSI } from "../config/apps/terminal.config"; -import { BASE_URL, NAME, TAG_LINE } from "../config/branding.config"; -import { WALLPAPERS } from "../config/desktop.config"; - -const PATH_TO_SITEMAP = "dist/sitemap.xml"; -const PATH_TO_PAGES = "tmp/pages.csv"; -const PATH_TO_INDEX = "dist/index.html"; -const PATH_TO_TEMPLATE = "tmp/template.html"; - -function generateSitemap() { - const date = new Date(); - const lastModified = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; - - const images = WALLPAPERS.map((path) => ` - - ${BASE_URL.slice(0, -1) + path} - ` - ); - - const pages = Object.values(APPS).map((appId) => ` - - ${BASE_URL + appId} - ${lastModified} - ` - ); - - const sitemap = ` - - - - - ${BASE_URL} - ${lastModified} - ${images.join("")} - - ${pages.join("")} -`; - - return sitemap.trim(); -} - -function generatePageRecords() { - const records = ["id;name;description"]; - - for (const [key, value] of Object.entries(APPS)) { - const id = value; - const name = (Object.keys(APP_NAMES).includes(key) ? APP_NAMES[key] : id) as string; - const description = (Object.keys(APP_DESCRIPTIONS).includes(key) ? APP_DESCRIPTIONS[key] : TAG_LINE) as string; - - records.push(`${id};${name};${description}`); - } - - return records.join("\n") + "\n"; -} - -function generatePageTemplate() { - let html = fs.readFileSync(PATH_TO_INDEX, "utf-8"); - - const titleRegex = /(?<=(|<meta property="og:title" content="|<meta name="twitter:title" content="))(([a-zA-Z|-]|\s)+)(?=(<\/title>|"\/?>))/g; - html = html.replaceAll(titleRegex, `_APP_NAME | ${NAME}`); - - const descriptionRegex = /(?<=(<meta name="description" content="|<meta property="og:description" content="|<meta name="twitter:description" content="))(([a-zA-Z-.]|\s)+)(?=("\/?>))/g; - html = html.replaceAll(descriptionRegex, "_APP_DESCRIPTION"); - - const canonicalRegex = /(?<=(<link rel="canonical" href="|<meta name="twitter:url" content="|<meta property="og:url" content="))(http(s)?:\/\/[a-zA-Z-.]+\/)(?=("\/?>))/g; - html = html.replaceAll(canonicalRegex, `${BASE_URL}_APP_ID`); - - const faqRegex = /<!-- FAQ -->.*?<script type="application\/ld\+json">.*?<\/script>/gs; - html = html.replaceAll(faqRegex, ""); - - return html; -} - -try { - const steps: [string, () => string][] = [ - [PATH_TO_SITEMAP, generateSitemap], - [PATH_TO_TEMPLATE, generatePageTemplate], - [PATH_TO_PAGES, generatePageRecords], - ]; - - steps.forEach(([path, generateContent]) => { - const directory = path.substring(0, path.lastIndexOf("/")); - if (directory != "" && !fs.existsSync(directory)){ - fs.mkdirSync(directory, { recursive: true }); - } - - fs.writeFileSync(path, generateContent(), { flag: "w+" }); - console.log(`${ANSI.fg.green}✓ Generated ${path}${ANSI.reset}`); - }); -} catch (error) { - console.error(error); -} \ No newline at end of file diff --git a/src/tools/stage.ts b/src/tools/stage.ts new file mode 100644 index 0000000..7b5d03e --- /dev/null +++ b/src/tools/stage.ts @@ -0,0 +1,129 @@ +import fs from "node:fs"; +import { APP_DESCRIPTIONS, APP_NAMES, APPS } from "../config/apps.config"; +import { ANSI } from "../config/apps/terminal.config"; +import { BASE_URL, DOMAIN, NAME, TAG_LINE } from "../config/branding.config"; +import { WALLPAPERS } from "../config/desktop.config"; + +const BUILD_DIR = "dist"; + +const PATHS = { + sitemapXml: BUILD_DIR + "/sitemap.xml", + robotsTxt: BUILD_DIR + "/robots.txt", + indexHtml: BUILD_DIR + "/index.html", + cname: BUILD_DIR + "/CNAME", +}; + +function generateSitemapXml() { + const date = new Date(); + const lastModified = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + + const images = WALLPAPERS.map((path) => ` + <image:image> + <image:loc>${BASE_URL.slice(0, -1) + path}</image:loc> + </image:image>` + ); + + const pages = Object.values(APPS).map((appId) => ` + <url> + <loc>${BASE_URL + appId}</loc> + <lastmod>${lastModified}</lastmod> + </url>` + ); + + const sitemap = ` +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE xml> +<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" + xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"> + <url> + <loc>${BASE_URL}</loc> + <lastmod>${lastModified}</lastmod> + ${images.join("")} + </url> + ${pages.join("")} +</urlset>`; + + return sitemap.trim(); +} + +function generateRobotsTxt() { + const sitemapUrl = BASE_URL + PATHS.sitemapXml.replace(BUILD_DIR + "/", ""); + + return `# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: +Sitemap: ${sitemapUrl}`; +} + +function generateCname() { + return DOMAIN; +} + +/** + * To avoid GitHub pages rendering certain pages that are only defined by React router as a 404 page, + * we copy the content of our index file to the 404 page, letting React router properly handle routing on every page + */ +function generate404Page(template: string) { + const path = `${BUILD_DIR}/404.html`; + fs.writeFileSync(path, template, { flag: "w+" }); + console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`); +} + +function generateAppPages(template: string) { + for (const [key, value] of Object.entries(APPS)) { + const appId = value; + const appName = Object.keys(APP_NAMES).includes(key) ? APP_NAMES[key] as string : appId; + const appDescription = Object.keys(APP_DESCRIPTIONS).includes(key) ? APP_DESCRIPTIONS[key] as string : TAG_LINE; + + if (appId === "index") { + console.log("Invalid app ID found: " + appId); + return; + } + + let html = template; + + const titleRegex = /(?<=(<title>|<meta property="og:title" content="|<meta name="twitter:title" content="))(([a-zA-Z|-]|\s)+)(?=(<\/title>|"\/?>))/g; + html = html.replaceAll(titleRegex, `${appName} | ${NAME}`); + + const descriptionRegex = /(?<=(<meta name="description" content="|<meta property="og:description" content="|<meta name="twitter:description" content="))(([a-zA-Z-.]|\s)+)(?=("\/?>))/g; + html = html.replaceAll(descriptionRegex, appDescription); + + const canonicalRegex = /(?<=(<link rel="canonical" href="|<meta name="twitter:url" content="|<meta property="og:url" content="))(http(s)?:\/\/[a-zA-Z-.]+\/)(?=("\/?>))/g; + html = html.replaceAll(canonicalRegex, BASE_URL + appId); + + const faqRegex = /<!-- FAQ -->.*?<script type="application\/ld\+json">.*?<\/script>/gs; + html = html.replaceAll(faqRegex, ""); + + const path = `${BUILD_DIR}/${appId}.html`; + fs.writeFileSync(path, html, { flag: "w+" }); + console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`); + } +} + +try { + console.log(`${ANSI.fg.yellow}Staging build...${ANSI.reset}`); + + const files: [string, () => string][] = [ + [PATHS.sitemapXml, generateSitemapXml], + [PATHS.robotsTxt, generateRobotsTxt], + [PATHS.cname, generateCname], + ]; + + files.forEach(([path, generateContent]) => { + const directory = path.substring(0, path.lastIndexOf("/")); + if (directory != "" && !fs.existsSync(directory)){ + fs.mkdirSync(directory, { recursive: true }); + } + + fs.writeFileSync(path, generateContent(), { flag: "w+" }); + console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`); + }); + + console.log(`\n${ANSI.fg.yellow}Generating pages...${ANSI.reset}`); + const template = fs.readFileSync(PATHS.indexHtml, "utf-8"); + generate404Page(template); + generateAppPages(template); + console.log(`\n${ANSI.fg.green}✓ Staging complete${ANSI.reset}`); +} catch (error) { + console.error(error); +} \ No newline at end of file