Convert copy-res to typescript

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2023-09-08 10:33:57 +01:00
parent 501f01cdd5
commit c944a273d0
No known key found for this signature in database
GPG key ID: A2B008A5F49F5D0D
8 changed files with 127 additions and 38 deletions

View file

@ -19,7 +19,7 @@ module.exports = {
}, },
overrides: [ overrides: [
{ {
files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}"], files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}", "scripts/*.ts"],
extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"], extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"],
// NOTE: These rules are frozen and new rules should not be added here. // NOTE: These rules are frozen and new rules should not be added here.
// New changes belong in https://github.com/matrix-org/eslint-plugin-matrix-org/ // New changes belong in https://github.com/matrix-org/eslint-plugin-matrix-org/

View file

@ -38,7 +38,7 @@
"build": "yarn clean && yarn build:genfiles && yarn build:bundle", "build": "yarn clean && yarn build:genfiles && yarn build:bundle",
"build-stats": "yarn clean && yarn build:genfiles && yarn build:bundle-stats", "build-stats": "yarn clean && yarn build:genfiles && yarn build:bundle-stats",
"build:jitsi": "ts-node scripts/build-jitsi.ts", "build:jitsi": "ts-node scripts/build-jitsi.ts",
"build:res": "node scripts/copy-res.js", "build:res": "ts-node scripts/copy-res.ts",
"build:genfiles": "yarn build:res && yarn build:jitsi && yarn build:module_system", "build:genfiles": "yarn build:res && yarn build:jitsi && yarn build:module_system",
"build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js", "build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js",
"build:bundle": "webpack --progress --bail --mode production", "build:bundle": "webpack --progress --bail --mode production",
@ -47,7 +47,7 @@
"dist": "scripts/package.sh", "dist": "scripts/package.sh",
"start": "yarn build:module_system && concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n res,element-js \"yarn start:res\" \"yarn start:js\"", "start": "yarn build:module_system && concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n res,element-js \"yarn start:res\" \"yarn start:js\"",
"start:https": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n res,element-js \"yarn start:res\" \"yarn start:js --https\"", "start:https": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n res,element-js \"yarn start:res\" \"yarn start:js --https\"",
"start:res": "yarn build:jitsi && node scripts/copy-res.js -w", "start:res": "yarn build:jitsi && ts-node scripts/copy-res.ts -w",
"start:js": "webpack-dev-server --output-filename=bundles/_dev_/[name].js --output-chunk-filename=bundles/_dev_/[name].js -w --mode development --disable-host-check --hot", "start:js": "webpack-dev-server --output-filename=bundles/_dev_/[name].js --output-chunk-filename=bundles/_dev_/[name].js -w --mode development --disable-host-check --hot",
"lint": "yarn lint:types && yarn lint:js && yarn lint:style", "lint": "yarn lint:types && yarn lint:js && yarn lint:style",
"lint:js": "yarn lint:js:src && yarn lint:js:module_system", "lint:js": "yarn lint:js:src && yarn lint:js:module_system",
@ -106,9 +106,11 @@
"@sentry/webpack-plugin": "^2.0.0", "@sentry/webpack-plugin": "^2.0.0",
"@svgr/webpack": "^5.5.0", "@svgr/webpack": "^5.5.0",
"@testing-library/react": "^12.1.5", "@testing-library/react": "^12.1.5",
"@types/cpx": "1.5.0",
"@types/jest": "^29.0.0", "@types/jest": "^29.0.0",
"@types/jitsi-meet": "^2.0.2", "@types/jitsi-meet": "^2.0.2",
"@types/jsrsasign": "^10.5.4", "@types/jsrsasign": "^10.5.4",
"@types/loader-utils": "^2.0.4",
"@types/lodash": "^4.14.197", "@types/lodash": "^4.14.197",
"@types/modernizr": "^3.5.3", "@types/modernizr": "^3.5.3",
"@types/node": "^16", "@types/node": "^16",
@ -123,7 +125,7 @@
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"chokidar": "^3.5.1", "chokidar": "^3.5.1",
"concurrently": "^8.0.0", "concurrently": "^8.0.0",
"cpx": "^1.5.0", "cpx": "1.5.0",
"css-loader": "^4", "css-loader": "^4",
"dotenv": "^16.0.2", "dotenv": "^16.0.2",
"eslint": "8.48.0", "eslint": "8.48.0",

View file

@ -1,16 +1,27 @@
#!/usr/bin/env node #!/usr/bin/env node
const loaderUtils = require("loader-utils");
// copies the resources into the webapp directory. // copies the resources into the webapp directory.
import parseArgs from "minimist";
import * as chokidar from "chokidar";
import * as fs from "node:fs";
import * as _ from "lodash";
import * as Cpx from "cpx";
import * as loaderUtils from "loader-utils";
const I18N_BASE_PATH = "src/i18n/strings/"; const I18N_BASE_PATH = "src/i18n/strings/";
const INCLUDE_LANGS = fs.readdirSync(I18N_BASE_PATH).filter((fn) => fn.endsWith(".json")); const INCLUDE_LANGS = fs.readdirSync(I18N_BASE_PATH).filter((fn) => fn.endsWith(".json"));
// cpx includes globbed parts of the filename in the destination, but excludes // cpx includes globbed parts of the filename in the destination, but excludes
// common parents. Hence, "res/{a,b}/**": the output will be "dest/a/..." and // common parents. Hence, "res/{a,b}/**": the output will be "dest/a/..." and
// "dest/b/...". // "dest/b/...".
const COPY_LIST = [ const COPY_LIST: [
sourceGlob: string,
outputPath: string,
opts?: {
directwatch?: 1;
},
][] = [
["res/apple-app-site-association", "webapp"], ["res/apple-app-site-association", "webapp"],
["res/manifest.json", "webapp"], ["res/manifest.json", "webapp"],
["res/sw.js", "webapp"], ["res/sw.js", "webapp"],
@ -24,19 +35,12 @@ const COPY_LIST = [
["./config.json", "webapp", { directwatch: 1 }], ["./config.json", "webapp", { directwatch: 1 }],
["contribute.json", "webapp"], ["contribute.json", "webapp"],
]; ];
const parseArgs = require("minimist");
const Cpx = require("cpx");
const chokidar = require("chokidar");
const fs = require("fs");
const _ = require("lodash");
const argv = parseArgs(process.argv.slice(2), {}); const argv = parseArgs(process.argv.slice(2), {});
const watch = argv.w; const watch = argv.w;
const verbose = argv.v; const verbose = argv.v;
function errCheck(err) { function errCheck(err?: Error): void {
if (err) { if (err) {
console.error(err.message); console.error(err.message);
process.exit(1); process.exit(1);
@ -52,7 +56,7 @@ if (!fs.existsSync("webapp/i18n/")) {
fs.mkdirSync("webapp/i18n/"); fs.mkdirSync("webapp/i18n/");
} }
function next(i, err) { function next(i: number, err?: Error): void {
errCheck(err); errCheck(err);
if (i >= COPY_LIST.length) { if (i >= COPY_LIST.length) {
@ -63,13 +67,9 @@ function next(i, err) {
const source = ent[0]; const source = ent[0];
const dest = ent[1]; const dest = ent[1];
const opts = ent[2] || {}; const opts = ent[2] || {};
let cpx = undefined; const cpx = new Cpx.Cpx(source, dest);
if (!opts.lang) { if (verbose) {
cpx = new Cpx.Cpx(source, dest);
}
if (verbose && cpx) {
cpx.on("copy", (event) => { cpx.on("copy", (event) => {
console.log(`Copied: ${event.srcPath} --> ${event.dstPath}`); console.log(`Copied: ${event.srcPath} --> ${event.dstPath}`);
}); });
@ -78,7 +78,7 @@ function next(i, err) {
}); });
} }
const cb = (err) => { const cb = (err?: Error): void => {
next(i + 1, err); next(i + 1, err);
}; };
@ -88,7 +88,7 @@ function next(i, err) {
// which in the case of config.json is '.', which inevitably takes // which in the case of config.json is '.', which inevitably takes
// ages to crawl. So we create our own watcher on the files // ages to crawl. So we create our own watcher on the files
// instead. // instead.
const copy = () => { const copy = (): void => {
cpx.copy(errCheck); cpx.copy(errCheck);
}; };
chokidar.watch(source).on("add", copy).on("change", copy).on("ready", cb).on("error", errCheck); chokidar.watch(source).on("add", copy).on("change", copy).on("ready", cb).on("error", errCheck);
@ -102,7 +102,7 @@ function next(i, err) {
} }
} }
function genLangFile(lang, dest) { function genLangFile(lang: string, dest: string): string {
const reactSdkFile = "node_modules/matrix-react-sdk/src/i18n/strings/" + lang + ".json"; const reactSdkFile = "node_modules/matrix-react-sdk/src/i18n/strings/" + lang + ".json";
const riotWebFile = I18N_BASE_PATH + lang + ".json"; const riotWebFile = I18N_BASE_PATH + lang + ".json";
@ -120,7 +120,7 @@ function genLangFile(lang, dest) {
const json = JSON.stringify(translations, null, 4); const json = JSON.stringify(translations, null, 4);
const jsonBuffer = Buffer.from(json); const jsonBuffer = Buffer.from(json);
const digest = loaderUtils.getHashDigest(jsonBuffer, null, null, 7); const digest = loaderUtils.getHashDigest(jsonBuffer, null, "hex", 7);
const filename = `${lang}.${digest}.json`; const filename = `${lang}.${digest}.json`;
fs.writeFileSync(dest + filename, json); fs.writeFileSync(dest + filename, json);
@ -131,8 +131,8 @@ function genLangFile(lang, dest) {
return filename; return filename;
} }
function genLangList(langFileMap) { function genLangList(langFileMap: Record<string, string>): void {
const languages = {}; const languages: Record<string, string> = {};
INCLUDE_LANGS.forEach(function (lang) { INCLUDE_LANGS.forEach(function (lang) {
const normalizedLanguage = lang.toLowerCase().replace("_", "-"); const normalizedLanguage = lang.toLowerCase().replace("_", "-");
const languageParts = normalizedLanguage.split("-"); const languageParts = normalizedLanguage.split("-");
@ -144,7 +144,7 @@ function genLangList(langFileMap) {
}); });
fs.writeFile("webapp/i18n/languages.json", JSON.stringify(languages, null, 4), function (err) { fs.writeFile("webapp/i18n/languages.json", JSON.stringify(languages, null, 4), function (err) {
if (err) { if (err) {
console.error("Copy Error occured: " + err); console.error("Copy Error occured: " + err.message);
throw new Error("Failed to generate languages.json"); throw new Error("Failed to generate languages.json");
} }
}); });
@ -158,15 +158,15 @@ function genLangList(langFileMap) {
* regenerate the file, adding its content-hashed filename to langFileMap * regenerate the file, adding its content-hashed filename to langFileMap
* and regenerating languages.json with the new filename * and regenerating languages.json with the new filename
*/ */
function watchLanguage(lang, dest, langFileMap) { function watchLanguage(lang: string, dest: string, langFileMap: Record<string, string>): void {
const reactSdkFile = "node_modules/matrix-react-sdk/src/i18n/strings/" + lang + ".json"; const reactSdkFile = "node_modules/matrix-react-sdk/src/i18n/strings/" + lang + ".json";
const riotWebFile = I18N_BASE_PATH + lang + ".json"; const riotWebFile = I18N_BASE_PATH + lang + ".json";
// XXX: Use a debounce because for some reason if we read the language // XXX: Use a debounce because for some reason if we read the language
// file immediately after the FS event is received, the file contents // file immediately after the FS event is received, the file contents
// appears empty. Possibly https://github.com/nodejs/node/issues/6112 // appears empty. Possibly https://github.com/nodejs/node/issues/6112
let makeLangDebouncer; let makeLangDebouncer: number;
const makeLang = () => { const makeLang = (): void => {
if (makeLangDebouncer) { if (makeLangDebouncer) {
clearTimeout(makeLangDebouncer); clearTimeout(makeLangDebouncer);
} }
@ -184,7 +184,7 @@ function watchLanguage(lang, dest, langFileMap) {
// language resources // language resources
const I18N_DEST = "webapp/i18n/"; const I18N_DEST = "webapp/i18n/";
const I18N_FILENAME_MAP = INCLUDE_LANGS.reduce((m, l) => { const I18N_FILENAME_MAP = INCLUDE_LANGS.reduce<Record<string, string>>((m, l) => {
const filename = genLangFile(l, I18N_DEST); const filename = genLangFile(l, I18N_DEST);
m[l] = filename; m[l] = filename;
return m; return m;
@ -192,7 +192,7 @@ const I18N_FILENAME_MAP = INCLUDE_LANGS.reduce((m, l) => {
genLangList(I18N_FILENAME_MAP); genLangList(I18N_FILENAME_MAP);
if (watch) { if (watch) {
INCLUDE_LANGS.forEach((l) => watchLanguage(l.value, I18N_DEST, I18N_FILENAME_MAP)); INCLUDE_LANGS.forEach((l) => watchLanguage(l, I18N_DEST, I18N_FILENAME_MAP));
} }
// non-language resources // non-language resources

43
src/@types/cpx.d.ts vendored Normal file
View file

@ -0,0 +1,43 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import "cpx";
import type EventEmitter from "events";
declare module "cpx" {
export class Cpx extends EventEmitter {
public constructor(source: string, outDir: string, options?: object);
/**
* Copy all files that matches `this.source` pattern to `this.outDir`.
*
* @param {function} [cb = null] - A callback function.
* @returns {void}
*/
public copy(cb: Function | null): void;
/**
* Copy all files that matches `this.source` pattern to `this.outDir`.
* And watch changes in `this.base`, and copy only the file every time.
*
* @returns {void}
* @throws {Error} This had been watching already.
*/
public watch(): void;
}
}
export as namespace Cpx;

28
src/@types/loader-utils.d.ts vendored Normal file
View file

@ -0,0 +1,28 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as LoaderUtils from "loader-utils";
declare module "loader-utils" {
export function getHashDigest(
buffer: Buffer,
hashType: null,
digestType: LoaderUtils.DigestType,
maxLength: number,
): string;
}
export as namespace Cpx;

View file

@ -24,6 +24,7 @@
"./src/**/*.ts", "./src/**/*.ts",
"./src/**/*.tsx", "./src/**/*.tsx",
"./test/**/*.ts", "./test/**/*.ts",
"./test/**/*.tsx" "./test/**/*.tsx",
"./scripts/*.ts"
] ]
} }

View file

@ -507,7 +507,7 @@ module.exports = (env, argv) => {
}, },
{ {
// cache-bust languages.json file placed in // cache-bust languages.json file placed in
// element-web/webapp/i18n during build by copy-res.js // element-web/webapp/i18n during build by copy-res.ts
test: /\.*languages.json$/, test: /\.*languages.json$/,
type: "javascript/auto", type: "javascript/auto",
loader: "file-loader", loader: "file-loader",

View file

@ -2398,6 +2398,13 @@
dependencies: dependencies:
"@babel/types" "^7.20.7" "@babel/types" "^7.20.7"
"@types/cpx@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@types/cpx/-/cpx-1.5.0.tgz#d2a44b0ab5ec32bfdd743f64aae84847858bce0c"
integrity sha512-kuGK3lZqEvHTSDbJcaA6tcPoEXV4/e88YrltZMcQUewZhzYQwNSTMGIiPBqeeFd4LCBo1CX5U6CV6LaHG3wXSg==
dependencies:
"@types/node" "*"
"@types/events@^3.0.0": "@types/events@^3.0.0":
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
@ -2492,6 +2499,14 @@
resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.8.tgz#0d6c638505454b5e95c684d6f604d57641417336" resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.8.tgz#0d6c638505454b5e95c684d6f604d57641417336"
integrity sha512-1oZ3TbarAhKtKUpyrCIqXpbx3ZAfoSulleJs6/UzzyYty0ut+kjRX7zHLAaHwVIuw8CBjIymwW4J2LK944HoHQ== integrity sha512-1oZ3TbarAhKtKUpyrCIqXpbx3ZAfoSulleJs6/UzzyYty0ut+kjRX7zHLAaHwVIuw8CBjIymwW4J2LK944HoHQ==
"@types/loader-utils@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@types/loader-utils/-/loader-utils-2.0.4.tgz#f1c9dd27392f163ee92394454563286dfc6e4778"
integrity sha512-I71X8yySVQW6DuXr78/McC+enpUYQ68JxAYlgVyuMvl5mb7jFUZpFAu1qURZcwvbITXwxPnrA7hbV0W3HHsbbg==
dependencies:
"@types/node" "*"
"@types/webpack" "^4"
"@types/lodash@^4.14.197": "@types/lodash@^4.14.197":
version "4.14.198" version "4.14.198"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.198.tgz#4d27465257011aedc741a809f1269941fa2c5d4c" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.198.tgz#4d27465257011aedc741a809f1269941fa2c5d4c"
@ -2651,7 +2666,7 @@
"@types/source-list-map" "*" "@types/source-list-map" "*"
source-map "^0.7.3" source-map "^0.7.3"
"@types/webpack@^4.41.8": "@types/webpack@^4", "@types/webpack@^4.41.8":
version "4.41.33" version "4.41.33"
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.33.tgz#16164845a5be6a306bcbe554a8e67f9cac215ffc" resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.33.tgz#16164845a5be6a306bcbe554a8e67f9cac215ffc"
integrity sha512-PPajH64Ft2vWevkerISMtnZ8rTs4YmRbs+23c402J0INmxDKCrhZNvwZYtzx96gY2wAtXdrK1BS2fiC8MlLr3g== integrity sha512-PPajH64Ft2vWevkerISMtnZ8rTs4YmRbs+23c402J0INmxDKCrhZNvwZYtzx96gY2wAtXdrK1BS2fiC8MlLr3g==
@ -4449,7 +4464,7 @@ counterpart@^0.18.6:
pluralizers "^0.1.7" pluralizers "^0.1.7"
sprintf-js "^1.0.3" sprintf-js "^1.0.3"
cpx@^1.5.0: cpx@1.5.0:
version "1.5.0" version "1.5.0"
resolved "https://registry.yarnpkg.com/cpx/-/cpx-1.5.0.tgz#185be018511d87270dedccc293171e37655ab88f" resolved "https://registry.yarnpkg.com/cpx/-/cpx-1.5.0.tgz#185be018511d87270dedccc293171e37655ab88f"
integrity sha512-jHTjZhsbg9xWgsP2vuNW2jnnzBX+p4T+vNI9Lbjzs1n4KhOfa22bQppiFYLsWQKd8TzmL5aSP/Me3yfsCwXbDA== integrity sha512-jHTjZhsbg9xWgsP2vuNW2jnnzBX+p4T+vNI9Lbjzs1n4KhOfa22bQppiFYLsWQKd8TzmL5aSP/Me3yfsCwXbDA==