refactor: Migrate scripts to Typescript

This commit is contained in:
Rodrigo Fernandes 2019-10-07 15:41:38 +01:00
parent a8b9b2b49a
commit ef08c53ba9
No known key found for this signature in database
GPG key ID: 67157D2E3D4258B4
11 changed files with 290 additions and 297 deletions

View file

@ -35,20 +35,20 @@
"node": ">=4"
},
"scripts": {
"build": "tsc",
"lint": "eslint '*/**/*.{js,jsx,ts,tsx}'",
"style": "yarn run lint",
"test": "jest",
"coverage": "jest --collectCoverage",
"coverage-html": "yarn run coverage && open ./coverage/index.html",
"codacy": "cat ./coverage/lcov.info | codacy-coverage",
"release": "yarn run release-css && yarn run release-templates && yarn run release-ts && yarn run release-browser-bundle && yarn run release-website",
"release-css": "./scripts/release-css.sh",
"release-templates": "./scripts/release-templates.sh",
"release-ts": "yarn run build",
"release-browser-bundle": "./scripts/release-browser-bundle.sh",
"release-website": "./scripts/release-website.sh",
"preversion": "yarn run release && yarn run lint && yarn test",
"build": "rm -rf build; yarn run build-scripts && yarn run build-css && yarn run build-templates && yarn run build-library && yarn run build-browser-bundle && yarn run build-website",
"build-scripts": "tsc -p tsconfig.scripts.json",
"build-css": "./scripts/build-css.sh",
"build-templates": "./scripts/build-templates.sh",
"build-library": "tsc -p tsconfig.json",
"build-browser-bundle": "./scripts/build-browser-bundle.sh",
"build-website": "./scripts/build-website.sh",
"preversion": "yarn run build && yarn run lint && yarn test",
"version": "git add -A package.json",
"postversion": "git push && git push --tags"
},
@ -63,8 +63,11 @@
"whatwg-fetch": "^3.0.0"
},
"devDependencies": {
"@types/hogan.js": "^3.0.0",
"@types/jest": "24.0.18",
"@types/mkdirp": "^0.5.2",
"@types/node": "^12.7.2",
"@types/nopt": "^3.0.29",
"@typescript-eslint/eslint-plugin": "2.0.0",
"@typescript-eslint/parser": "2.0.0",
"autoprefixer": "^9.6.0",
@ -86,15 +89,17 @@
"nopt": "^4.0.1",
"postcss-cli": "^6.1.3",
"prettier": "1.18.2",
"terser": "^4.3.8",
"ts-jest": "24.0.2",
"typescript": "^3.6.3",
"terser": "^4.3.8"
"typescript": "^3.6.3"
},
"resolutions": {
"lodash": "4.17.15"
},
"license": "MIT",
"files": [
"build"
"build/commonjs-node",
"build/browser",
"build/css"
]
}

View file

@ -4,7 +4,7 @@ set -e
SCRIPT_DIRECTORY="$( cd "$( dirname "$0" )" && pwd )"
node ${SCRIPT_DIRECTORY}/hulk.js \
node ${SCRIPT_DIRECTORY}/../build/scripts/hulk.js \
--wrapper node \
--variable 'browserTemplates' \
${SCRIPT_DIRECTORY}/../src/templates/*.mustache > ${SCRIPT_DIRECTORY}/../src/diff2html-templates.js

View file

@ -31,7 +31,7 @@ echo "Minifying ${OUTPUT_DEMO_JS} to ${OUTPUT_DEMO_MIN_JS}"
terser ${OUTPUT_DEMO_JS} -c -o ${OUTPUT_DEMO_MIN_JS}
echo "Generating HTMLs from templates ..."
node ${SCRIPT_DIRECTORY}/release-website.js
node ${SCRIPT_DIRECTORY}/../build/scripts/build-website.js
echo "Copying static files ..."
cp -rf ${INPUT_DIR}/img ${OUTPUT_DIR}/

54
scripts/build-website.ts Normal file
View file

@ -0,0 +1,54 @@
#!/usr/bin/env node
import * as fs from "fs";
import * as hogan from "hogan.js";
type OptionsType = {
all: object;
[page: string]: object;
};
const templatesRoot = "website/templates";
const pagesRoot = `${templatesRoot}/pages`;
const options: OptionsType = {
all: {
demoUrl: "demo.html?diff=https://github.com/rtfpessoa/diff2html/pull/106"
},
demo: {
extraClass: "template-index-min"
}
};
function readFile(filePath: string): string {
try {
return fs.readFileSync(filePath, "utf8");
} catch (_ignore) {}
return "";
}
function writeFile(filePath: string, content: string): void {
return fs.writeFileSync(filePath, content);
}
const websitePages = fs.readdirSync(pagesRoot);
const template = hogan.compile(readFile(`${templatesRoot}/template.mustache`));
websitePages.map(page => {
const baseOptions = { ...(options.all || {}), ...(options[page] || {}) };
const pagePartialTemplate = hogan.compile(readFile(`${pagesRoot}/${page}/${page}.partial.mustache`));
const pageAssetsTemplate = hogan.compile(readFile(`${pagesRoot}/${page}/${page}-assets.partial.mustache`));
const pageScriptsTemplate = hogan.compile(readFile(`${pagesRoot}/${page}/${page}-scripts.partial.mustache`));
const pagePartial = pagePartialTemplate.render(baseOptions);
const pageAssets = pageAssetsTemplate.render(baseOptions);
const pageScripts = pageScriptsTemplate.render(baseOptions);
const pageOptions = { ...baseOptions, assets: pageAssets, scripts: pageScripts, content: pagePartial };
const pageHtml = template.render(pageOptions);
writeFile(`docs/${page}.html`, pageHtml);
});

View file

@ -1,212 +0,0 @@
#!/usr/bin/env node
/*
* Copyright 2011 Twitter, Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// dependencies
const path = require("path");
const fs = require("fs");
const hogan = require("hogan.js");
const nopt = require("nopt");
const mkderp = require("mkdirp");
// locals
const specials = ["/", ".", "*", "+", "?", "|", "(", ")", "[", "]", "{", "}", "\\"];
const specialsRegExp = new RegExp("(\\" + specials.join("|\\") + ")", "g");
let options = {
namespace: String,
outputdir: path,
variable: String,
wrapper: String,
version: true,
help: true
};
const shortHand = {
n: ["--namespace"],
o: ["--outputdir"],
vn: ["--variable"],
w: ["--wrapper"],
h: ["--help"],
v: ["--version"]
};
let templates;
// options
options = nopt(options, shortHand);
// escape special regexp characters
function esc(text) {
return text.replace(specialsRegExp, "\\$1");
}
// cyan function for rob
function cyan(text) {
return "\x1B[36m" + text + "\x1B[39m";
}
// check for dirs and correct ext (<3 for windows)
function extractFiles(args) {
const usage =
"\n" +
cyan("USAGE:") +
" hulk [--wrapper wrapper] [--outputdir outputdir] " +
"[--namespace namespace] [--variable variable] FILES\n\n" +
cyan("OPTIONS:") +
" [-w, --wrapper] :: wraps the template (i.e. amd)\n" +
" [-o, --outputdir] :: outputs the templates as individual files to a directory\n\n" +
" [-n, --namespace] :: prepend string to template names\n\n" +
" [-vn, --variable] :: variable name for non-amd wrapper\n\n" +
cyan("EXAMPLE:") +
" hulk --wrapper amd ./templates/*.mustache\n\n" +
cyan("NOTE:") +
' hulk supports the "*" wildcard and allows you to target specific extensions too\n';
let files = [];
if (options.version) {
console.log(require("../package.json").version);
process.exit(0);
}
if (!args.length || options.help) {
console.log(usage);
process.exit(0);
}
args.forEach(function(arg) {
if (/\*/.test(arg)) {
arg = arg.split("*");
files = files.concat(
fs
.readdirSync(arg[0] || ".")
.map(function(f) {
const file = path.join(arg[0], f);
return new RegExp(esc(arg[1]) + "$").test(f) && fs.statSync(file).isFile() && file;
})
.filter(function(f) {
return f;
})
);
return files;
}
if (fs.statSync(arg).isFile()) files.push(arg);
});
return files;
}
// remove utf-8 byte order mark, http://en.wikipedia.org/wiki/Byte_order_mark
function removeByteOrderMark(text) {
if (text.charCodeAt(0) === 0xfeff) {
return text.substring(1);
}
return text;
}
// wrap templates
function wrap(file, name, openedFile) {
switch (options.wrapper) {
case "amd":
return (
"define(" +
(!options.outputdir ? '"' + path.join(path.dirname(file), name) + '", ' : "") +
'[ "hogan.js" ], function(Hogan){ return new Hogan.Template(' +
hogan.compile(openedFile, { asString: 1 }) +
");});"
);
case "node":
var globalObj = "global." + (options.variable || "templates") + '["' + name + '"]';
var globalStmt = globalObj + " = new Hogan.Template(" + hogan.compile(openedFile, { asString: 1 }) + ");";
var nodeOutput = globalStmt;
// if we have a template per file the export will expose the template directly
if (options.outputdir) {
nodeOutput = nodeOutput + "\n" + "module.exports = " + globalObj + ";";
}
return nodeOutput;
default:
return (
(options.variable || "templates") +
'["' +
name +
'"] = new Hogan.Template(' +
hogan.compile(openedFile, { asString: 1 }) +
");"
);
}
}
function prepareOutput(content) {
const variableName = options.variable || "templates";
switch (options.wrapper) {
case "amd":
return content;
case "node":
var nodeExport = "";
// if we have aggregated templates the export will expose the template map
if (!options.outputdir) {
nodeExport = "module.exports = global." + variableName + ";\n";
}
return (
"(function() {\n" +
"if (!!!global." +
variableName +
") global." +
variableName +
" = {};\n" +
'var Hogan = require("hogan.js");' +
content +
"\n" +
nodeExport +
"})();"
);
default:
return "if (!!!" + variableName + ") var " + variableName + " = {};\n" + content;
}
}
// write the directory
if (options.outputdir) {
mkderp.sync(options.outputdir);
}
// Prepend namespace to template name
function namespace(name) {
return (options.namespace || "") + name;
}
// write a template foreach file that matches template extension
templates = extractFiles(options.argv.remain)
.map(function(file) {
let openedFile = fs.readFileSync(file, "utf-8").trim();
let name;
if (!openedFile) return;
name = namespace(path.basename(file).replace(/\..*$/, ""));
openedFile = removeByteOrderMark(openedFile);
openedFile = wrap(file, name, openedFile);
if (!options.outputdir) return openedFile;
fs.writeFileSync(path.join(options.outputdir, name + ".js"), prepareOutput(openedFile));
})
.filter(function(t) {
return t;
});
// output templates
if (!templates.length || options.outputdir) process.exit(0);
console.log(prepareOutput(templates.join("\n")));

191
scripts/hulk.ts Executable file
View file

@ -0,0 +1,191 @@
#!/usr/bin/env node
/*
* Copyright 2011 Twitter, Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as path from "path";
import * as fs from "fs";
import * as hogan from "hogan.js";
import * as nopt from "nopt";
import * as mkderp from "mkdirp";
const options = nopt(
{
namespace: String,
outputdir: path,
variable: String,
wrapper: String,
version: true,
help: true
},
{
n: ["--namespace"],
o: ["--outputdir"],
vn: ["--variable"],
w: ["--wrapper"],
h: ["--help"],
v: ["--version"]
}
);
const specials = ["/", ".", "*", "+", "?", "|", "(", ")", "[", "]", "{", "}", "\\"];
const specialsRegExp = new RegExp("(\\" + specials.join("|\\") + ")", "g");
function escape(text: string): string {
return text.replace(specialsRegExp, "\\$1");
}
function cyan(text: string): string {
return "\x1B[36m" + text + "\x1B[39m";
}
function extractFiles(files: string[]): string[] {
const usage = `
${cyan(
"USAGE:"
)} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES
${cyan("OPTIONS:")} [-w, --wrapper] :: wraps the template (i.e. amd)
[-o, --outputdir] :: outputs the templates as individual files to a directory
[-n, --namespace] :: prepend string to template names
[-vn, --variable] :: variable name for non-amd wrapper
${cyan("EXAMPLE:")} hulk --wrapper amd ./templates/*.mustache
${cyan("NOTE:")} hulk supports the "*" wildcard and allows you to target specific extensions too
`;
if (options.version) {
console.log(require("../package.json").version);
process.exit(0);
}
if (!files.length || options.help) {
console.log(usage);
process.exit(0);
}
const templateFiles = files
.map((fileGlob: string) => {
if (/\*/.test(fileGlob)) {
const [fileGlobPrefix, fileGlobSuffix] = fileGlob.split("*");
const files = fs.readdirSync(fileGlobPrefix || ".").reduce<string[]>((previousFiles, relativeFilePath) => {
const file = path.join(fileGlobPrefix, relativeFilePath);
if (new RegExp(`${escape(fileGlobSuffix)}$`).test(relativeFilePath) && fs.statSync(file).isFile()) {
previousFiles.push(file);
}
return previousFiles;
}, []);
return files;
} else if (fs.statSync(fileGlob).isFile()) {
return [fileGlob];
} else {
return [];
}
})
.reduce((previous, current) => previous.concat(current), []);
return templateFiles;
}
// Remove utf-8 byte order mark, http://en.wikipedia.org/wiki/Byte_order_mark
function removeByteOrderMark(text: string): string {
if (text.charCodeAt(0) === 0xfeff) {
return text.substring(1);
}
return text;
}
// Wrap templates
function wrap(file: string, name: string, openedFile: string): string {
const hoganTemplateString = `new Hogan.Template(${hogan.compile(openedFile, { asString: true })})`;
const objectName = options.variable || "templates";
const objectAccessor = `${objectName}["${name}"]`;
const objectStmt = `${objectAccessor} = ${hoganTemplateString};`;
switch (options.wrapper) {
case "amd":
return `define(${
!options.outputdir ? `"${path.join(path.dirname(file), name)}", ` : ""
}["hogan.js"], function(Hogan) { return ${hoganTemplateString}; });`;
case "node":
// If we have a template per file the export will expose the template directly
return options.outputdir ? `global.${objectStmt};\nmodule.exports = ${objectAccessor};` : `global.${objectStmt}`;
default:
return objectStmt;
}
}
function prepareOutput(content: string): string {
const variableName = options.variable || "templates";
switch (options.wrapper) {
case "amd":
return content;
case "node":
return (
"(function() {\n" +
"if (!!!global." +
variableName +
") global." +
variableName +
" = {};\n" +
'var Hogan = require("hogan.js");' +
content +
"\n" +
(!options.outputdir ? "module.exports = global." + variableName + ";\n" : "") +
"})();"
);
default:
return "if (!!!" + variableName + ") var " + variableName + " = {};\n" + content;
}
}
// Write the directory
if (options.outputdir) {
mkderp.sync(options.outputdir);
}
// Prepend namespace to template name
function namespace(name: string): string {
return (options.namespace || "") + name;
}
// Write a template foreach file that matches template extension
const templates = extractFiles(options.argv.remain)
.map(file => {
const timmedFileContents = fs.readFileSync(file, "utf-8").trim();
if (!timmedFileContents) return;
const name = namespace(path.basename(file).replace(/\..*$/, ""));
const cleanFileContents = wrap(file, name, removeByteOrderMark(timmedFileContents));
if (!options.outputdir) return cleanFileContents;
return fs.writeFileSync(path.join(options.outputdir, `${name}.js`), prepareOutput(cleanFileContents));
})
.filter(templateContents => typeof templateContents !== "undefined");
// Output templates
if (!templates.length || options.outputdir) process.exit(0);
console.log(prepareOutput(templates.join("\n")));

View file

@ -1,72 +0,0 @@
#!/usr/bin/env node
const fs = require("fs");
const hogan = require("hogan.js");
const root = "website/templates";
const pagesRoot = root + "/pages";
const websitePages = fs.readdirSync(root + "/pages");
const template = hogan.compile(readFile(root + "/template.mustache"));
const options = {
all: {
demoUrl: "demo.html?diff=https://github.com/rtfpessoa/diff2html/pull/106"
},
demo: {
extraClass: "template-index-min"
}
};
websitePages.map(function(page) {
const pagePartialTemplate = hogan.compile(readFile(pagesRoot + "/" + page + "/" + page + ".partial.mustache"));
const pageAssetsTemplate = hogan.compile(readFile(pagesRoot + "/" + page + "/" + page + "-assets.partial.mustache"));
const pageScriptsTemplate = hogan.compile(
readFile(pagesRoot + "/" + page + "/" + page + "-scripts.partial.mustache")
);
const templateOptions = {};
let key;
// Allow the pages to share common options
const genericOptions = options.all || {};
for (key in genericOptions) {
if (genericOptions.hasOwnProperty(key)) {
templateOptions[key] = genericOptions[key];
}
}
// Allow each page to have custom options
const pageOptions = options[page] || {};
for (key in pageOptions) {
if (pageOptions.hasOwnProperty(key)) {
templateOptions[key] = pageOptions[key];
}
}
const pagePartial = pagePartialTemplate.render(templateOptions);
const pageAssets = pageAssetsTemplate.render(templateOptions);
const pageScripts = pageScriptsTemplate.render(templateOptions);
templateOptions.assets = pageAssets;
templateOptions.scripts = pageScripts;
templateOptions.content = pagePartial;
const pageHtml = template.render(templateOptions);
writeFile("docs/" + page + ".html", pageHtml);
});
function readFile(filePath) {
try {
return fs.readFileSync(filePath, "utf8");
} catch (_ignore) {}
return "";
}
function writeFile(filePath, content) {
return fs.writeFileSync(filePath, content);
}

10
tsconfig.scripts.json Normal file
View file

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./build/scripts",
"declaration": false,
"declarationMap": false,
"sourceMap": false
},
"include": ["./scripts/**/*"]
}

View file

@ -351,6 +351,11 @@
"@types/minimatch" "*"
"@types/node" "*"
"@types/hogan.js@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/hogan.js/-/hogan.js-3.0.0.tgz#bf26560f39a38224ab6d0491b06f72c8fbe0953d"
integrity sha512-djkvb/AN43c3lIGCojNQ1FBS9VqqKhcTns5RQnHw4xBT/csy0jAssAsOiJ8NfaaioZaeKYE7XkVRxE5NeSZcaA==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
@ -393,11 +398,23 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/mkdirp@^0.5.2":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f"
integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@^12.7.2":
version "12.7.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446"
integrity sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==
"@types/nopt@^3.0.29":
version "3.0.29"
resolved "https://registry.yarnpkg.com/@types/nopt/-/nopt-3.0.29.tgz#f19df3db4c97ee1459a2740028320a71d70964ce"
integrity sha1-8Z3z20yX7hRZonQAKDIKcdcJZM4=
"@types/stack-utils@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"