Compare commits

...

1 Commits

Author SHA1 Message Date
180491cdd2 fix: potential fixes 2024-03-11 15:12:02 +01:00
20 changed files with 112 additions and 147 deletions

View File

@@ -40,7 +40,7 @@
"start:debug": "cross-env STAPPS_LOG_LEVEL=31 NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true node app.js", "start:debug": "cross-env STAPPS_LOG_LEVEL=31 NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true node app.js",
"test": "pnpm run test:unit", "test": "pnpm run test:unit",
"test:integration": "sh integration-test.sh", "test:integration": "sh integration-test.sh",
"test:unit": "cross-env NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true STAPPS_LOG_LEVEL=0 mocha --exit" "test:unit": "cross-env NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true STAPPS_LOG_LEVEL=0 c8 mocha"
}, },
"dependencies": { "dependencies": {
"@elastic/elasticsearch": "8.4.0", "@elastic/elasticsearch": "8.4.0",

View File

@@ -16,7 +16,7 @@
*/ */
import {Logger, SMTP} from '@openstapps/logger'; import {Logger, SMTP} from '@openstapps/logger';
import {MailOptions} from 'nodemailer/lib/sendmail-transport'; import {MailOptions} from 'nodemailer/lib/sendmail-transport';
import Queue from 'promise-queue';
/** /**
* A queue that can send mails in serial * A queue that can send mails in serial
*/ */
@@ -32,80 +32,45 @@ export class MailQueue {
static readonly VERIFICATION_TIMEOUT = 5000; static readonly VERIFICATION_TIMEOUT = 5000;
/** /**
* A queue that saves mails, before the transport is ready. When * A promise that resolves when the last mail was sent
* the transport gets ready this mails are getting pushed in to
* the normal queue.
*/ */
dryQueue: MailOptions[]; last?: Promise<string>;
/**
* A queue that saves mails, that are being sent in series
*/
queue: Queue;
/**
* A counter for the number of verifications that failed
*/
verificationCounter: number;
/** /**
* Creates a mail queue * Creates a mail queue
* @param transport Transport which is used for sending mails * @param transport Transport which is used for sending mails
*/ */
constructor(private readonly transport: SMTP) { constructor(private readonly transport: SMTP) {}
this.queue = new Queue(1);
// this queue saves all request when the transport is not ready yet
this.dryQueue = [];
this.verificationCounter = 0;
// if the transport can be verified it should check if it was done...
this.checkForVerification();
}
/** /**
* Adds a mail into the queue so it gets send when the queue is ready * Wait for the transport to be verified
* @param mail Information required for sending a mail
*/ */
private async addToQueue(mail: MailOptions) { private async waitForVerification() {
return this.queue.add<string>(() => this.transport.sendMail(mail)); for (let i = 0; i < MailQueue.MAX_VERIFICATION_ATTEMPTS; i++) {
} if (this.transport.isVerified()) {
Logger.ok('Transport for mail queue was verified. We can send mails now');
/** return;
* Verify the given transport
*/
private checkForVerification() {
if (this.verificationCounter >= MailQueue.MAX_VERIFICATION_ATTEMPTS) {
throw new Error('Failed to initialize the SMTP transport for the mail queue');
}
if (this.transport.isVerified()) {
Logger.ok('Transport for mail queue was verified. We can send mails now');
// if the transport finally was verified send all our mails from the dry queue
for (const mail of this.dryQueue) {
void this.addToQueue(mail);
} }
} else { await new Promise(resolve => setTimeout(resolve, MailQueue.VERIFICATION_TIMEOUT));
this.verificationCounter++; Logger.warn('Transport not verified yet. Trying to send mails here...');
setTimeout(() => {
Logger.warn('Transport not verified yet. Trying to send mails here...');
this.checkForVerification();
}, MailQueue.VERIFICATION_TIMEOUT);
} }
throw new Error('Failed to initialize the SMTP transport for the mail queue');
} }
/** /**
* Push a mail into the queue so it gets send when the queue is ready * Push a mail into the queue so it gets send when the queue is ready
* @param mail Information required for sending a mail * @param mail Information required for sending a mail
*/ */
public async push(mail: MailOptions) { public async push(mail: MailOptions): Promise<string> {
if (this.transport.isVerified()) { const previousQueue = this.last ?? this.waitForVerification();
await this.addToQueue(mail); this.last = previousQueue.then(() =>
} else { Promise.race([
// the transport has verification, but is not verified yet this.transport.sendMail(mail),
// push to a dry queue which gets pushed to the real queue when the transport is verified new Promise<string>((_, reject) =>
this.dryQueue.push(mail); setTimeout(() => reject(new Error('Timeout')), MailQueue.VERIFICATION_TIMEOUT),
} ),
]),
);
return this.last;
} }
} }

View File

@@ -44,7 +44,7 @@ describe('MailQueue', async function () {
clock.tick(MailQueue.VERIFICATION_TIMEOUT * (MailQueue.MAX_VERIFICATION_ATTEMPTS + 1)); clock.tick(MailQueue.VERIFICATION_TIMEOUT * (MailQueue.MAX_VERIFICATION_ATTEMPTS + 1));
}; };
expect(() => test()).to.throw(); expect(test).to.throw();
expect(loggerStub.callCount).to.be.equal(MailQueue.MAX_VERIFICATION_ATTEMPTS); expect(loggerStub.callCount).to.be.equal(MailQueue.MAX_VERIFICATION_ATTEMPTS);
}); });

View File

@@ -39,7 +39,6 @@ import {Elasticsearch} from '../../../src/storage/elasticsearch/elasticsearch.js
import {bulk, DEFAULT_TEST_TIMEOUT, getTransport, getIndex} from '../../common.js'; import {bulk, DEFAULT_TEST_TIMEOUT, getTransport, getIndex} from '../../common.js';
import fs from 'fs'; import fs from 'fs';
import {backendConfig} from '../../../src/config.js'; import {backendConfig} from '../../../src/config.js';
import {readFile} from 'fs/promises';
import { import {
ACTIVE_INDICES_ALIAS, ACTIVE_INDICES_ALIAS,
getIndexUID, getIndexUID,
@@ -50,6 +49,8 @@ import {
} from '../../../src/storage/elasticsearch/util/index.js'; } from '../../../src/storage/elasticsearch/util/index.js';
import cron from 'node-cron'; import cron from 'node-cron';
import {query} from './query.js'; import {query} from './query.js';
import message from '@openstapps/core/test/resources/indexable/Message.1.json' assert {type: 'json'};
import book from '@openstapps/core/test/resources/indexable/Book.1.json' assert {type: 'json'};
use(chaiAsPromised); use(chaiAsPromised);
@@ -60,21 +61,12 @@ function searchResponse<T>(...hits: SearchHit<T>[]): SearchResponse<T> {
return {hits: {hits}, took: 0, timed_out: false, _shards: {total: 1, failed: 0, successful: 1}}; return {hits: {hits}, took: 0, timed_out: false, _shards: {total: 1, failed: 0, successful: 1}};
} }
const message = JSON.parse(
await readFile('node_modules/@openstapps/core/test/resources/indexable/Message.1.json', 'utf8'),
);
const book = JSON.parse(
await readFile('node_modules/@openstapps/core/test/resources/indexable/Book.1.json', 'utf8'),
);
describe('Elasticsearch', function () { describe('Elasticsearch', function () {
// increase timeout for the suite // increase timeout for the suite
this.timeout(DEFAULT_TEST_TIMEOUT); this.timeout(DEFAULT_TEST_TIMEOUT);
const sandbox = sinon.createSandbox(); const sandbox = sinon.createSandbox();
before(function () { before(function () {
// eslint-disable-next-line no-console
console.log('before');
sandbox.stub(fs, 'readFileSync').returns('{}'); sandbox.stub(fs, 'readFileSync').returns('{}');
}); });
after(function () { after(function () {
@@ -269,7 +261,7 @@ describe('Elasticsearch', function () {
return expect(es.init()).to.be.rejected; return expect(es.init()).to.be.rejected;
}); });
it('should setup the monitoring if there is monitoring is set and mail queue is defined', function () { it('should setup the monitoring if there is monitoring is set and mail queue is defined', async function () {
const config: SCConfigFile = { const config: SCConfigFile = {
...backendConfig, ...backendConfig,
internal: { internal: {
@@ -291,7 +283,7 @@ describe('Elasticsearch', function () {
const cronSetupStub = sandbox.stub(cron, 'schedule'); const cronSetupStub = sandbox.stub(cron, 'schedule');
const es = new Elasticsearch(config, new MailQueue(getTransport(false) as unknown as SMTP)); const es = new Elasticsearch(config, new MailQueue(getTransport(false) as unknown as SMTP));
es.init(); await es.init();
expect(cronSetupStub.called).to.be.true; expect(cronSetupStub.called).to.be.true;
}); });
@@ -445,7 +437,7 @@ describe('Elasticsearch', function () {
_id: '', _id: '',
_index: '', _index: '',
_score: 0, _score: 0,
_source: message as SCMessage, _source: message.instance as SCMessage,
}; };
sandbox.stub(es.client, 'search').resolves(searchResponse(foundObject)); sandbox.stub(es.client, 'search').resolves(searchResponse(foundObject));
@@ -475,7 +467,7 @@ describe('Elasticsearch', function () {
const object: SearchHit<SCMessage> = { const object: SearchHit<SCMessage> = {
_id: '', _id: '',
_index: oldIndex, _index: oldIndex,
_source: message as SCMessage, _source: message.instance as SCMessage,
}; };
sandbox.stub(es.client, 'search').resolves(searchResponse<SCMessage>(object)); sandbox.stub(es.client, 'search').resolves(searchResponse<SCMessage>(object));
sandbox.stub(es, 'prepareBulkWrite').resolves(index); sandbox.stub(es, 'prepareBulkWrite').resolves(index);
@@ -489,7 +481,7 @@ describe('Elasticsearch', function () {
sandbox.stub(es.client, 'create').resolves({result: 'not_found'} as CreateResponse); sandbox.stub(es.client, 'create').resolves({result: 'not_found'} as CreateResponse);
await es.init(); await es.init();
return expect(es.post(message as SCMessage, bulk)).to.rejectedWith('creation'); return expect(es.post(message.instance as SCMessage, bulk)).to.rejectedWith('creation');
}); });
it('should create a new object', async function () { it('should create a new object', async function () {
@@ -502,11 +494,11 @@ describe('Elasticsearch', function () {
}); });
await es.init(); await es.init();
await es.post(message as SCMessage, bulk); await es.post(message.instance as SCMessage, bulk);
expect(createStub.called).to.be.true; expect(createStub.called).to.be.true;
expect(caughtParameter.document).to.be.eql({ expect(caughtParameter.document).to.be.eql({
...message, ...message.instance,
creation_date: caughtParameter.document.creation_date, creation_date: caughtParameter.document.creation_date,
}); });
}); });
@@ -527,7 +519,7 @@ describe('Elasticsearch', function () {
_id: '', _id: '',
_index: getIndex(), _index: getIndex(),
_score: 0, _score: 0,
_source: message as SCMessage, _source: message.instance as SCMessage,
}; };
sandbox.stub(es.client, 'search').resolves(searchResponse()); sandbox.stub(es.client, 'search').resolves(searchResponse());
@@ -541,7 +533,7 @@ describe('Elasticsearch', function () {
_id: '', _id: '',
_index: getIndex(), _index: getIndex(),
_score: 0, _score: 0,
_source: message as SCMessage, _source: message.instance as SCMessage,
}; };
sandbox.stub(es.client, 'search').resolves(searchResponse(object)); sandbox.stub(es.client, 'search').resolves(searchResponse(object));
// @ts-expect-error unused // @ts-expect-error unused
@@ -564,13 +556,13 @@ describe('Elasticsearch', function () {
_id: '123', _id: '123',
_index: getIndex(), _index: getIndex(),
_score: 0, _score: 0,
_source: message as SCMessage, _source: message.instance as SCMessage,
}; };
const objectBook: SearchHit<SCBook> = { const objectBook: SearchHit<SCBook> = {
_id: '321', _id: '321',
_index: getIndex(), _index: getIndex(),
_score: 0, _score: 0,
_source: book as SCBook, _source: book.instance as SCBook,
}; };
const fakeEsAggregations = { const fakeEsAggregations = {
'@all': { '@all': {

View File

@@ -1,2 +1,2 @@
nginx & nginx &
node ./lib/cli.js node ./app.js

View File

@@ -14,7 +14,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
// ESM is not supported, and cts is not detected, so we use type-checked cjs instead.
/** @type {import('../src/common').ConfigFile} */ /** @type {import('../src/common').ConfigFile} */
const configFile = { const configFile = {
activeVersions: ['1\\.0\\.\\d+', '2\\.0\\.\\d+'], activeVersions: ['1\\.0\\.\\d+', '2\\.0\\.\\d+'],

View File

@@ -46,7 +46,6 @@
"@types/dockerode": "3.3.17", "@types/dockerode": "3.3.17",
"@types/node": "18.15.3", "@types/node": "18.15.3",
"@types/sha1": "1.1.3", "@types/sha1": "1.1.3",
"config": "3.3.9",
"dockerode": "3.3.5", "dockerode": "3.3.5",
"is-cidr": "4.0.2", "is-cidr": "4.0.2",
"mustache": "4.2.0", "mustache": "4.2.0",
@@ -59,7 +58,6 @@
"@openstapps/prettier-config": "workspace:*", "@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*", "@openstapps/tsconfig": "workspace:*",
"@types/chai": "4.3.5", "@types/chai": "4.3.5",
"@types/config": "3.3.0",
"@types/dockerode": "3.3.17", "@types/dockerode": "3.3.17",
"@types/mocha": "10.0.1", "@types/mocha": "10.0.1",
"@types/mustache": "4.2.2", "@types/mustache": "4.2.2",

View File

@@ -14,7 +14,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Logger, SMTP} from '@openstapps/logger'; import {Logger, SMTP} from '@openstapps/logger';
import config from 'config';
import {existsSync} from 'fs'; import {existsSync} from 'fs';
// set transport on logger // set transport on logger
@@ -163,7 +162,7 @@ ssl_stapling_verify on;`;
/** /**
* Config file * Config file
*/ */
export const configFile: ConfigFile = config.util.toObject(); export const configFile: ConfigFile = await import('../config/default.js').then(it => it.default);
/** /**
* Check if path is a specific file type * Check if path is a specific file type

View File

@@ -1,3 +1,4 @@
// @ts-check
/* /*
* Copyright (C) 2022 StApps * Copyright (C) 2022 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
@@ -12,9 +13,8 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import type {IconConfig} from './scripts/icon-config'; /** @type {import('./scripts/icon-config').IconConfig} */
const config = {
const config: IconConfig = {
inputPath: 'node_modules/material-symbols/material-symbols-rounded.woff2', inputPath: 'node_modules/material-symbols/material-symbols-rounded.woff2',
outputPath: 'src/assets/icons.min.woff2', outputPath: 'src/assets/icons.min.woff2',
htmlGlob: 'src/**/*.html', htmlGlob: 'src/**/*.html',

View File

@@ -21,7 +21,7 @@
"build:prod": "ng build --configuration=production", "build:prod": "ng build --configuration=production",
"build:stats": "ng build --configuration=production --stats-json", "build:stats": "ng build --configuration=production --stats-json",
"changelog": "conventional-changelog -p angular -i src/assets/about/CHANGELOG.md -s -r 0", "changelog": "conventional-changelog -p angular -i src/assets/about/CHANGELOG.md -s -r 0",
"check-icons": "ts-node-esm scripts/check-icon-correctness.ts", "check-icons": "node scripts/check-icon-correctness.mjs",
"chromium:no-cors": "chromium --disable-web-security --user-data-dir=\".browser-data/chromium\"", "chromium:no-cors": "chromium --disable-web-security --user-data-dir=\".browser-data/chromium\"",
"chromium:virtual-host": "chromium --host-resolver-rules=\"MAP mobile.app.uni-frankfurt.de:* localhost:8100\" --ignore-certificate-errors", "chromium:virtual-host": "chromium --host-resolver-rules=\"MAP mobile.app.uni-frankfurt.de:* localhost:8100\" --ignore-certificate-errors",
"cypress:open": "cypress open", "cypress:open": "cypress open",
@@ -38,7 +38,7 @@
"licenses": "license-checker --json > src/assets/about/licenses.json && ts-node ./scripts/accumulate-licenses.ts && git add src/assets/about/licenses.json", "licenses": "license-checker --json > src/assets/about/licenses.json && ts-node ./scripts/accumulate-licenses.ts && git add src/assets/about/licenses.json",
"lint": "ng lint && stylelint \"**/*.scss\"", "lint": "ng lint && stylelint \"**/*.scss\"",
"lint:fix": "eslint --fix -c .eslintrc.json --ignore-path .eslintignore --ext .ts,.html src/ && stylelint --fix \"**/*.scss\"", "lint:fix": "eslint --fix -c .eslintrc.json --ignore-path .eslintignore --ext .ts,.html src/ && stylelint --fix \"**/*.scss\"",
"minify-icons": "ts-node-esm scripts/minify-icon-font.ts", "minify-icons": "node scripts/minify-icon-font.mjs",
"postinstall": "jetify && echo \"skipping jetify in production mode\"", "postinstall": "jetify && echo \"skipping jetify in production mode\"",
"preview": "http-server www --p 8101 -o", "preview": "http-server www --p 8101 -o",
"push": "git push && git push origin \"v$npm_package_version\"", "push": "git push && git push origin \"v$npm_package_version\"",

View File

@@ -1,3 +1,4 @@
// @ts-check
/* /*
* Copyright (C) 2022 StApps * Copyright (C) 2022 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
@@ -13,13 +14,14 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import fs from 'fs'; import fs from 'fs';
import {omit} from '../src/app/_helpers/collections/omit'; import {omit, pickBy} from '@openstapps/collection-utils';
import {pickBy} from '../src/app/_helpers/collections/pick';
/** /**
* accumulate and transform licenses based on two license files * accumulate and transform licenses based on two license files
* @param {string} path
* @param {string} additionalLicensesPath
*/ */
function accumulateFile(path: string, additionalLicensesPath: string) { function accumulateFile(path, additionalLicensesPath) {
const packageJson = JSON.parse(fs.readFileSync('./package.json').toString()); const packageJson = JSON.parse(fs.readFileSync('./package.json').toString());
const dependencies = packageJson.dependencies; const dependencies = packageJson.dependencies;
@@ -28,10 +30,9 @@ function accumulateFile(path: string, additionalLicensesPath: string) {
fs.writeFileSync( fs.writeFileSync(
path, path,
JSON.stringify( JSON.stringify(
// eslint-disable-next-line @typescript-eslint/no-explicit-any Object.entries({
Object.entries<any>({ ...pickBy(JSON.parse(fs.readFileSync(path).toString()), (_, key) => {
...pickBy(JSON.parse(fs.readFileSync(path).toString()), (_, key: string) => { const parts = /** @type {string} */ (key).split('@');
const parts = key.split('@');
return dependencies[parts.slice(0, -1).join('@')] === parts[parts.length - 1]; return dependencies[parts.slice(0, -1).join('@')] === parts[parts.length - 1];
}), }),

View File

@@ -1,3 +1,4 @@
// @ts-check
/* /*
* Copyright (C) 2022 StApps * Copyright (C) 2022 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
@@ -12,18 +13,18 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import fontkit, {Font} from 'fontkit'; import {openSync} from 'fontkit';
import config from '../icons.config'; import config from '../icons.config.mjs';
import {existsSync} from 'fs'; import {existsSync} from 'fs';
import {getUsedIconsHtml, getUsedIconsTS} from './gather-used-icons'; import {getUsedIconsHtml, getUsedIconsTS} from './gather-used-icons.mjs';
const commandName = '"npm run minify-icons"'; const commandName = '"npm run minify-icons"';
const originalFont = fontkit.openSync(config.inputPath); const originalFont = openSync(config.inputPath);
if (!existsSync(config.outputPath)) { if (!existsSync(config.outputPath)) {
console.error(`Minified font not found. Run ${commandName} first.`); console.error(`Minified font not found. Run ${commandName} first.`);
process.exit(-1); process.exit(-1);
} }
const modifiedFont = fontkit.openSync(config.outputPath); const modifiedFont = openSync(config.outputPath);
let success = true; let success = true;
@@ -48,9 +49,9 @@ async function checkAll() {
} }
/** /**
* * @param {Record<string, string[]>} icons
*/ */
function check(icons: Record<string, string[]>) { function check(icons) {
for (const [purpose, iconSet] of Object.entries(icons)) { for (const [purpose, iconSet] of Object.entries(icons)) {
for (const icon of iconSet) { for (const icon of iconSet) {
if (!hasIcon(originalFont, icon)) { if (!hasIcon(originalFont, icon)) {
@@ -65,8 +66,9 @@ function check(icons: Record<string, string[]>) {
} }
/** /**
* * @param {import('fontkit').Font} font
* @param {string} icon
*/ */
function hasIcon(font: Font, icon: string) { function hasIcon(font, icon) {
return font.layout(icon).glyphs.some(it => it.isLigature); return font.layout(icon).glyphs.some(it => it.isLigature);
} }

View File

@@ -1,3 +1,4 @@
// @ts-check
/* /*
* Copyright (C) 2022 StApps * Copyright (C) 2022 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
@@ -14,31 +15,33 @@
*/ */
import {glob} from 'glob'; import {glob} from 'glob';
import {readFileSync} from 'fs'; import {readFileSync} from 'fs';
import {matchPropertyContent, matchTagProperties} from '../src/app/util/ion-icon/icon-match'; import {matchPropertyContent, matchTagProperties} from '../src/app/util/ion-icon/icon-match.mjs';
/** /**
* * @returns {Promise<Record<string, string[]>>}
*/ */
export async function getUsedIconsHtml(pattern = 'src/**/*.html'): Promise<Record<string, string[]>> { export async function getUsedIconsHtml(pattern = 'src/**/*.html') {
return Object.fromEntries( return Object.fromEntries(
(await glob(pattern)) (await glob(pattern))
.map(file => [ .map(file => [
file, file,
(readFileSync(file, 'utf8') /** @type {string[]} */ (
.match(matchTagProperties('ion-icon')) readFileSync(file, 'utf8')
?.flatMap(match => { .match(matchTagProperties('ion-icon'))
return match.match(matchPropertyContent(['name', 'md', 'ios'])); ?.flatMap(match => {
}) return match.match(matchPropertyContent(['name', 'md', 'ios']));
.filter(it => !!it) as string[]) || [], })
.filter(it => !!it)
) || [],
]) ])
.filter(([, values]) => values.length > 0), .filter(([, values]) => values.length > 0),
); );
} }
/** /**
* * @returns {Promise<Record<string, string[]>>}
*/ */
export async function getUsedIconsTS(pattern = 'src/**/*.ts'): Promise<Record<string, string[]>> { export async function getUsedIconsTS(pattern = 'src/**/*.ts') {
return Object.fromEntries( return Object.fromEntries(
(await glob(pattern)) (await glob(pattern))
.map(file => [file, readFileSync(file, 'utf8').match(/(?<=Icon`)[\w-]+(?=`)/g) || []]) .map(file => [file, readFileSync(file, 'utf8').match(/(?<=Icon`)[\w-]+(?=`)/g) || []])

View File

@@ -1,3 +1,4 @@
// @ts-check
/* /*
* Copyright (C) 2022 StApps * Copyright (C) 2022 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
@@ -12,18 +13,17 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {openSync} from 'fontkit';
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import fontkit from 'fontkit';
import {exec} from 'child_process'; import {exec} from 'child_process';
import config from '../icons.config'; import config from '../icons.config.mjs';
import {statSync} from 'fs'; import {statSync} from 'fs';
import {getUsedIconsHtml, getUsedIconsTS} from './gather-used-icons'; import {getUsedIconsHtml, getUsedIconsTS} from './gather-used-icons.mjs';
/** /**
* * @param {string[] | string} command
* @returns {Promise<string>}
*/ */
async function run(command: string[] | string): Promise<string> { async function run(command) {
const fullCommand = Array.isArray(command) ? command.join(' ') : command; const fullCommand = Array.isArray(command) ? command.join(' ') : command;
console.log(`>> ${fullCommand}`); console.log(`>> ${fullCommand}`);
@@ -44,7 +44,8 @@ async function run(command: string[] | string): Promise<string> {
* *
*/ */
async function minifyIconFont() { async function minifyIconFont() {
const icons = new Set<string>(); /** @type {Set<string>} */
const icons = new Set();
for (const iconSet of [ for (const iconSet of [
...Object.values(config.additionalIcons || []), ...Object.values(config.additionalIcons || []),
@@ -57,9 +58,10 @@ async function minifyIconFont() {
} }
console.log('Icons used:', [...icons.values()].sort()); console.log('Icons used:', [...icons.values()].sort());
const font = fontkit.openSync(config.inputPath); const font = openSync(config.inputPath);
const glyphs: string[] = ['5f-7a', '30-39']; /** @type {string[]} */
const glyphs = ['5f-7a', '30-39'];
for (const icon of icons) { for (const icon of icons) {
const iconGlyphs = font.layout(icon).glyphs; const iconGlyphs = font.layout(icon).glyphs;
if (iconGlyphs.length === 0) { if (iconGlyphs.length === 0) {
@@ -70,7 +72,7 @@ async function minifyIconFont() {
const codePoints = iconGlyphs const codePoints = iconGlyphs
.flatMap(it => font.stringsForGlyph(it.id)) .flatMap(it => font.stringsForGlyph(it.id))
.flatMap(it => [...it]) .flatMap(it => [...it])
.map(it => it.codePointAt(0)!.toString(16)); .map(it => it.codePointAt(0).toString(16));
if (codePoints.length === 0) { if (codePoints.length === 0) {
if (config.codePoints?.[icon]) { if (config.codePoints?.[icon]) {
@@ -114,8 +116,10 @@ minifyIconFont();
/** /**
* Bytes to respective units * Bytes to respective units
* @param {number} value
* @returns {string}
*/ */
function toByteUnit(value: number): string { function toByteUnit(value) {
if (value < 1024) { if (value < 1024) {
return `${value}B`; return `${value}B`;
} else if (value < 1024 * 1024) { } else if (value < 1024 * 1024) {

View File

@@ -1,7 +0,0 @@
{
"extends": "@openstapps/tsconfig",
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node"
}
}

View File

@@ -1,3 +1,4 @@
// @ts-check
/* /*
* Copyright (C) 2022 StApps * Copyright (C) 2022 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
@@ -14,16 +15,16 @@
*/ */
/** /**
* * @param {string} tag
*/ */
export function matchTagProperties(tag: string) { export function matchTagProperties(tag) {
return new RegExp(`(?<=<${tag})[\\s\\S]*?(?=>\\s*<\\/${tag}\\s*>)`, 'g'); return new RegExp(`(?<=<${tag})[\\s\\S]*?(?=>\\s*<\\/${tag}\\s*>)`, 'g');
} }
/** /**
* * @param {string[]} properties
*/ */
export function matchPropertyContent(properties: string[]) { export function matchPropertyContent(properties) {
const names = properties.join('|'); const names = properties.join('|');
return new RegExp(`((?<=(${names})=")[\\w-]+(?="))|((?<=\\[(${names})]="')[\\w-]+(?='"))`, 'g'); return new RegExp(`((?<=(${names})=")[\\w-]+(?="))|((?<=\\[(${names})]="')[\\w-]+(?='"))`, 'g');

View File

@@ -13,8 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* eslint-disable unicorn/no-null */ /* eslint-disable unicorn/no-null */
import {matchPropertyContent, matchTagProperties} from './icon-match.mjs';
import {matchPropertyContent, matchTagProperties} from './icon-match';
describe('matchTagProperties', function () { describe('matchTagProperties', function () {
const regex = matchTagProperties('test'); const regex = matchTagProperties('test');

View File

@@ -0,0 +1,7 @@
{
"extension": ["ts"],
"require": "ts-node/register",
"reporter": "mocha-junit-reporter",
"reporter-option": ["mochaFile=coverage/report-junit.xml"],
"spec": ["test/**/*.spec.ts"]
}

View File

@@ -18,8 +18,10 @@
"noUnusedParameters": true, "noUnusedParameters": true,
"outDir": "./lib/", "outDir": "./lib/",
"strict": true, "strict": true,
"skipLibCheck": true,
"target": "ES2020" "target": "ES2020"
}, },
"exclude": ["./lib/", "./test/"] "exclude": [
"./lib/",
"./test/"
]
} }