summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xtools/generate-images.js79
-rwxr-xr-xtools/generate-svg.js70
-rwxr-xr-xtools/lint-go-gopls.sh23
-rwxr-xr-xtools/lint-templates-svg.js26
-rw-r--r--tools/watch.sh8
5 files changed, 206 insertions, 0 deletions
diff --git a/tools/generate-images.js b/tools/generate-images.js
new file mode 100755
index 0000000..0bd3af2
--- /dev/null
+++ b/tools/generate-images.js
@@ -0,0 +1,79 @@
+#!/usr/bin/env node
+import imageminZopfli from 'imagemin-zopfli'; // eslint-disable-line i/no-unresolved
+import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node'; // eslint-disable-line i/no-unresolved
+import {optimize} from 'svgo';
+import {readFile, writeFile} from 'node:fs/promises';
+import {argv, exit} from 'node:process';
+
+function doExit(err) {
+ if (err) console.error(err);
+ exit(err ? 1 : 0);
+}
+
+async function generate(svg, path, {size, bg}) {
+ const outputFile = new URL(path, import.meta.url);
+
+ if (String(outputFile).endsWith('.svg')) {
+ const {data} = optimize(svg, {
+ plugins: [
+ 'preset-default',
+ 'removeDimensions',
+ {
+ name: 'addAttributesToSVGElement',
+ params: {attributes: [{width: size}, {height: size}]},
+ },
+ ],
+ });
+ await writeFile(outputFile, data);
+ return;
+ }
+
+ const {objects, options} = await loadSVGFromString(svg);
+ const canvas = new Canvas();
+ canvas.setDimensions({width: size, height: size});
+ const ctx = canvas.getContext('2d');
+ ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1);
+
+ if (bg) {
+ canvas.add(new Rect({
+ left: 0,
+ top: 0,
+ height: size * (1 / (size / options.height)),
+ width: size * (1 / (size / options.width)),
+ fill: 'white',
+ }));
+ }
+
+ canvas.add(util.groupSVGElements(objects, options));
+ canvas.renderAll();
+
+ let png = Buffer.from([]);
+ for await (const chunk of canvas.createPNGStream()) {
+ png = Buffer.concat([png, chunk]);
+ }
+
+ png = await imageminZopfli({more: true})(png);
+ await writeFile(outputFile, png);
+}
+
+async function main() {
+ const gitea = argv.slice(2).includes('gitea');
+ const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8');
+ const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8');
+
+ await Promise.all([
+ generate(logoSvg, '../public/assets/img/logo.svg', {size: 32}),
+ generate(logoSvg, '../public/assets/img/logo.png', {size: 512}),
+ generate(faviconSvg, '../public/assets/img/favicon.svg', {size: 32}),
+ generate(faviconSvg, '../public/assets/img/favicon.png', {size: 180}),
+ generate(logoSvg, '../public/assets/img/avatar_default.png', {size: 200}),
+ generate(logoSvg, '../public/assets/img/apple-touch-icon.png', {size: 180, bg: true}),
+ gitea && generate(logoSvg, '../public/assets/img/gitea.svg', {size: 32}),
+ ]);
+}
+
+try {
+ doExit(await main());
+} catch (err) {
+ doExit(err);
+}
diff --git a/tools/generate-svg.js b/tools/generate-svg.js
new file mode 100755
index 0000000..1c5851e
--- /dev/null
+++ b/tools/generate-svg.js
@@ -0,0 +1,70 @@
+#!/usr/bin/env node
+import fastGlob from 'fast-glob';
+import {optimize} from 'svgo';
+import {parse} from 'node:path';
+import {readFile, writeFile, mkdir} from 'node:fs/promises';
+import {fileURLToPath} from 'node:url';
+import {exit} from 'node:process';
+
+const glob = (pattern) => fastGlob.sync(pattern, {
+ cwd: fileURLToPath(new URL('..', import.meta.url)),
+ absolute: true,
+});
+
+function doExit(err) {
+ if (err) console.error(err);
+ exit(err ? 1 : 0);
+}
+
+async function processFile(file, {prefix, fullName} = {}) {
+ let name;
+ if (fullName) {
+ name = fullName;
+ } else {
+ name = parse(file).name;
+ if (prefix) name = `${prefix}-${name}`;
+ if (prefix === 'octicon') name = name.replace(/-[0-9]+$/, ''); // chop of '-16' on octicons
+ }
+
+ // Set the `xmlns` attribute so that the files are displayable in standalone documents
+ // The svg backend module will strip the attribute during startup for inline display
+ const {data} = optimize(await readFile(file, 'utf8'), {
+ plugins: [
+ {name: 'preset-default'},
+ {name: 'removeDimensions'},
+ {name: 'prefixIds', params: {prefix: () => name}},
+ {name: 'addClassesToSVGElement', params: {classNames: ['svg', name]}},
+ {
+ name: 'addAttributesToSVGElement', params: {
+ attributes: [
+ {'xmlns': 'http://www.w3.org/2000/svg'},
+ {'width': '16'}, {'height': '16'}, {'aria-hidden': 'true'},
+ ],
+ },
+ },
+ ],
+ });
+
+ await writeFile(fileURLToPath(new URL(`../public/assets/img/svg/${name}.svg`, import.meta.url)), data);
+}
+
+function processFiles(pattern, opts) {
+ return glob(pattern).map((file) => processFile(file, opts));
+}
+
+async function main() {
+ try {
+ await mkdir(fileURLToPath(new URL('../public/assets/img/svg', import.meta.url)), {recursive: true});
+ } catch {}
+
+ await Promise.all([
+ ...processFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}),
+ ...processFiles('web_src/svg/*.svg'),
+ ]);
+}
+
+try {
+ doExit(await main());
+} catch (err) {
+ doExit(err);
+}
diff --git a/tools/lint-go-gopls.sh b/tools/lint-go-gopls.sh
new file mode 100755
index 0000000..a222ea1
--- /dev/null
+++ b/tools/lint-go-gopls.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+set -uo pipefail
+
+cd "$(dirname -- "${BASH_SOURCE[0]}")" && cd ..
+
+IGNORE_PATTERNS=(
+ "is deprecated" # TODO: fix these
+)
+
+# lint all go files with 'gopls check' and look for lines starting with the
+# current absolute path, indicating a error was found. This is necessary
+# because the tool does not set non-zero exit code when errors are found.
+# ref: https://github.com/golang/go/issues/67078
+ERROR_LINES=$("$GO" run "$GOPLS_PACKAGE" check "$@" 2>/dev/null | grep -E "^$PWD" | grep -vFf <(printf '%s\n' "${IGNORE_PATTERNS[@]}"));
+NUM_ERRORS=$(echo -n "$ERROR_LINES" | wc -l)
+
+if [ "$NUM_ERRORS" -eq "0" ]; then
+ exit 0;
+else
+ echo "$ERROR_LINES"
+ echo "Found $NUM_ERRORS 'gopls check' errors"
+ exit 1;
+fi
diff --git a/tools/lint-templates-svg.js b/tools/lint-templates-svg.js
new file mode 100755
index 0000000..72f7564
--- /dev/null
+++ b/tools/lint-templates-svg.js
@@ -0,0 +1,26 @@
+#!/usr/bin/env node
+import {readdirSync, readFileSync} from 'node:fs';
+import {parse, relative} from 'node:path';
+import {fileURLToPath} from 'node:url';
+import {exit} from 'node:process';
+import fastGlob from 'fast-glob';
+
+const knownSvgs = new Set();
+for (const file of readdirSync(new URL('../public/assets/img/svg', import.meta.url))) {
+ knownSvgs.add(parse(file).name);
+}
+
+const rootPath = fileURLToPath(new URL('..', import.meta.url));
+let hadErrors = false;
+
+for (const file of fastGlob.sync(fileURLToPath(new URL('../templates/**/*.tmpl', import.meta.url)))) {
+ const content = readFileSync(file, 'utf8');
+ for (const [_, name] of content.matchAll(/svg ["'`]([^"'`]+)["'`]/g)) {
+ if (!knownSvgs.has(name)) {
+ console.info(`SVG "${name}" not found, used in ${relative(rootPath, file)}`);
+ hadErrors = true;
+ }
+ }
+}
+
+exit(hadErrors ? 1 : 0);
diff --git a/tools/watch.sh b/tools/watch.sh
new file mode 100644
index 0000000..5e8defa
--- /dev/null
+++ b/tools/watch.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -euo pipefail
+
+make --no-print-directory watch-frontend &
+make --no-print-directory watch-backend &
+
+trap 'kill $(jobs -p)' EXIT
+wait