diff --git a/development/builder.js b/development/builder.js index 757cf7e492..9a2f025994 100644 --- a/development/builder.js +++ b/development/builder.js @@ -1,5 +1,6 @@ const fs = require('fs'); const pathUtil = require('path'); +const sizeOfImage = require('image-size'); const renderTemplate = require('./render-template'); const compatibilityAliases = require('./compatibility-aliases'); @@ -7,8 +8,6 @@ const compatibilityAliases = require('./compatibility-aliases'); * @typedef {'development'|'production'|'desktop'} Mode */ -const IMAGE_EXTENSIONS = ['png', 'jpg', 'svg']; - /** * Recursively read a directory. * @param {string} directory @@ -52,6 +51,10 @@ class DiskFile { read () { return fs.readFileSync(this.path); } + + validate () { + // no-op + } } class ExtensionFile extends DiskFile { @@ -63,10 +66,12 @@ class ExtensionFile extends DiskFile { // TODO: we can add some code to eg. show a message when the extension was modified on disk? } -class HTMLFile { +class HTMLFile extends DiskFile { constructor (path, data) { - this.path = path; + super(path); this.data = data; + // force development server to use read() + this.getDiskPath = null; } getType () { @@ -78,6 +83,33 @@ class HTMLFile { } } +class ImageFile extends DiskFile { + validate () { + const contents = this.read(); + const {width, height} = sizeOfImage(contents); + const aspectRatio = width / height; + if (aspectRatio !== 2) { + throw new Error(`Aspect ratio must be exactly 2, but found ${aspectRatio.toFixed(4)} (${width}x${height})`); + } + } +} + +class SVGFile extends ImageFile { + validate () { + const contents = this.read(); + if (contents.includes(' elements -- please convert the text to a path. This ensures it will display correctly on all devices.'); + } + + super.validate(); + } +} + +const IMAGE_FORMATS = new Map(); +IMAGE_FORMATS.set('.png', ImageFile); +IMAGE_FORMATS.set('.jpg', ImageFile); +IMAGE_FORMATS.set('.svg', SVGFile); + class Build { constructor () { this.files = {}; @@ -134,14 +166,16 @@ class Builder { const images = {}; for (const [imageFilename, path] of readDirectory(this.imagesRoot)) { - if (!IMAGE_EXTENSIONS.some(extension => imageFilename.endsWith(`.${extension}`))) { + const extension = pathUtil.extname(imageFilename); + const ImageFileClass = IMAGE_FORMATS.get(extension); + if (!ImageFileClass) { continue; } const extensionId = imageFilename.split('.')[0]; if (extensionId !== 'unknown') { images[extensionId] = imageFilename; } - build.files[`/images/${imageFilename}`] = new DiskFile(path); + build.files[`/images/${imageFilename}`] = new ImageFileClass(path); } const extensionFiles = []; @@ -212,6 +246,22 @@ class Builder { callback(this.tryBuild()); }); } + + validate () { + const errors = []; + const build = this.build(); + for (const [fileName, file] of Object.entries(build.files)) { + try { + file.validate(); + } catch (e) { + errors.push({ + fileName, + error: e + }); + } + } + return errors; + } } module.exports = Builder; diff --git a/development/colors.js b/development/colors.js new file mode 100644 index 0000000000..6dc21194d1 --- /dev/null +++ b/development/colors.js @@ -0,0 +1,9 @@ +const enableColor = !process.env.NO_COLOR; +const color = (i) => enableColor ? i : ''; + +module.exports = { + RESET: color('\x1b[0m'), + BOLD: color('\x1b[1m'), + RED: color('\x1b[31m'), + GREEN: color('\x1b[32m') +}; diff --git a/development/validate.js b/development/validate.js index 79f37a5eee..e9386baebe 100644 --- a/development/validate.js +++ b/development/validate.js @@ -1 +1,18 @@ -require('./build-production'); +const Builder = require('./builder'); +const Colors = require('./colors'); + +const builder = new Builder('production'); +const errors = builder.validate(); + +if (errors.length === 0) { + console.log(`${Colors.GREEN}${Colors.BOLD}Validation checks passed.${Colors.RESET}`); + process.exit(0); +} else { + console.error(`${Colors.RED}${Colors.BOLD}${errors.length} ${errors.length === 1 ? 'file' : 'files'} failed validation.${Colors.RESET}`); + console.error(''); + for (const {fileName, error} of errors) { + console.error(`${Colors.BOLD}${fileName}${Colors.RESET}: ${error}`); + } + console.error(``); + process.exit(1); +} diff --git a/images/clouddata-ping.svg b/images/clouddata-ping.svg index 274b7585c3..6eae7df964 100644 --- a/images/clouddata-ping.svg +++ b/images/clouddata-ping.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/images/godslayerakp/http.png b/images/godslayerakp/http.png deleted file mode 100644 index cc0d60026e..0000000000 Binary files a/images/godslayerakp/http.png and /dev/null differ diff --git a/images/utilities.svg b/images/utilities.svg index aeeda95117..ab0ce25a11 100644 --- a/images/utilities.svg +++ b/images/utilities.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 120c844694..7362fb5480 100644 --- a/package-lock.json +++ b/package-lock.json @@ -127,7 +127,7 @@ }, "@turbowarp/types": { "version": "git+https://github.com/TurboWarp/types-tw.git#59febbb5494a25898defda095ebe306a747974dd", - "from": "git+https://github.com/TurboWarp/types-tw.git#59febbb5494a25898defda095ebe306a747974dd" + "from": "git+https://github.com/TurboWarp/types-tw.git#tw" }, "accepts": { "version": "1.3.8", @@ -806,6 +806,14 @@ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, + "image-size": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", + "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", + "requires": { + "queue": "6.0.2" + } + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -1124,6 +1132,14 @@ "side-channel": "^1.0.4" } }, + "queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "requires": { + "inherits": "~2.0.3" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", diff --git a/package.json b/package.json index 562be34f1c..faffa558b0 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "@turbowarp/types": "git+https://github.com/TurboWarp/types-tw.git#tw", "chokidar": "^3.5.3", "ejs": "^3.1.9", - "express": "^4.18.2" + "express": "^4.18.2", + "image-size": "^1.0.2" }, "devDependencies": { "eslint": "^8.43.0" diff --git a/website/index.ejs b/website/index.ejs index 653cd4d146..1e620f2d0d 100644 --- a/website/index.ejs +++ b/website/index.ejs @@ -688,8 +688,8 @@
<%- banner('godslayerakp/http') %> -

http/https

-

Extension HTTP extension. Created by RedMan13.

+

HTTP

+

Comprehensive extension for interacting with external websites. Created by RedMan13.