Skip to content

Commit

Permalink
Automatic image validation (#688)
Browse files Browse the repository at this point in the history
* Validate that images are exactly 2:1 aspect ratio
* Validate that SVGs do not contain <text>
* Update or remove images to pass validation:
/images/clouddata-ping.svg: Error: Aspect ratio must be exactly 2, but found 2.0127 (159x79)
/images/godslayerakp/http.png: Error: Aspect ratio must be exactly 2, but found 1.6129 (600x372)
/images/utilities.svg: Error: Aspect ratio must be exactly 2, but found 2.0127 (159x79)
* Update godslayerakp/http description
  • Loading branch information
GarboMuffin authored Jul 4, 2023
1 parent d374041 commit 5b9aac1
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 13 deletions.
62 changes: 56 additions & 6 deletions development/builder.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
const fs = require('fs');
const pathUtil = require('path');
const sizeOfImage = require('image-size');
const renderTemplate = require('./render-template');
const compatibilityAliases = require('./compatibility-aliases');

/**
* @typedef {'development'|'production'|'desktop'} Mode
*/

const IMAGE_EXTENSIONS = ['png', 'jpg', 'svg'];

/**
* Recursively read a directory.
* @param {string} directory
Expand Down Expand Up @@ -52,6 +51,10 @@ class DiskFile {
read () {
return fs.readFileSync(this.path);
}

validate () {
// no-op
}
}

class ExtensionFile extends DiskFile {
Expand All @@ -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 () {
Expand All @@ -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('<text')) {
throw new Error('SVG must not contain <text> 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 = {};
Expand Down Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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;
9 changes: 9 additions & 0 deletions development/colors.js
Original file line number Diff line number Diff line change
@@ -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')
};
19 changes: 18 additions & 1 deletion development/validate.js
Original file line number Diff line number Diff line change
@@ -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);
}
2 changes: 1 addition & 1 deletion images/clouddata-ping.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed images/godslayerakp/http.png
Binary file not shown.
2 changes: 1 addition & 1 deletion images/utilities.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 17 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.44.0"
Expand Down
4 changes: 2 additions & 2 deletions website/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -688,8 +688,8 @@
<div class="extension">
<%- banner('godslayerakp/http') %>
<h3>http/https</h3>
<p>Extension HTTP extension. Created by <a href="https://scratch.mit.edu/users/RedMan13/">RedMan13</a>.</p>
<h3>HTTP</h3>
<p>Comprehensive extension for interacting with external websites. Created by <a href="https://scratch.mit.edu/users/RedMan13/">RedMan13</a>.</p>
</div>
<div class="extension">
Expand Down

0 comments on commit 5b9aac1

Please sign in to comment.