Use TS with gulp

This commit is contained in:
Wendelin
2025-05-09 08:23:53 +02:00
parent e1b099e88b
commit ff3b65605e
33 changed files with 325 additions and 200 deletions

View File

@@ -1,19 +1,20 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import gulp from "gulp";
import env from "../env.cjs";
import "./clean.js";
import "./compress.js";
import "./entry-html.js";
import "./gather-static.js";
import "./gen-icons-json.js";
import "./locale-data.js";
import "./service-worker.js";
import "./translations.js";
import "./rspack.js";
import "./clean.ts";
import "./compress.ts";
import env from "../env";
import "./entry-html.ts";
import "./gather-static.ts";
import "./gen-icons-json.ts";
import "./locale-data.ts";
import "./rspack.ts";
import "./service-worker.ts";
import "./translations.ts";
gulp.task(
"develop-app",
gulp.series(
async function setEnv() {
async () => {
process.env.NODE_ENV = "development";
},
"clean",
@@ -32,7 +33,7 @@ gulp.task(
gulp.task(
"build-app",
gulp.series(
async function setEnv() {
async () => {
process.env.NODE_ENV = "production";
},
"clean",
@@ -48,7 +49,7 @@ gulp.task(
gulp.task(
"analyze-app",
gulp.series(
async function setEnv() {
async () => {
process.env.STATS = "1";
},
"clean",

View File

@@ -1,15 +1,16 @@
/* eslint-disable import/no-extraneous-dependencies */
import gulp from "gulp";
import "./clean.js";
import "./entry-html.js";
import "./gather-static.js";
import "./service-worker.js";
import "./translations.js";
import "./rspack.js";
import "./clean.ts";
import "./entry-html.ts";
import "./gather-static.ts";
import "./rspack.ts";
import "./service-worker.ts";
import "./translations.ts";
gulp.task(
"develop-cast",
gulp.series(
async function setEnv() {
async () => {
process.env.NODE_ENV = "development";
},
"clean-cast",
@@ -24,7 +25,7 @@ gulp.task(
gulp.task(
"build-cast",
gulp.series(
async function setEnv() {
async () => {
process.env.NODE_ENV = "production";
},
"clean-cast",

View File

@@ -1,6 +1,6 @@
import { deleteSync } from "del";
import gulp from "gulp";
import paths from "../paths.cjs";
import paths from "../paths";
import "./translations.js";
gulp.task(

View File

@@ -1,10 +1,10 @@
// Tasks to compress
import { constants } from "node:zlib";
import gulp from "gulp";
import brotli from "gulp-brotli";
import zopfli from "gulp-zopfli-green";
import paths from "../paths.cjs";
import { constants } from "node:zlib";
import paths from "../paths";
const filesGlob = "*.{js,json,css,svg,xml}";
const brotliOptions = {

View File

@@ -1,10 +1,10 @@
import { LokaliseApi } from "@lokalise/node-api";
import fs from "fs/promises";
import gulp from "gulp";
import path from "path";
import mapStream from "map-stream";
import transform from "gulp-json-transform";
import { LokaliseApi } from "@lokalise/node-api";
import JSZip from "jszip";
import mapStream from "map-stream";
import path from "path";
const inDir = "translations";
const inDirFrontend = `${inDir}/frontend`;
@@ -16,7 +16,7 @@ function hasHtml(data) {
return /<\S*>/i.test(data);
}
function recursiveCheckHasHtml(file, data, errors, recKey) {
function recursiveCheckHasHtml(file, data, errors: string[], recKey?: string) {
Object.keys(data).forEach(function (key) {
if (typeof data[key] === "object") {
const nextRecKey = recKey ? `${recKey}.${key}` : key;
@@ -80,7 +80,7 @@ gulp.task("check-translations-html", function () {
gulp.task("check-all-files-exist", async function () {
const file = await fs.readFile(srcMeta, { encoding });
const meta = JSON.parse(file);
const writings = [];
const writings: Promise<void>[] = [];
Object.keys(meta).forEach((lang) => {
writings.push(
fs.writeFile(`${inDirFrontend}/${lang}.json`, JSON.stringify({}), {

View File

@@ -10,8 +10,8 @@ import gulp from "gulp";
import { minify } from "html-minifier-terser";
import template from "lodash.template";
import { dirname, extname, resolve } from "node:path";
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
import paths from "../paths.cjs";
import { htmlMinifierOptions, terserOptions } from "../bundle";
import paths from "../paths";
// macOS companion app has no way to obtain the Safari version used by WKWebView,
// and it is not in the default user agent string. So we add an additional regex
@@ -36,7 +36,7 @@ const getCommonTemplateVars = () => {
});
const minSafariVersion = browserRegexes.find(
(regex) => regex.family === "safari"
)?.matchedVersions[0][0];
)?.matchedVersions[0][0] ?? 18;
const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion];
if (!minMacOSVersion) {
throw Error(
@@ -106,10 +106,10 @@ const genPagesDevTask =
resolve(inputRoot, inputSub, `${page}.template`),
{
...commonVars,
latestEntryJS: entries.map(
latestEntryJS: (entries as string[]).map(
(entry) => `${publicRoot}/frontend_latest/${entry}.js`
),
es5EntryJS: entries.map(
es5EntryJS: (entries as string[]).map(
(entry) => `${publicRoot}/frontend_es5/${entry}.js`
),
latestCustomPanelJS: `${publicRoot}/frontend_latest/custom-panel.js`,
@@ -128,7 +128,7 @@ const genPagesProdTask =
inputRoot,
outputRoot,
outputLatest,
outputES5,
outputES5?: string,
inputSub = "src/html"
) =>
async () => {
@@ -139,14 +139,14 @@ const genPagesProdTask =
? fs.readJsonSync(resolve(outputES5, "manifest.json"))
: {};
const commonVars = getCommonTemplateVars();
const minifiedHTML = [];
const minifiedHTML: Promise<void>[] = [];
for (const [page, entries] of Object.entries(pageEntries)) {
const content = renderTemplate(
resolve(inputRoot, inputSub, `${page}.template`),
{
...commonVars,
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
latestEntryJS: (entries as string[]).map((entry) => latestManifest[`${entry}.js`]),
es5EntryJS: (entries as string[]).map((entry) => es5Manifest[`${entry}.js`]),
latestCustomPanelJS: latestManifest["custom-panel.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"],
}

View File

@@ -54,7 +54,7 @@ gulp.task("fetch-nightly-translations", async function () {
// To store file writing promises
const createExtractDir = mkdir(EXTRACT_DIR, { recursive: true });
const writings = [];
const writings: Promise<void>[] = [];
// Authenticate to GitHub using GitHub action token if it exists,
// otherwise look for a saved user token or generate a new one if none
@@ -87,7 +87,7 @@ gulp.task("fetch-nightly-translations", async function () {
});
tokenAuth = await auth({ type: "oauth" });
writings.push(
createExtractDir.then(
createExtractDir.then(() =>
writeFile(TOKEN_FILE, JSON.stringify(tokenAuth, null, 2))
)
);
@@ -131,13 +131,13 @@ gulp.task("fetch-nightly-translations", async function () {
throw Error("Latest nightly workflow run has no translations artifact");
}
writings.push(
createExtractDir.then(
createExtractDir.then(() =>
writeFile(ARTIFACT_FILE, JSON.stringify(latestArtifact, null, 2))
)
);
// Remove the current translations
const deleteCurrent = Promise.all(writings).then(
const deleteCurrent = Promise.all(writings).then(() =>
deleteAsync([`${EXTRACT_DIR}/*`, `!${ARTIFACT_FILE}`, `!${TOKEN_FILE}`])
);
@@ -148,13 +148,14 @@ gulp.task("fetch-nightly-translations", async function () {
artifact_id: latestArtifact.id,
archive_format: "zip",
});
// @ts-ignore
if (downloadResponse.status !== 200) {
throw Error("Failure downloading translations artifact");
}
// Artifact is a tarball, but GitHub adds it to a zip file
console.log("Unpacking downloaded translations...");
const zip = await jszip.loadAsync(downloadResponse.data);
const zip = await jszip.loadAsync(downloadResponse.data as any);
await deleteCurrent;
const extractStream = zip.file(/.*/)[0].nodeStream().pipe(extract());
await new Promise((resolve, reject) => {

View File

@@ -4,14 +4,14 @@ import gulp from "gulp";
import yaml from "js-yaml";
import { marked } from "marked";
import path from "path";
import paths from "../paths.cjs";
import paths from "../paths";
import "./clean.js";
import "./entry-html.js";
import "./gather-static.js";
import "./gen-icons-json.js";
import "./rspack";
import "./service-worker.js";
import "./translations.js";
import "./rspack.js";
gulp.task("gather-gallery-pages", async function gatherPages() {
const pageDir = path.resolve(paths.gallery_dir, "src/pages");
@@ -22,7 +22,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
let content = "export const PAGES = {\n";
const processed = new Set();
const processed = new Set<string>();
for (const file of files) {
if (fs.lstatSync(file).isDirectory()) {
@@ -47,7 +47,9 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
if (descriptionContent.startsWith("---")) {
const metadataEnd = descriptionContent.indexOf("---", 3);
metadata = yaml.load(descriptionContent.substring(3, metadataEnd));
metadata = yaml.load(
descriptionContent.substring(3, metadataEnd)
) as any;
descriptionContent = descriptionContent
.substring(metadataEnd + 3)
.trim();
@@ -57,7 +59,9 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
if (descriptionContent === "") {
hasDescription = false;
} else {
descriptionContent = marked(descriptionContent).replace(/`/g, "\\`");
// eslint-disable-next-line no-await-in-loop
descriptionContent = await marked(descriptionContent);
descriptionContent = descriptionContent.replace(/`/g, "\\`");
fs.mkdirSync(path.resolve(galleryBuild, category), { recursive: true });
fs.writeFileSync(
path.resolve(galleryBuild, `${pageId}-description.ts`),
@@ -95,7 +99,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
pagesToProcess[category].add(page);
}
for (const group of Object.values(sidebar)) {
for (const group of Object.values(sidebar) as Array<{ category: string; pages?: string[] }>) {
const toProcess = pagesToProcess[group.category];
delete pagesToProcess[group.category];
@@ -118,7 +122,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
group.pages = [];
}
for (const page of Array.from(toProcess).sort()) {
group.pages.push(page);
group.pages.push(page as string);
}
}
@@ -126,7 +130,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
sidebar.push({
category,
header: category,
pages: Array.from(pages).sort(),
pages: Array.from(pages as Set<string>).sort(),
});
}

View File

@@ -3,7 +3,7 @@
import fs from "fs-extra";
import gulp from "gulp";
import path from "path";
import paths from "../paths.cjs";
import paths from "../paths";
const npmPath = (...parts) =>
path.resolve(paths.root_dir, "node_modules", ...parts);

View File

@@ -2,7 +2,7 @@ import fs from "fs";
import gulp from "gulp";
import hash from "object-hash";
import path from "path";
import paths from "../paths.cjs";
import paths from "../paths";
const ICON_PACKAGE_PATH = path.resolve("node_modules/@mdi/svg/");
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
@@ -21,7 +21,7 @@ const getMeta = () => {
encoding,
});
return {
path: svg.match(/ d="([^"]+)"/)[1],
path: svg.match(/ d="([^"]+)"/)?.[1],
name: icon.name,
tags: icon.tags,
aliases: icon.aliases,
@@ -55,14 +55,14 @@ const orderMeta = (meta) => {
};
const splitBySize = (meta) => {
const chunks = [];
const chunks: any[] = [];
const CHUNK_SIZE = 50000;
let curSize = 0;
let startKey;
let icons = [];
let icons: any[] = [];
Object.values(meta).forEach((icon) => {
Object.values(meta).forEach((icon: any) => {
if (startKey === undefined) {
startKey = icon.name;
}
@@ -94,7 +94,7 @@ const findDifferentiator = (curString, prevString) => {
return curString.substring(0, i + 1);
}
}
throw new Error("Cannot find differentiator", curString, prevString);
throw new Error(`Cannot find differentiator; ${curString}; ${prevString}`);
};
gulp.task("gen-icons-json", (done) => {
@@ -106,7 +106,7 @@ gulp.task("gen-icons-json", (done) => {
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
const parts = [];
const parts: any[] = [];
let lastEnd;
split.forEach((chunk) => {

View File

@@ -1,12 +1,12 @@
import gulp from "gulp";
import env from "../env.cjs";
import env from "../env";
import "./clean.js";
import "./compress.js";
import "./entry-html.js";
import "./gather-static.js";
import "./gen-icons-json.js";
import "./translations.js";
import "./rspack.js";
import "./translations.js";
gulp.task(
"develop-hassio",

View File

@@ -1,17 +0,0 @@
import "./app.js";
import "./cast.js";
import "./clean.js";
import "./compress.js";
import "./demo.js";
import "./download-translations.js";
import "./entry-html.js";
import "./fetch-nightly-translations.js";
import "./gallery.js";
import "./gather-static.js";
import "./gen-icons-json.js";
import "./hassio.js";
import "./landing-page.js";
import "./locale-data.js";
import "./rspack.js";
import "./service-worker.js";
import "./translations.js";

View File

@@ -0,0 +1,17 @@
import "./app.ts";
import "./cast.ts";
import "./clean.ts";
import "./compress.ts";
import "./demo.ts";
import "./download-translations.ts";
import "./entry-html.ts";
import "./fetch-nightly-translations.ts";
import "./gallery.ts";
import "./gather-static.ts";
import "./gen-icons-json.ts";
import "./hassio.ts";
import "./landing-page.ts";
import "./locale-data.ts";
import "./rspack.ts";
import "./service-worker.ts";
import "./translations.ts";

View File

@@ -4,8 +4,8 @@ import "./compress.js";
import "./entry-html.js";
import "./gather-static.js";
import "./gen-icons-json.js";
import "./translations.js";
import "./rspack.js";
import "./translations.js";
gulp.task(
"develop-landing-page",

View File

@@ -2,7 +2,7 @@ import { deleteSync } from "del";
import { mkdir, readFile, writeFile } from "fs/promises";
import gulp from "gulp";
import { join, resolve } from "node:path";
import paths from "../paths.cjs";
import paths from "../paths";
const formatjsDir = join(paths.root_dir, "node_modules", "@formatjs");
const outDir = join(paths.build_dir, "locale-data");
@@ -31,7 +31,7 @@ const convertToJSON = async (
join(formatjsDir, pkg, subDir, `${language}.js`),
"utf-8"
);
} catch (e) {
} catch (e: any) {
// Ignore if language is missing (i.e. not supported by @formatjs)
if (e.code === "ENOENT" && skipMissing) {
console.warn(`Skipped missing data for language ${lang} from ${pkg}`);
@@ -63,7 +63,7 @@ gulp.task("create-locale-data", async () => {
"utf-8"
)
);
const conversions = [];
const conversions: any[] = [];
for (const pkg of Object.keys(INTL_POLYFILLS)) {
// eslint-disable-next-line no-await-in-loop
await mkdir(join(outDir, pkg), { recursive: true });

View File

@@ -1,13 +1,13 @@
// Tasks to run rspack.
import fs from "fs";
import path from "path";
import log from "fancy-log";
import gulp from "gulp";
import rspack from "@rspack/core";
import { RspackDevServer } from "@rspack/dev-server";
import env from "../env.cjs";
import paths from "../paths.cjs";
import log from "fancy-log";
import fs from "fs";
import gulp from "gulp";
import path from "path";
import env from "../env";
import paths from "../paths";
import {
createAppConfig,
createCastConfig,
@@ -15,7 +15,7 @@ import {
createGalleryConfig,
createHassioConfig,
createLandingPageConfig,
} from "../rspack.cjs";
} from "../rspack";
const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: true }),
@@ -29,6 +29,14 @@ const isWsl =
.toLocaleLowerCase()
.includes("microsoft");
interface RunDevServer {
compiler: any;
contentBase: string;
port: number;
listenHost?: string;
proxy: any;
}
/**
* @param {{
* compiler: import("@rspack/core").Compiler,
@@ -43,7 +51,7 @@ const runDevServer = async ({
port,
listenHost = undefined,
proxy = undefined,
}) => {
}: RunDevServer) => {
if (listenHost === undefined) {
// For dev container, we need to listen on all hosts
listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost";
@@ -68,7 +76,7 @@ const runDevServer = async ({
log("[rspack-dev-server]", `Project is running at http://localhost:${port}`);
};
const doneHandler = (done) => (err, stats) => {
const doneHandler = (done?: (value?: unknown) => void) => (err, stats) => {
if (err) {
log.error(err.stack || err);
if (err.details) {

View File

@@ -5,7 +5,7 @@ import gulp from "gulp";
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
import { basename, join, relative } from "node:path";
import { injectManifest } from "workbox-build";
import paths from "../paths.cjs";
import paths from "../paths";
const SW_MAP = {
[paths.app_output_latest]: "modern",

View File

@@ -1,3 +1,4 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable max-classes-per-file */
import { deleteAsync } from "del";
@@ -10,9 +11,9 @@ import { mkdir, readFile } from "node:fs/promises";
import { basename, join } from "node:path";
import { PassThrough, Transform } from "node:stream";
import { finished } from "node:stream/promises";
import env from "../env.cjs";
import paths from "../paths.cjs";
import "./fetch-nightly-translations.js";
import env from "../env";
import paths from "../paths";
import "./fetch-nightly-translations.ts";
const inFrontendDir = "translations/frontend";
const inBackendDir = "translations/backend";
@@ -111,11 +112,12 @@ const testReviver = (_key, value) =>
const KEY_REFERENCE = /\[%key:([^%]+)%\]/;
const lokaliseTransform = (data, path, original = data) => {
const output = {};
for (const [key, value] of Object.entries(data)) {
for (const entry of Object.entries(data)) {
const [key, value] = entry as [string, string];
if (typeof value === "object") {
output[key] = lokaliseTransform(value, path, original);
} else {
output[key] = value.replace(KEY_REFERENCE, (_match, lokalise_key) => {
output[key] = value?.replace(KEY_REFERENCE, (_match, lokalise_key) => {
const replace = lokalise_key.split("::").reduce((tr, k) => {
if (!tr) {
throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`);
@@ -248,7 +250,7 @@ const createTranslations = async () => {
for (const translationFile of translationFiles) {
const locale = basename(translationFile, ".json");
const subtags = locale.split("-");
const mergeFiles = [];
const mergeFiles: string[] = [];
for (let i = 1; i <= subtags.length; i++) {
const lang = subtags.slice(0, i).join("-");
if (lang === TEST_LOCALE) {