Drop Rollup in favor of esbuild

This commit is contained in:
Zaid Humaimidi committed 2023-12-12 22:27:58 -08:00
1 parent 342210faf1
commit 4f2dfb4f48
13 files changed
+735 -3805

No files matched your search

-4
View File
@@ -1,4 +0,0 @@
export default {
extensions: ["ts"],
require: ["esm", "ts-node/register/transpile-only"],
};
+5
View File
@@ -0,0 +1,5 @@
export const APP_JS = "app.js";
export const APP_CSS = "app.css";
export const ICONS_SVG = "icons.svg";
export const LOGO_SVG = "logo.svg";
export const FAVICON_PNG = "favicon.png";
+225 -210
View File
@@ -17,22 +17,28 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import * as path from "path"; import path from "path";
import * as fs from "fs"; import fs from "fs";
import { rollup, WarningHandler } from "rollup"; import { createHash } from "crypto";
import rollupJson from "@rollup/plugin-json"; import { promisify } from "util";
import typescript from "@rollup/plugin-typescript"; import * as esbuild from "esbuild";
import { terser } from "rollup-plugin-terser";
import nodeResolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import postcss from "postcss";
import postcssImport from "postcss-import";
import postcssPresetEnv from "postcss-preset-env";
import postcssColorMod from "postcss-color-mod-function";
import cssnano from "cssnano";
import { optimize } from "svgo"; import { optimize } from "svgo";
import * as xmlParser from "../lib/xml-parser"; import * as xmlParser from "../lib/xml-parser";
const fsAsync = {
readdir: promisify(fs.readdir),
readFile: promisify(fs.readFile),
writeFile: promisify(fs.writeFile),
copyFile: promisify(fs.copyFile),
rename: promisify(fs.rename),
chmod: promisify(fs.chmod),
stat: promisify(fs.stat),
exists: promisify(fs.exists),
rmdir: promisify(fs.rmdir),
unlink: promisify(fs.unlink),
mkdir: promisify(fs.mkdir),
};
const MODE = process.env["NODE_ENV"] || "production"; const MODE = process.env["NODE_ENV"] || "production";
const BUILD_METADATA = new Date() const BUILD_METADATA = new Date()
@@ -40,40 +46,19 @@ const BUILD_METADATA = new Date()
.split(".")[0] .split(".")[0]
.replace(/[^0-9]/g, ""); .replace(/[^0-9]/g, "");
const INPUT_DIR = path.resolve(__dirname, ".."); const INPUT_DIR = process.cwd();
const OUTPUT_DIR = path.resolve(__dirname, "../dist"); const OUTPUT_DIR = path.join(INPUT_DIR, "dist");
const builtins = [ async function rmDir(dirPath: string): Promise<void> {
"path", if (!(await fsAsync.exists(dirPath))) return;
"fs", const files = await fsAsync.readdir(dirPath);
"cluster",
"os",
"tls",
"http",
"https",
"zlib",
"crypto",
"util",
"vm",
"querystring",
"child_process",
"dgram",
"url",
"readline",
"stream",
"bson", // not a built-in but not a direct dependency either
];
function rmDirSync(dirPath): void {
if (!fs.existsSync(dirPath)) return;
const files = fs.readdirSync(dirPath);
for (const file of files) { for (const file of files) {
const filePath = `${dirPath}/${file}`; const filePath = path.join(dirPath, file);
if (fs.statSync(filePath).isFile()) fs.unlinkSync(filePath); if ((await fsAsync.stat(filePath)).isFile()) await fsAsync.unlink(filePath);
else rmDirSync(filePath); else await rmDir(filePath);
} }
fs.rmdirSync(dirPath); await fsAsync.rmdir(dirPath);
} }
// For lockfileVersion = 1 // For lockfileVersion = 1
@@ -104,6 +89,68 @@ function xmlTostring(xml): string {
: `<${xml.name} ${xml.attrs}>${children.join("")}</${xml.name}>`; : `<${xml.name} ${xml.attrs}>${children.join("")}</${xml.name}>`;
} }
function assetHash(buffer: Buffer | string): string {
return createHash("md5").update(buffer).digest("hex").slice(0, 8);
}
const ASSETS = {} as {
APP_JS?: string;
APP_CSS?: string;
ICONS_SVG?: string;
LOGO_SVG?: string;
FAVICON_PNG?: string;
};
const assetsPlugin = {
name: "assets",
setup(build) {
build.onLoad({ filter: /\/build\/assets.ts$/ }, () => {
const lines = Object.entries(ASSETS).map(
([k, v]) => `export const ${k} = ${JSON.stringify(v)};`
);
return { contents: lines.join("\n") };
});
},
} as esbuild.Plugin;
const packageDotJsonPlugin = {
name: "packageDotJson",
setup(build) {
const sourcePath = path.join(INPUT_DIR, "package.json");
build.onResolve({ filter: /\/package.json$/ }, (args) => {
const p = path.join(args.resolveDir, args.path);
if (p !== sourcePath) return undefined;
return { path: path.join(OUTPUT_DIR, "package.json") };
});
},
} as esbuild.Plugin;
const inlineDepsPlugin = {
name: "inlineDeps",
setup(build) {
const deps = [
"parsimmon",
"espresso-iisojs",
"codemirror",
"mithril",
"yaml",
];
const sym = Symbol();
build.onResolve({ filter: /^[^.]/ }, async (args) => {
if (args.pluginData === sym) return undefined;
if (deps.some((d) => args.path.startsWith(d))) return undefined;
const res = await build.resolve(args.path, {
pluginData: sym,
importer: args.importer,
namespace: args.namespace,
resolveDir: args.resolveDir,
kind: args.kind,
});
return { path: res.path, sideEffects: false, external: true };
});
},
} as esbuild.Plugin;
function generateSymbol(id: string, svgStr: string): string { function generateSymbol(id: string, svgStr: string): string {
const xml = xmlParser.parseXml(svgStr); const xml = xmlParser.parseXml(svgStr);
const svg = xml.children[0]; const svg = xml.children[0];
@@ -121,43 +168,39 @@ function generateSymbol(id: string, svgStr: string): string {
return `<symbol id="icon-${id}" ${viewBox}>${symbolBody}</symbol>`; return `<symbol id="icon-${id}" ${viewBox}>${symbolBody}</symbol>`;
} }
async function init(): Promise<string[]> { async function init(): Promise<void> {
// Delete any old output directory const [packageJsonFile, npmShrinkwrapFile] = await Promise.all([
rmDirSync(OUTPUT_DIR); fsAsync.readFile(path.join(INPUT_DIR, "package.json")),
fsAsync.readFile(path.join(INPUT_DIR, "npm-shrinkwrap.json")),
]);
// Create output directory layout const packageJson = JSON.parse(packageJsonFile.toString());
fs.mkdirSync(OUTPUT_DIR);
fs.mkdirSync(OUTPUT_DIR + "/bin");
fs.mkdirSync(OUTPUT_DIR + "/public");
// Create package.json
const packageJson = JSON.parse(
fs.readFileSync(path.resolve(INPUT_DIR, "package.json")).toString()
);
delete packageJson["devDependencies"]; delete packageJson["devDependencies"];
delete packageJson["private"]; delete packageJson["private"];
packageJson["scripts"] = { delete packageJson["scripts"];
install: packageJson["scripts"].install,
configure: packageJson["scripts"].configure,
};
packageJson["version"] = `${packageJson["version"]}+${BUILD_METADATA}`; packageJson["version"] = `${packageJson["version"]}+${BUILD_METADATA}`;
fs.writeFileSync(
path.resolve(OUTPUT_DIR, "package.json"),
JSON.stringify(packageJson, null, 2)
);
// Create npm-shrinkwrap.json const npmShrinkwrap = JSON.parse(npmShrinkwrapFile.toString());
const npmShrinkwrapJson = JSON.parse( npmShrinkwrap["version"] = packageJson["version"];
fs.readFileSync(path.resolve(INPUT_DIR, "npm-shrinkwrap.json")).toString() stripDevDeps(npmShrinkwrap);
); stripDevDeps2(npmShrinkwrap);
npmShrinkwrapJson["version"] = packageJson["version"];
stripDevDeps(npmShrinkwrapJson); await rmDir(OUTPUT_DIR);
stripDevDeps2(npmShrinkwrapJson);
fs.writeFileSync( await fsAsync.mkdir(OUTPUT_DIR);
path.resolve(OUTPUT_DIR, "npm-shrinkwrap.json"),
JSON.stringify(npmShrinkwrapJson, null, 2) await Promise.all([
); fsAsync.mkdir(path.join(OUTPUT_DIR, "bin")),
return Object.keys(packageJson["dependencies"]); fsAsync.mkdir(path.join(OUTPUT_DIR, "public")),
fsAsync.writeFile(
path.join(OUTPUT_DIR, "package.json"),
JSON.stringify(packageJson, null, 2)
),
fsAsync.writeFile(
path.join(OUTPUT_DIR, "npm-shrinkwrap.json"),
JSON.stringify(npmShrinkwrap, null, 2)
),
]);
} }
async function copyStatic(): Promise<void> { async function copyStatic(): Promise<void> {
@@ -169,149 +212,124 @@ async function copyStatic(): Promise<void> {
"public/favicon.png", "public/favicon.png",
]; ];
for (const file of files) { const [logo, favicon] = await Promise.all([
fs.copyFileSync( fsAsync.readFile(path.join(INPUT_DIR, "public/logo.svg")),
path.resolve(INPUT_DIR, file), fsAsync.readFile(path.join(INPUT_DIR, "public/favicon.png")),
path.resolve(OUTPUT_DIR, file) ]);
);
} ASSETS.LOGO_SVG = `logo-${assetHash(logo)}.svg`;
ASSETS.FAVICON_PNG = `favicon-${assetHash(favicon)}.png`;
const filenames = {} as Record<string, string>;
filenames["public/logo.svg"] = path.join("public", ASSETS.LOGO_SVG);
filenames["public/favicon.png"] = path.join("public", ASSETS.FAVICON_PNG);
await Promise.all(
files.map((f) =>
fsAsync.copyFile(
path.join(INPUT_DIR, f),
path.join(OUTPUT_DIR, filenames[f] || f)
)
)
);
} }
async function generateCss(): Promise<void> { async function generateCss(): Promise<void> {
const cssInPath = path.resolve(INPUT_DIR, "ui/css/app.css"); const res = await esbuild.build({
const cssOutPath = path.resolve(OUTPUT_DIR, "public/app.css"); bundle: true,
const cssIn = fs.readFileSync(cssInPath); absWorkingDir: INPUT_DIR,
const cssOut = await postcss([ minify: MODE === "production",
postcssImport, sourcemap: "linked",
postcssPresetEnv({ sourcesContent: false,
stage: 3, entryPoints: ["ui/css/app.css"],
features: { entryNames: "[dir]/[name]-[hash]",
"nesting-rules": true, outfile: path.join(OUTPUT_DIR, "public/app.css"),
}, target: ["chrome109", "safari15.6", "firefox115", "opera102", "edge118"],
}), metafile: true,
postcssColorMod, });
...(MODE === "production" ? [cssnano] : []),
]).process(cssIn, { from: cssInPath, to: cssOutPath }); for (const [k, v] of Object.entries(res.metafile.outputs)) {
fs.writeFileSync(cssOutPath, cssOut.css); if (v.entryPoint === "ui/css/app.css") {
ASSETS.APP_CSS = path.relative(
path.join(OUTPUT_DIR, "public"),
path.join(INPUT_DIR, k)
);
break;
}
}
} }
async function generateBackendJs(externals: string[]): Promise<void> { async function generateBackendJs(): Promise<void> {
for (const bin of [ const services = [
"genieacs-cwmp", "genieacs-cwmp",
"genieacs-ext", "genieacs-ext",
"genieacs-nbi", "genieacs-nbi",
"genieacs-fs", "genieacs-fs",
"genieacs-ui", "genieacs-ui",
]) { ];
const inputFile = path.resolve(INPUT_DIR, `bin/${bin}.ts`);
const outputFile = path.resolve(OUTPUT_DIR, `bin/${bin}`);
const bundle = await rollup({
input: inputFile,
external: [...builtins, ...externals],
acorn: {
allowHashBang: true,
},
treeshake: {
propertyReadSideEffects: false,
moduleSideEffects: false,
},
plugins: [
rollupJson({ preferConst: true }),
{
name: "",
resolveId: (importee, importer) => {
if (importee.endsWith("/package.json")) {
const p = path.resolve(path.dirname(importer), importee);
if (p === path.resolve(INPUT_DIR, "package.json"))
return path.resolve(OUTPUT_DIR, "package.json");
}
return null;
},
},
typescript({
tsconfig: "./tsconfig.json",
include: [`bin/${bin}.ts`, "lib/**/*.ts"],
}),
MODE === "production" ? terser() : null,
],
});
await bundle.write({ await esbuild.build({
format: "cjs", bundle: true,
preferConst: true, absWorkingDir: INPUT_DIR,
sourcemap: "inline", minify: MODE === "production",
sourcemapExcludeSources: true, sourcemap: "inline",
banner: "#!/usr/bin/env node", sourcesContent: false,
file: outputFile, platform: "node",
}); target: "node12.13.0",
packages: "external",
banner: { js: "#!/usr/bin/env node" },
entryPoints: services.map((s) => `bin/${s}.ts`),
outdir: path.join(OUTPUT_DIR, "bin"),
plugins: [packageDotJsonPlugin, assetsPlugin],
});
for (const bin of services) {
const p = path.join(OUTPUT_DIR, "bin", bin);
await fsAsync.rename(`${p}.js`, p);
// Mark as executable // Mark as executable
const mode = fs.statSync(outputFile).mode; const mode = (await fsAsync.stat(p)).mode;
fs.chmodSync(outputFile, mode | 73); await fsAsync.chmod(p, mode | 73);
} }
} }
async function generateFrontendJs(externals: string[]): Promise<void> { async function generateFrontendJs(): Promise<void> {
const inputFile = path.resolve(INPUT_DIR, "ui/app.ts"); const res = await esbuild.build({
const outputDir = path.resolve(OUTPUT_DIR, "public"); bundle: true,
absWorkingDir: INPUT_DIR,
splitting: true,
minify: MODE === "production",
sourcemap: "linked",
sourcesContent: false,
platform: "browser",
format: "esm",
target: ["chrome109", "safari15.6", "firefox115", "opera102", "edge118"],
entryPoints: ["ui/app.ts"],
entryNames: "[dir]/[name]-[hash]",
outdir: path.join(OUTPUT_DIR, "public"),
const inlineDeps = ["parsimmon", "espresso-iisojs"]; plugins: [packageDotJsonPlugin, inlineDepsPlugin, assetsPlugin],
const bundle = await rollup({ metafile: true,
input: inputFile,
external: [
...builtins,
...externals.filter((e) => !inlineDeps.includes(e)),
],
plugins: [
rollupJson({ preferConst: true }),
{
name: "",
resolveId: function (importee, importer) {
if (importee.endsWith("/package.json")) {
const p = path.resolve(path.dirname(importer), importee);
if (p === path.resolve(INPUT_DIR, "package.json"))
return path.resolve(OUTPUT_DIR, "package.json");
}
return null;
},
},
typescript({ tsconfig: "./tsconfig.json" }),
nodeResolve(),
commonjs(),
MODE === "production" ? terser() : null,
],
preserveEntrySignatures: false,
treeshake: {
propertyReadSideEffects: false,
moduleSideEffects: false,
},
onwarn: ((warning, warn) => {
// Ignore circular dependency warnings
if (warning.code !== "CIRCULAR_DEPENDENCY") warn(warning);
}) as WarningHandler,
}); });
await bundle.write({ for (const [k, v] of Object.entries(res.metafile.outputs)) {
manualChunks: (id) => { if (v.entryPoint === "ui/app.ts") {
if (id.includes("node_modules/codemirror")) return "codemirror"; ASSETS.APP_JS = path.relative(
else if (id.includes("node_modules/yaml")) return "yaml"; path.join(OUTPUT_DIR, "public"),
return "app"; path.join(INPUT_DIR, k)
}, );
preferConst: true, break;
format: "es", }
sourcemap: true, }
sourcemapExcludeSources: true,
dir: outputDir,
});
} }
async function generateIconsSprite(): Promise<void> { async function generateIconsSprite(): Promise<void> {
const symbols = []; const symbols = [] as string[];
const iconsDir = path.resolve(INPUT_DIR, "ui/icons"); const iconsDir = path.join(INPUT_DIR, "ui/icons");
for (const file of fs.readdirSync(iconsDir)) { for (const file of await fsAsync.readdir(iconsDir)) {
const id = path.parse(file).name; const id = path.parse(file).name;
const filePath = path.join(iconsDir, file); const filePath = path.join(iconsDir, file);
const { data } = await optimize(fs.readFileSync(filePath).toString(), { const src = (await fsAsync.readFile(filePath)).toString();
const { data } = await optimize(src, {
plugins: [ plugins: [
{ {
name: "preset-default", name: "preset-default",
@@ -325,28 +343,25 @@ async function generateIconsSprite(): Promise<void> {
}); });
symbols.push(generateSymbol(id, data)); symbols.push(generateSymbol(id, data));
} }
fs.writeFileSync( const data = `<svg xmlns="http://www.w3.org/2000/svg">${symbols.join(
path.resolve(OUTPUT_DIR, "public/icons.svg"), ""
`<svg xmlns="http://www.w3.org/2000/svg">${symbols.join("")}</svg>` )}</svg>`;
ASSETS.ICONS_SVG = `icons-${assetHash(data)}.svg`;
await fsAsync.writeFile(
path.join(OUTPUT_DIR, "public", ASSETS.ICONS_SVG),
data
); );
} }
init() init()
.then((externals) => { .then(() =>
Promise.all([ Promise.all([
copyStatic(), Promise.all([generateIconsSprite(), copyStatic()]).then(
generateFrontendJs
),
generateCss(), generateCss(),
generateIconsSprite(), ]).then(generateBackendJs)
generateBackendJs(externals), )
generateFrontendJs(externals),
])
.then(() => {
// Ignore
})
.catch((err) => {
process.stderr.write(err.stack + "\n");
});
})
.catch((err) => { .catch((err) => {
process.stderr.write(err.stack + "\n"); process.stderr.write(err.stack + "\n");
}); });
+1 -1
View File
@@ -103,4 +103,4 @@ export function asIntN(a: number, b: bigint): bigint {
const _BigInt = BigInt; const _BigInt = BigInt;
type _bigint = bigint; type _bigint = bigint;
export { _BigInt as BigInt, _bigint as bigint }; export { _BigInt as BigInt, type _bigint as bigint };
+2 -4
View File
@@ -30,8 +30,6 @@ function flattenObject<T extends Record<keyof T, unknown>>(
prefix = "", prefix = "",
dst = {} as T dst = {} as T
): T { ): T {
const t: Record<string, string> = {};
t["hi"] = "hi";
for (const k of Object.keys(src)) { for (const k of Object.keys(src)) {
const v = src[k]; const v = src[k];
if (typeof v === "object" && !Array.isArray(v)) if (typeof v === "object" && !Array.isArray(v))
@@ -201,7 +199,7 @@ async function fetchProvisions(): Promise<[string, Provisions]> {
.digest("hex"); .digest("hex");
provisions[r._id].script = new vm.Script( provisions[r._id].script = new vm.Script(
`"use strict";(function(){\n${r.script}\n})();`, `"use strict";(function(){\n${r.script}\n})();`,
{ filename: r._id, lineOffset: -1, timeout: 50 } { filename: r._id, lineOffset: -1 }
); );
} }
@@ -222,7 +220,7 @@ async function fetchVirtualParameters(): Promise<[string, VirtualParameters]> {
.digest("hex"); .digest("hex");
virtualParameters[r._id].script = new vm.Script( virtualParameters[r._id].script = new vm.Script(
`"use strict";(function(){\n${r.script}\n})();`, `"use strict";(function(){\n${r.script}\n})();`,
{ filename: r._id, lineOffset: -1, timeout: 50 } { filename: r._id, lineOffset: -1 }
); );
} }
+2 -2
View File
@@ -34,7 +34,7 @@ const EXT = Symbol();
const UNDEFINED = undefined; const UNDEFINED = undefined;
const context = vm.createContext(); const context = vm.createContext(undefined, { microtaskMode: "afterEvaluate" });
let state; let state;
@@ -391,7 +391,7 @@ export async function run(
let ret, status; let ret, status;
try { try {
ret = script.runInContext(context, { displayErrors: false }); ret = script.runInContext(context, { displayErrors: false, timeout: 50 });
status = 0; status = 0;
} catch (err) { } catch (err) {
if (err === COMMIT) { if (err === COMMIT) {
+4 -3
View File
@@ -35,6 +35,7 @@ import { authLocal } from "./api-functions";
import * as init from "./init"; import * as init from "./init";
import { version as VERSION } from "../package.json"; import { version as VERSION } from "../package.json";
import memoize from "./common/memoize"; import memoize from "./common/memoize";
import { APP_JS, APP_CSS, FAVICON_PNG } from "../build/assets";
declare module "koa" { declare module "koa" {
interface Request { interface Request {
@@ -232,8 +233,8 @@ router.get("/", async (ctx) => {
<html> <html>
<head> <head>
<title>GenieACS</title> <title>GenieACS</title>
<link rel="shortcut icon" type="image/png" href="favicon.png" /> <link rel="shortcut icon" type="image/png" href="${FAVICON_PNG}" />
<link rel="stylesheet" href="app.css"> <link rel="stylesheet" href="${APP_CSS}">
</head> </head>
<body> <body>
<noscript>GenieACS UI requires JavaScript to work. Please enable JavaScript in your browser.</noscript> <noscript>GenieACS UI requires JavaScript to work. Please enable JavaScript in your browser.</noscript>
@@ -248,7 +249,7 @@ router.get("/", async (ctx) => {
)}; )};
window.permissionSets = ${JSON.stringify(permissionSets)}; window.permissionSets = ${JSON.stringify(permissionSets)};
</script> </script>
<script type="module" src="app.js"></script>${wizard} <script type="module" src="${APP_JS}"></script>${wizard}
</body> </body>
</html> </html>
`; `;
+470 -3545
View File
File diff suppressed because it is too large. Load diff
+3 -15
View File
@@ -35,10 +35,6 @@
"seedrandom": "^3.0.5" "seedrandom": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^22.0.2",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-typescript": "^8.3.4",
"@types/codemirror": "^5.60.5", "@types/codemirror": "^5.60.5",
"@types/jsonwebtoken": "^8.5.8", "@types/jsonwebtoken": "^8.5.8",
"@types/koa": "^2.13.5", "@types/koa": "^2.13.5",
@@ -53,21 +49,13 @@
"@typescript-eslint/parser": "^5.33.1", "@typescript-eslint/parser": "^5.33.1",
"ava": "^3.15.0", "ava": "^3.15.0",
"codemirror": "^5.65.8", "codemirror": "^5.65.8",
"cssnano": "^5.1.13", "esbuild": "^0.19.9",
"eslint": "^8.22.0", "eslint": "^8.22.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"esm": "^3.2.25",
"mithril": "^2.2.2", "mithril": "^2.2.2",
"postcss": "^8.4.16",
"postcss-color-mod-function": "^3.0.3",
"postcss-import": "^14.1.0",
"postcss-preset-env": "^7.8.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"rollup": "^2.78.1",
"rollup-plugin-terser": "^7.0.2",
"sql.js": "^1.7.0", "sql.js": "^1.7.0",
"svgo": "^2.8.0", "svgo": "^2.8.0",
"ts-node": "^10.9.1",
"typescript": "^4.7.4", "typescript": "^4.7.4",
"yaml": "^1.10.2" "yaml": "^1.10.2"
}, },
@@ -75,11 +63,11 @@
"node": ">=12.13.0" "node": ">=12.13.0"
}, },
"scripts": { "scripts": {
"test": "ava --verbose --color", "test": "esbuild --log-level=warning --bundle --platform=node --target=node12 --packages=external --outdir=test test/*.ts && ava --verbose --color && rm test/*.js",
"lint-eslint": "eslint --fix \"bin/*.ts\" \"lib/**/*.ts\" \"ui/**/*.ts\" \"test/**/*.ts\" \"build/**/*.ts\"", "lint-eslint": "eslint --fix \"bin/*.ts\" \"lib/**/*.ts\" \"ui/**/*.ts\" \"test/**/*.ts\" \"build/**/*.ts\"",
"lint-prettier": "prettier --prose-wrap always --write .", "lint-prettier": "prettier --prose-wrap always --write .",
"lint-tsc": "tsc --noEmit", "lint-tsc": "tsc --noEmit",
"lint": "npm run lint-eslint && npm run lint-prettier && npm run lint-tsc", "lint": "npm run lint-eslint && npm run lint-prettier && npm run lint-tsc",
"build": "node -r esm -r ts-node/register/transpile-only build/build" "build": "esbuild build/build.ts --bundle --platform=node --target=node12 --packages=external | node"
} }
} }
+4 -4
View File
@@ -1,15 +1,15 @@
{ {
"compilerOptions": { "compilerOptions": {
"lib": ["ES2019", "dom"], "lib": ["ES2022", "dom"],
"module": "ES2020", "module": "ES2022",
"moduleResolution": "node", "moduleResolution": "node",
"target": "ES2019", "target": "ES2022",
"resolveJsonModule": true, "resolveJsonModule": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"alwaysStrict": true, "alwaysStrict": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"strictFunctionTypes": true, "strictFunctionTypes": true,
"allowJs": true "isolatedModules": true
}, },
"include": [ "include": [
"./bin/*.ts", "./bin/*.ts",
+15 -15
View File
@@ -34,10 +34,10 @@
--disabled: #666; --disabled: #666;
--status-red: hsl(0, 80%, 80%); --status-red: 0 80% 80%;
--status-green: hsl(125, 80%, 80%); --status-green: 125 80% 80%;
--status-gray: hsl(58, 3%, 80%); --status-gray: 58 3% 80%;
--status-yellow: hsl(46, 79%, 80%); --status-yellow: 46 79% 80%;
} }
body, body,
@@ -79,7 +79,7 @@ i {
@keyframes pending-blink { @keyframes pending-blink {
from { from {
background-color: color-mod(var(--status-green) a(90%)); background-color: hsl(var(--status-green) / 0.9);
} }
to { to {
background-color: transparent; background-color: transparent;
@@ -142,10 +142,10 @@ body {
} }
} }
& > .stale.active { & > .stale.active {
background-color: color-mod(var(--status-gray) a(90%)); background-color: hsl(var(--status-gray) / 0.9);
} }
& > .fault.active { & > .fault.active {
background-color: color-mod(var(--status-red) a(90%)); background-color: hsl(var(--status-red) / 0.9);
} }
& > .queued.active { & > .queued.active {
font-weight: bold; font-weight: bold;
@@ -187,11 +187,11 @@ body {
border-radius: 0.4em; border-radius: 0.4em;
} }
& > .stale { & > .stale {
background-color: color-mod(var(--status-gray) a(90%)); background-color: hsl(var(--status-gray) / 0.9);
border-radius: 0.4em; border-radius: 0.4em;
} }
& > .fault { & > .fault {
background-color: color-mod(var(--status-red) a(90%)); background-color: hsl(var(--status-red) / 0.9);
border-radius: 0.4em; border-radius: 0.4em;
} }
& .parameter { & .parameter {
@@ -231,16 +231,16 @@ body {
transition: opacity var(--fade), top var(--fade); transition: opacity var(--fade), top var(--fade);
&.error { &.error {
background-color: color-mod(var(--status-red) a(90%)); background-color: hsl(var(--status-red) / 0.9);
border: 1px solid var(--status-red); border: 1px solid hsl(var(--status-red));
} }
&.success { &.success {
background-color: color-mod(var(--status-green) a(90%)); background-color: hsl(var(--status-green) / 0.9);
border: 1px solid var(--status-green); border: 1px solid hsl(var(--status-green));
} }
&.warning { &.warning {
background-color: color-mod(var(--status-yellow) a(90%)); background-color: hsl(var(--status-yellow) / 0.9);
border: 1px solid var(--status-yellow); border: 1px solid hsl(var(--status-yellow));
} }
} }
} }
+2 -1
View File
@@ -19,11 +19,12 @@
import { Children } from "mithril"; import { Children } from "mithril";
import m from "mithril"; import m from "mithril";
import { ICONS_SVG } from "../build/assets";
export function getIcon(name: string): Children { export function getIcon(name: string): Children {
return m( return m(
`svg.icon.icon-${name}`, `svg.icon.icon-${name}`,
{ key: `icon-${name}` }, { key: `icon-${name}` },
m("use", { href: `icons.svg#icon-${name}` }) m("use", { href: `${ICONS_SVG}#icon-${name}` })
); );
} }
+2 -1
View File
@@ -25,6 +25,7 @@ import adminMenu from "./admin-menu";
import * as overlay from "./overlay"; import * as overlay from "./overlay";
import { version as VERSION } from "../package.json"; import { version as VERSION } from "../package.json";
import datalist from "./datalist"; import datalist from "./datalist";
import { LOGO_SVG } from "../build/assets";
const adminPages = [ const adminPages = [
"presets", "presets",
@@ -55,7 +56,7 @@ const component: ClosureComponent = (): Component => {
m("#header", [ m("#header", [
m( m(
"div.logo", "div.logo",
m("img", { src: "logo.svg" }), m("img", { src: LOGO_SVG }),
m("span.version", "v" + VERSION) m("span.version", "v" + VERSION)
), ),
m(userMenu), m(userMenu),