Drop Rollup in favor of esbuild
This commit is contained in:
1 parent
342210faf1
commit
4f2dfb4f48
13 files changed
+729
-3799
No files matched your search
@@ -1,4 +0,0 @@
|
|||||||
export default {
|
|
||||||
extensions: ["ts"],
|
|
||||||
require: ["esm", "ts-node/register/transpile-only"],
|
|
||||||
};
|
|
||||||
@@ -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";
|
||||||
+223
-208
@@ -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,
|
||||||
|
minify: MODE === "production",
|
||||||
sourcemap: "inline",
|
sourcemap: "inline",
|
||||||
sourcemapExcludeSources: true,
|
sourcesContent: false,
|
||||||
banner: "#!/usr/bin/env node",
|
platform: "node",
|
||||||
file: outputFile,
|
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,
|
for (const [k, v] of Object.entries(res.metafile.outputs)) {
|
||||||
...externals.filter((e) => !inlineDeps.includes(e)),
|
if (v.entryPoint === "ui/app.ts") {
|
||||||
],
|
ASSETS.APP_JS = path.relative(
|
||||||
plugins: [
|
path.join(OUTPUT_DIR, "public"),
|
||||||
rollupJson({ preferConst: true }),
|
path.join(INPUT_DIR, k)
|
||||||
{
|
);
|
||||||
name: "",
|
break;
|
||||||
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({
|
|
||||||
manualChunks: (id) => {
|
|
||||||
if (id.includes("node_modules/codemirror")) return "codemirror";
|
|
||||||
else if (id.includes("node_modules/yaml")) return "yaml";
|
|
||||||
return "app";
|
|
||||||
},
|
|
||||||
preferConst: true,
|
|
||||||
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");
|
||||||
});
|
});
|
||||||
@@ -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 };
|
||||||
@@ -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
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
`;
|
`;
|
||||||
|
|||||||
Generated
+466
-3541
File diff suppressed because it is too large.
Load diff
+3
-15
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||||
|
|||||||
Reference in new issue
Block a user