Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic image validation #688

Merged
merged 5 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.43.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