Implement correct types

This commit is contained in:
Wendelin 2025-05-09 13:07:09 +02:00
parent ff3b65605e
commit 8f7760f88f
No known key found for this signature in database
31 changed files with 461 additions and 562 deletions

View File

@ -1,34 +1,33 @@
import path from "path"; import path from "node:path";
import { dependencies } from "../package.json"; import packageJson from "../package.json" assert { type: "json" };
import env from "./env"; import paths, { dirname } from "./paths.ts";
import paths from "./paths"; import { version } from "./env.ts";
const BABEL_PLUGINS = path.join(__dirname, "babel-plugins"); const dependencies = packageJson.dependencies;
const BABEL_PLUGINS = path.join(dirname, "babel-plugins");
// GitHub base URL to use for production source maps // GitHub base URL to use for production source maps
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version // Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
export const sourceMapURL = () => { export const sourceMapURL = () => {
const ref = env.version().endsWith("dev") const ref = version().endsWith("dev")
? process.env.GITHUB_SHA || "dev" ? process.env.GITHUB_SHA || "dev"
: env.version(); : version();
return `https://raw.githubusercontent.com/home-assistant/frontend/${ref}/`; return `https://raw.githubusercontent.com/home-assistant/frontend/${ref}/`;
}; };
// Files from NPM Packages that should not be imported
export const ignorePackages = () => [];
// Files from NPM packages that we should replace with empty file // Files from NPM packages that we should replace with empty file
export const emptyPackages = ({ isHassioBuild }) => export const emptyPackages = ({ isHassioBuild }) =>
[ [
require.resolve("@vaadin/vaadin-material-styles/typography.js"), import.meta.resolve("@vaadin/vaadin-material-styles/typography.js"),
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"), import.meta.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
// Icons in supervisor conflict with icons in HA so we don't load. // Icons in supervisor conflict with icons in HA so we don't load.
isHassioBuild && isHassioBuild &&
require.resolve( import.meta.resolve(
path.resolve(paths.root_dir, "src/components/ha-icon.ts") path.resolve(paths.root_dir, "src/components/ha-icon.ts")
), ),
isHassioBuild && isHassioBuild &&
require.resolve( import.meta.resolve(
path.resolve(paths.root_dir, "src/components/ha-icon-picker.ts") path.resolve(paths.root_dir, "src/components/ha-icon-picker.ts")
), ),
].filter(Boolean); ].filter(Boolean);
@ -36,7 +35,7 @@ export const emptyPackages = ({ isHassioBuild }) =>
export const definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ export const definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
__DEV__: !isProdBuild, __DEV__: !isProdBuild,
__BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"), __BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"),
__VERSION__: JSON.stringify(env.version()), __VERSION__: JSON.stringify(version()),
__DEMO__: false, __DEMO__: false,
__SUPERVISOR__: false, __SUPERVISOR__: false,
__BACKWARDS_COMPAT__: false, __BACKWARDS_COMPAT__: false,
@ -67,7 +66,7 @@ export const htmlMinifierOptions = {
export const terserOptions = ({ latestBuild, isTestBuild }) => ({ export const terserOptions = ({ latestBuild, isTestBuild }) => ({
safari10: !latestBuild, safari10: !latestBuild,
ecma: latestBuild ? 2015 : 5, ecma: latestBuild ? (2015 as const) : (5 as const),
module: latestBuild, module: latestBuild,
format: { comments: false }, format: { comments: false },
sourceMap: !isTestBuild, sourceMap: !isTestBuild,
@ -91,6 +90,11 @@ export const babelOptions = ({
isProdBuild, isProdBuild,
isTestBuild, isTestBuild,
sw, sw,
}: {
latestBuild?: boolean;
isProdBuild?: boolean;
isTestBuild?: boolean;
sw?: boolean;
}) => ({ }) => ({
babelrc: false, babelrc: false,
compact: false, compact: false,
@ -137,7 +141,7 @@ export const babelOptions = ({
"@polymer/polymer/lib/utils/html-tag.js": ["html"], "@polymer/polymer/lib/utils/html-tag.js": ["html"],
}, },
strictCSS: true, strictCSS: true,
htmlMinifier: module.exports.htmlMinifierOptions, htmlMinifier: htmlMinifierOptions,
failOnError: false, // we can turn this off in case of false positives failOnError: false, // we can turn this off in case of false positives
}, },
], ],
@ -222,7 +226,19 @@ const publicPath = (latestBuild, root = "") =>
*/ */
export const config = { export const config = {
app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) { app({
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
isWDS,
}: {
isProdBuild?: boolean;
latestBuild?: boolean;
isStatsBuild?: boolean;
isTestBuild?: boolean;
isWDS?: boolean;
}) {
return { return {
name: "frontend" + nameSuffix(latestBuild), name: "frontend" + nameSuffix(latestBuild),
entry: { entry: {
@ -257,7 +273,7 @@ export const config = {
outputPath: outputPath(paths.demo_output_root, latestBuild), outputPath: outputPath(paths.demo_output_root, latestBuild),
publicPath: publicPath(latestBuild), publicPath: publicPath(latestBuild),
defineOverlay: { defineOverlay: {
__VERSION__: JSON.stringify(`DEMO-${env.version()}`), __VERSION__: JSON.stringify(`DEMO-${version()}`),
__DEMO__: true, __DEMO__: true,
}, },
isProdBuild, isProdBuild,
@ -267,13 +283,12 @@ export const config = {
}, },
cast({ isProdBuild, latestBuild }) { cast({ isProdBuild, latestBuild }) {
const entry = { const entry: Record<string, string> = {
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"), launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
media: path.resolve(paths.cast_dir, "src/media/entrypoint.ts"), media: path.resolve(paths.cast_dir, "src/media/entrypoint.ts"),
}; };
if (latestBuild) { if (latestBuild) {
// @ts-ignore
entry.receiver = path.resolve( entry.receiver = path.resolve(
paths.cast_dir, paths.cast_dir,
"src/receiver/entrypoint.ts" "src/receiver/entrypoint.ts"

View File

@ -1,34 +1,21 @@
import fs from "fs"; import fs from "node:fs";
import path from "path"; import path from "node:path";
import paths from "./paths"; import paths from "./paths.ts";
const isTrue = (value) => value === "1" || value?.toLowerCase() === "true"; const isTrue = (value) => value === "1" || value?.toLowerCase() === "true";
export default { export const isProdBuild = () =>
isProdBuild() { process.env.NODE_ENV === "production" || isStatsBuild();
return ( export const isStatsBuild = () => isTrue(process.env.STATS);
process.env.NODE_ENV === "production" || module.exports.isStatsBuild() export const isTestBuild = () => isTrue(process.env.IS_TEST);
); export const isNetlify = () => isTrue(process.env.NETLIFY);
}, export const version = () => {
isStatsBuild() { const pyProjectVersion = fs
return isTrue(process.env.STATS); .readFileSync(path.resolve(paths.root_dir, "pyproject.toml"), "utf8")
}, .match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
isTestBuild() { if (!pyProjectVersion) {
return isTrue(process.env.IS_TEST); throw Error("Version not found");
}, }
isNetlify() { return pyProjectVersion[1];
return isTrue(process.env.NETLIFY);
},
version() {
const version = fs
.readFileSync(path.resolve(paths.root_dir, "pyproject.toml"), "utf8")
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
if (!version) {
throw Error("Version not found");
}
return version[1];
},
isDevContainer() {
return isTrue(process.env.DEV_CONTAINER);
},
}; };
export const isDevContainer = () => isTrue(process.env.DEV_CONTAINER);

View File

@ -1,8 +1,7 @@
// eslint-disable-next-line import/no-extraneous-dependencies import { parallel, series, task } from "gulp";
import gulp from "gulp"; import { isStatsBuild, isTestBuild } from "../env.ts";
import "./clean.ts"; import "./clean.ts";
import "./compress.ts"; import "./compress.ts";
import env from "../env";
import "./entry-html.ts"; import "./entry-html.ts";
import "./gather-static.ts"; import "./gather-static.ts";
import "./gen-icons-json.ts"; import "./gen-icons-json.ts";
@ -11,14 +10,14 @@ import "./rspack.ts";
import "./service-worker.ts"; import "./service-worker.ts";
import "./translations.ts"; import "./translations.ts";
gulp.task( task(
"develop-app", "develop-app",
gulp.series( series(
async () => { async () => {
process.env.NODE_ENV = "development"; process.env.NODE_ENV = "development";
}, },
"clean", "clean",
gulp.parallel( parallel(
"gen-service-worker-app-dev", "gen-service-worker-app-dev",
"gen-icons-json", "gen-icons-json",
"gen-pages-app-dev", "gen-pages-app-dev",
@ -30,25 +29,25 @@ gulp.task(
) )
); );
gulp.task( task(
"build-app", "build-app",
gulp.series( series(
async () => { async () => {
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
}, },
"clean", "clean",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-app", "copy-static-app",
"rspack-prod-app", "rspack-prod-app",
gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod"), parallel("gen-pages-app-prod", "gen-service-worker-app-prod"),
// Don't compress running tests // Don't compress running tests
...(env.isTestBuild() || env.isStatsBuild() ? [] : ["compress-app"]) ...(isTestBuild() || isStatsBuild() ? [] : ["compress-app"])
) )
); );
gulp.task( task(
"analyze-app", "analyze-app",
gulp.series( series(
async () => { async () => {
process.env.STATS = "1"; process.env.STATS = "1";
}, },

View File

@ -1,5 +1,4 @@
/* eslint-disable import/no-extraneous-dependencies */ import { parallel, series, task } from "gulp";
import gulp from "gulp";
import "./clean.ts"; import "./clean.ts";
import "./entry-html.ts"; import "./entry-html.ts";
import "./gather-static.ts"; import "./gather-static.ts";
@ -7,30 +6,30 @@ import "./rspack.ts";
import "./service-worker.ts"; import "./service-worker.ts";
import "./translations.ts"; import "./translations.ts";
gulp.task( task(
"develop-cast", "develop-cast",
gulp.series( series(
async () => { async () => {
process.env.NODE_ENV = "development"; process.env.NODE_ENV = "development";
}, },
"clean-cast", "clean-cast",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-cast", "copy-static-cast",
"gen-pages-cast-dev", "gen-pages-cast-dev",
"rspack-dev-server-cast" "rspack-dev-server-cast"
) )
); );
gulp.task( task(
"build-cast", "build-cast",
gulp.series( series(
async () => { async () => {
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
}, },
"clean-cast", "clean-cast",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-cast", "copy-static-cast",
"rspack-prod-cast", "rspack-prod-cast",
"gen-pages-cast-prod" "gen-pages-cast-prod"

View File

@ -1,36 +1,36 @@
import { deleteSync } from "del"; import { deleteSync } from "del";
import gulp from "gulp"; import { parallel, task } from "gulp";
import paths from "../paths"; import paths from "../paths.ts";
import "./translations.js"; import "./translations.ts";
gulp.task( task(
"clean", "clean",
gulp.parallel("clean-translations", async () => parallel("clean-translations", async () =>
deleteSync([paths.app_output_root, paths.build_dir]) deleteSync([paths.app_output_root, paths.build_dir])
) )
); );
gulp.task( task(
"clean-demo", "clean-demo",
gulp.parallel("clean-translations", async () => parallel("clean-translations", async () =>
deleteSync([paths.demo_output_root, paths.build_dir]) deleteSync([paths.demo_output_root, paths.build_dir])
) )
); );
gulp.task( task(
"clean-cast", "clean-cast",
gulp.parallel("clean-translations", async () => parallel("clean-translations", async () =>
deleteSync([paths.cast_output_root, paths.build_dir]) deleteSync([paths.cast_output_root, paths.build_dir])
) )
); );
gulp.task("clean-hassio", async () => task("clean-hassio", async () =>
deleteSync([paths.hassio_output_root, paths.build_dir]) deleteSync([paths.hassio_output_root, paths.build_dir])
); );
gulp.task( task(
"clean-gallery", "clean-gallery",
gulp.parallel("clean-translations", async () => parallel("clean-translations", async () =>
deleteSync([ deleteSync([
paths.gallery_output_root, paths.gallery_output_root,
paths.gallery_build, paths.gallery_build,
@ -39,9 +39,9 @@ gulp.task(
) )
); );
gulp.task( task(
"clean-landing-page", "clean-landing-page",
gulp.parallel("clean-translations", async () => parallel("clean-translations", async () =>
deleteSync([ deleteSync([
paths.landingPage_output_root, paths.landingPage_output_root,
paths.landingPage_build, paths.landingPage_build,

View File

@ -1,10 +1,10 @@
// Tasks to compress // Tasks to compress
import gulp from "gulp"; import { dest, parallel, src, task } from "gulp";
import brotli from "gulp-brotli"; import brotli from "gulp-brotli";
import zopfli from "gulp-zopfli-green"; import zopfli from "gulp-zopfli-green";
import { constants } from "node:zlib"; import { constants } from "node:zlib";
import paths from "../paths"; import paths from "../paths.ts";
const filesGlob = "*.{js,json,css,svg,xml}"; const filesGlob = "*.{js,json,css,svg,xml}";
const brotliOptions = { const brotliOptions = {
@ -16,27 +16,25 @@ const brotliOptions = {
const zopfliOptions = { threshold: 150 }; const zopfliOptions = { threshold: 150 };
const compressModern = (rootDir, modernDir, compress) => const compressModern = (rootDir, modernDir, compress) =>
gulp src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
.src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], { base: rootDir,
base: rootDir, allowEmpty: true,
allowEmpty: true, })
})
.pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions)) .pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions))
.pipe(gulp.dest(rootDir)); .pipe(dest(rootDir));
const compressOther = (rootDir, modernDir, compress) => const compressOther = (rootDir, modernDir, compress) =>
gulp src(
.src( [
[ `${rootDir}/**/${filesGlob}`,
`${rootDir}/**/${filesGlob}`, `!${modernDir}/**/${filesGlob}`,
`!${modernDir}/**/${filesGlob}`, `!${rootDir}/{sw-modern,service_worker}.js`,
`!${rootDir}/{sw-modern,service_worker}.js`, `${rootDir}/{authorize,onboarding}.html`,
`${rootDir}/{authorize,onboarding}.html`, ],
], { base: rootDir, allowEmpty: true }
{ base: rootDir, allowEmpty: true } )
)
.pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions)) .pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions))
.pipe(gulp.dest(rootDir)); .pipe(dest(rootDir));
const compressAppModernBrotli = () => const compressAppModernBrotli = () =>
compressModern(paths.app_output_root, paths.app_output_latest, "brotli"); compressModern(paths.app_output_root, paths.app_output_latest, "brotli");
@ -66,18 +64,18 @@ const compressHassioOtherBrotli = () =>
const compressHassioOtherZopfli = () => const compressHassioOtherZopfli = () =>
compressOther(paths.hassio_output_root, paths.hassio_output_latest, "zopfli"); compressOther(paths.hassio_output_root, paths.hassio_output_latest, "zopfli");
gulp.task( task(
"compress-app", "compress-app",
gulp.parallel( parallel(
compressAppModernBrotli, compressAppModernBrotli,
compressAppOtherBrotli, compressAppOtherBrotli,
compressAppModernZopfli, compressAppModernZopfli,
compressAppOtherZopfli compressAppOtherZopfli
) )
); );
gulp.task( task(
"compress-hassio", "compress-hassio",
gulp.parallel( parallel(
compressHassioModernBrotli, compressHassioModernBrotli,
compressHassioOtherBrotli, compressHassioOtherBrotli,
compressHassioModernZopfli, compressHassioModernZopfli,

View File

@ -1,21 +1,21 @@
import gulp from "gulp"; import { parallel, series, task } from "gulp";
import "./clean.js"; import "./clean.ts";
import "./entry-html.js"; import "./entry-html.ts";
import "./gather-static.js"; import "./gather-static.ts";
import "./gen-icons-json.js"; import "./gen-icons-json.ts";
import "./service-worker.js"; import "./rspack.ts";
import "./translations.js"; import "./service-worker.ts";
import "./rspack.js"; import "./translations.ts";
gulp.task( task(
"develop-demo", "develop-demo",
gulp.series( series(
async function setEnv() { async function setEnv() {
process.env.NODE_ENV = "development"; process.env.NODE_ENV = "development";
}, },
"clean-demo", "clean-demo",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel( parallel(
"gen-icons-json", "gen-icons-json",
"gen-pages-demo-dev", "gen-pages-demo-dev",
"build-translations", "build-translations",
@ -26,25 +26,25 @@ gulp.task(
) )
); );
gulp.task( task(
"build-demo", "build-demo",
gulp.series( series(
async function setEnv() { async function setEnv() {
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
}, },
"clean-demo", "clean-demo",
// Cast needs to be backwards compatible and older HA has no translations // Cast needs to be backwards compatible and older HA has no translations
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-demo", "copy-static-demo",
"rspack-prod-demo", "rspack-prod-demo",
"gen-pages-demo-prod" "gen-pages-demo-prod"
) )
); );
gulp.task( task(
"analyze-demo", "analyze-demo",
gulp.series( series(
async function setEnv() { async function setEnv() {
process.env.STATS = "1"; process.env.STATS = "1";
}, },

View File

@ -1,10 +1,10 @@
import { LokaliseApi } from "@lokalise/node-api"; import { LokaliseApi } from "@lokalise/node-api";
import fs from "fs/promises"; import fs from "node:fs/promises";
import gulp from "gulp"; import { dest, series, src, task } from "gulp";
import transform from "gulp-json-transform"; import transform from "gulp-json-transform";
import JSZip from "jszip"; import JSZip from "jszip";
import mapStream from "map-stream"; import mapStream from "map-stream";
import path from "path"; import path from "node:path";
const inDir = "translations"; const inDir = "translations";
const inDirFrontend = `${inDir}/frontend`; const inDirFrontend = `${inDir}/frontend`;
@ -64,20 +64,19 @@ function convertBackendTranslations(data, _file) {
return output; return output;
} }
gulp.task("convert-backend-translations", function () { task("convert-backend-translations", function () {
return gulp return src([`${inDirBackend}/*.json`])
.src([`${inDirBackend}/*.json`])
.pipe(transform((data, file) => convertBackendTranslations(data, file))) .pipe(transform((data, file) => convertBackendTranslations(data, file)))
.pipe(gulp.dest(inDirBackend)); .pipe(dest(inDirBackend));
}); });
gulp.task("check-translations-html", function () { task("check-translations-html", function () {
return gulp return src([`${inDirFrontend}/*.json`, `${inDirBackend}/*.json`]).pipe(
.src([`${inDirFrontend}/*.json`, `${inDirBackend}/*.json`]) checkHtml()
.pipe(checkHtml()); );
}); });
gulp.task("check-all-files-exist", async function () { task("check-all-files-exist", async function () {
const file = await fs.readFile(srcMeta, { encoding }); const file = await fs.readFile(srcMeta, { encoding });
const meta = JSON.parse(file); const meta = JSON.parse(file);
const writings: Promise<void>[] = []; const writings: Promise<void>[] = [];
@ -99,7 +98,7 @@ const lokaliseProjects = {
frontend: "3420425759f6d6d241f598.13594006", frontend: "3420425759f6d6d241f598.13594006",
}; };
gulp.task("fetch-lokalise", async function () { task("fetch-lokalise", async function () {
let apiKey; let apiKey;
try { try {
apiKey = apiKey =
@ -170,9 +169,9 @@ gulp.task("fetch-lokalise", async function () {
); );
}); });
gulp.task( task(
"download-translations", "download-translations",
gulp.series( series(
"fetch-lokalise", "fetch-lokalise",
"convert-backend-translations", "convert-backend-translations",
"check-translations-html", "check-translations-html",

View File

@ -6,12 +6,12 @@ import {
getPreUserAgentRegexes, getPreUserAgentRegexes,
} from "browserslist-useragent-regexp"; } from "browserslist-useragent-regexp";
import fs from "fs-extra"; import fs from "fs-extra";
import gulp from "gulp"; import { task } from "gulp";
import { minify } from "html-minifier-terser"; import { minify } from "html-minifier-terser";
import template from "lodash.template"; import template from "lodash.template";
import { dirname, extname, resolve } from "node:path"; import { dirname, extname, resolve } from "node:path";
import { htmlMinifierOptions, terserOptions } from "../bundle"; import { htmlMinifierOptions, terserOptions } from "../bundle.ts";
import paths from "../paths"; import paths from "../paths.ts";
// macOS companion app has no way to obtain the Safari version used by WKWebView, // 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 // and it is not in the default user agent string. So we add an additional regex
@ -34,9 +34,9 @@ const getCommonTemplateVars = () => {
mobileToDesktop: true, mobileToDesktop: true,
throwOnMissing: true, throwOnMissing: true,
}); });
const minSafariVersion = browserRegexes.find( const minSafariVersion =
(regex) => regex.family === "safari" browserRegexes.find((regex) => regex.family === "safari")
)?.matchedVersions[0][0] ?? 18; ?.matchedVersions[0][0] ?? 18;
const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion]; const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion];
if (!minMacOSVersion) { if (!minMacOSVersion) {
throw Error( throw Error(
@ -145,8 +145,12 @@ const genPagesProdTask =
resolve(inputRoot, inputSub, `${page}.template`), resolve(inputRoot, inputSub, `${page}.template`),
{ {
...commonVars, ...commonVars,
latestEntryJS: (entries as string[]).map((entry) => latestManifest[`${entry}.js`]), latestEntryJS: (entries as string[]).map(
es5EntryJS: (entries as string[]).map((entry) => es5Manifest[`${entry}.js`]), (entry) => latestManifest[`${entry}.js`]
),
es5EntryJS: (entries as string[]).map(
(entry) => es5Manifest[`${entry}.js`]
),
latestCustomPanelJS: latestManifest["custom-panel.js"], latestCustomPanelJS: latestManifest["custom-panel.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"], es5CustomPanelJS: es5Manifest["custom-panel.js"],
} }
@ -167,12 +171,12 @@ const APP_PAGE_ENTRIES = {
"index.html": ["core", "app"], "index.html": ["core", "app"],
}; };
gulp.task( task(
"gen-pages-app-dev", "gen-pages-app-dev",
genPagesDevTask(APP_PAGE_ENTRIES, paths.root_dir, paths.app_output_root) genPagesDevTask(APP_PAGE_ENTRIES, paths.root_dir, paths.app_output_root)
); );
gulp.task( task(
"gen-pages-app-prod", "gen-pages-app-prod",
genPagesProdTask( genPagesProdTask(
APP_PAGE_ENTRIES, APP_PAGE_ENTRIES,
@ -190,12 +194,12 @@ const CAST_PAGE_ENTRIES = {
"receiver.html": ["receiver"], "receiver.html": ["receiver"],
}; };
gulp.task( task(
"gen-pages-cast-dev", "gen-pages-cast-dev",
genPagesDevTask(CAST_PAGE_ENTRIES, paths.cast_dir, paths.cast_output_root) genPagesDevTask(CAST_PAGE_ENTRIES, paths.cast_dir, paths.cast_output_root)
); );
gulp.task( task(
"gen-pages-cast-prod", "gen-pages-cast-prod",
genPagesProdTask( genPagesProdTask(
CAST_PAGE_ENTRIES, CAST_PAGE_ENTRIES,
@ -208,12 +212,12 @@ gulp.task(
const DEMO_PAGE_ENTRIES = { "index.html": ["main"] }; const DEMO_PAGE_ENTRIES = { "index.html": ["main"] };
gulp.task( task(
"gen-pages-demo-dev", "gen-pages-demo-dev",
genPagesDevTask(DEMO_PAGE_ENTRIES, paths.demo_dir, paths.demo_output_root) genPagesDevTask(DEMO_PAGE_ENTRIES, paths.demo_dir, paths.demo_output_root)
); );
gulp.task( task(
"gen-pages-demo-prod", "gen-pages-demo-prod",
genPagesProdTask( genPagesProdTask(
DEMO_PAGE_ENTRIES, DEMO_PAGE_ENTRIES,
@ -226,7 +230,7 @@ gulp.task(
const GALLERY_PAGE_ENTRIES = { "index.html": ["entrypoint"] }; const GALLERY_PAGE_ENTRIES = { "index.html": ["entrypoint"] };
gulp.task( task(
"gen-pages-gallery-dev", "gen-pages-gallery-dev",
genPagesDevTask( genPagesDevTask(
GALLERY_PAGE_ENTRIES, GALLERY_PAGE_ENTRIES,
@ -235,7 +239,7 @@ gulp.task(
) )
); );
gulp.task( task(
"gen-pages-gallery-prod", "gen-pages-gallery-prod",
genPagesProdTask( genPagesProdTask(
GALLERY_PAGE_ENTRIES, GALLERY_PAGE_ENTRIES,
@ -247,7 +251,7 @@ gulp.task(
const LANDING_PAGE_PAGE_ENTRIES = { "index.html": ["entrypoint"] }; const LANDING_PAGE_PAGE_ENTRIES = { "index.html": ["entrypoint"] };
gulp.task( task(
"gen-pages-landing-page-dev", "gen-pages-landing-page-dev",
genPagesDevTask( genPagesDevTask(
LANDING_PAGE_PAGE_ENTRIES, LANDING_PAGE_PAGE_ENTRIES,
@ -256,7 +260,7 @@ gulp.task(
) )
); );
gulp.task( task(
"gen-pages-landing-page-prod", "gen-pages-landing-page-prod",
genPagesProdTask( genPagesProdTask(
LANDING_PAGE_PAGE_ENTRIES, LANDING_PAGE_PAGE_ENTRIES,
@ -269,7 +273,7 @@ gulp.task(
const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] }; const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] };
gulp.task( task(
"gen-pages-hassio-dev", "gen-pages-hassio-dev",
genPagesDevTask( genPagesDevTask(
HASSIO_PAGE_ENTRIES, HASSIO_PAGE_ENTRIES,
@ -280,7 +284,7 @@ gulp.task(
) )
); );
gulp.task( task(
"gen-pages-hassio-prod", "gen-pages-hassio-prod",
genPagesProdTask( genPagesProdTask(
HASSIO_PAGE_ENTRIES, HASSIO_PAGE_ENTRIES,

View File

@ -1,14 +1,14 @@
// Task to download the latest Lokalise translations from the nightly workflow artifacts // Task to download the latest 00Lokalise translations from the nightly workflow artifacts
import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device"; import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device";
import { retry } from "@octokit/plugin-retry"; import { retry } from "@octokit/plugin-retry";
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import { deleteAsync } from "del"; import { deleteAsync } from "del";
import { mkdir, readFile, writeFile } from "fs/promises"; import { mkdir, readFile, writeFile } from "node:fs/promises";
import gulp from "gulp"; import { series, task } from "gulp";
import jszip from "jszip"; import jszip from "jszip";
import path from "path"; import path from "node:path";
import process from "process"; import process from "node:process";
import { extract } from "tar"; import { extract } from "tar";
const MAX_AGE = 24; // hours const MAX_AGE = 24; // hours
@ -22,12 +22,12 @@ const TOKEN_FILE = path.posix.join(EXTRACT_DIR, "token.json");
const ARTIFACT_FILE = path.posix.join(EXTRACT_DIR, "artifact.json"); const ARTIFACT_FILE = path.posix.join(EXTRACT_DIR, "artifact.json");
let allowTokenSetup = false; let allowTokenSetup = false;
gulp.task("allow-setup-fetch-nightly-translations", (done) => { task("allow-setup-fetch-nightly-translations", (done) => {
allowTokenSetup = true; allowTokenSetup = true;
done(); done();
}); });
gulp.task("fetch-nightly-translations", async function () { task("fetch-nightly-translations", async function () {
// Skip all when environment flag is set (assumes translations are already in place) // Skip all when environment flag is set (assumes translations are already in place)
if (process.env?.SKIP_FETCH_NIGHTLY_TRANSLATIONS) { if (process.env?.SKIP_FETCH_NIGHTLY_TRANSLATIONS) {
console.log("Skipping fetch due to environment signal"); console.log("Skipping fetch due to environment signal");
@ -148,7 +148,7 @@ gulp.task("fetch-nightly-translations", async function () {
artifact_id: latestArtifact.id, artifact_id: latestArtifact.id,
archive_format: "zip", archive_format: "zip",
}); });
// @ts-ignore // @ts-ignore OctokitResponse<unknown, 302> doesn't allow to check for 200
if (downloadResponse.status !== 200) { if (downloadResponse.status !== 200) {
throw Error("Failure downloading translations artifact"); throw Error("Failure downloading translations artifact");
} }
@ -163,10 +163,7 @@ gulp.task("fetch-nightly-translations", async function () {
}); });
}); });
gulp.task( task(
"setup-and-fetch-nightly-translations", "setup-and-fetch-nightly-translations",
gulp.series( series("allow-setup-fetch-nightly-translations", "fetch-nightly-translations")
"allow-setup-fetch-nightly-translations",
"fetch-nightly-translations"
)
); );

View File

@ -1,19 +1,19 @@
import fs from "fs"; import fs from "node:fs";
import { glob } from "glob"; import { glob } from "glob";
import gulp from "gulp"; import { parallel, series, task, watch } from "gulp";
import yaml from "js-yaml"; import yaml from "js-yaml";
import { marked } from "marked"; import { marked } from "marked";
import path from "path"; import path from "node:path";
import paths from "../paths"; import paths from "../paths.ts";
import "./clean.js"; import "./clean.ts";
import "./entry-html.js"; import "./entry-html.ts";
import "./gather-static.js"; import "./gather-static.ts";
import "./gen-icons-json.js"; import "./gen-icons-json.ts";
import "./rspack"; import "./rspack.ts";
import "./service-worker.js"; import "./service-worker.ts";
import "./translations.js"; import "./translations.ts";
gulp.task("gather-gallery-pages", async function gatherPages() { task("gather-gallery-pages", async function gatherPages() {
const pageDir = path.resolve(paths.gallery_dir, "src/pages"); const pageDir = path.resolve(paths.gallery_dir, "src/pages");
const files = await glob(path.resolve(pageDir, "**/*")); const files = await glob(path.resolve(pageDir, "**/*"));
@ -99,7 +99,10 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
pagesToProcess[category].add(page); pagesToProcess[category].add(page);
} }
for (const group of Object.values(sidebar) as Array<{ category: string; pages?: string[] }>) { for (const group of Object.values(sidebar) as {
category: string;
pages?: string[];
}[]) {
const toProcess = pagesToProcess[group.category]; const toProcess = pagesToProcess[group.category];
delete pagesToProcess[group.category]; delete pagesToProcess[group.category];
@ -143,15 +146,15 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
); );
}); });
gulp.task( task(
"develop-gallery", "develop-gallery",
gulp.series( series(
async function setEnv() { async function setEnv() {
process.env.NODE_ENV = "development"; process.env.NODE_ENV = "development";
}, },
"clean-gallery", "clean-gallery",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel( parallel(
"gen-icons-json", "gen-icons-json",
"build-translations", "build-translations",
"build-locale-data", "build-locale-data",
@ -159,30 +162,27 @@ gulp.task(
), ),
"copy-static-gallery", "copy-static-gallery",
"gen-pages-gallery-dev", "gen-pages-gallery-dev",
gulp.parallel( parallel("rspack-dev-server-gallery", async function watchMarkdownFiles() {
"rspack-dev-server-gallery", watch(
async function watchMarkdownFiles() { [
gulp.watch( path.resolve(paths.gallery_dir, "src/pages/**/*.markdown"),
[ path.resolve(paths.gallery_dir, "sidebar.js"),
path.resolve(paths.gallery_dir, "src/pages/**/*.markdown"), ],
path.resolve(paths.gallery_dir, "sidebar.js"), series("gather-gallery-pages")
], );
gulp.series("gather-gallery-pages") })
);
}
)
) )
); );
gulp.task( task(
"build-gallery", "build-gallery",
gulp.series( series(
async function setEnv() { async function setEnv() {
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
}, },
"clean-gallery", "clean-gallery",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel( parallel(
"gen-icons-json", "gen-icons-json",
"build-translations", "build-translations",
"build-locale-data", "build-locale-data",

View File

@ -1,9 +1,9 @@
// Gulp task to gather all static files. // Gulp task to gather all static files.
import fs from "fs-extra"; import fs from "fs-extra";
import gulp from "gulp"; import { task } from "gulp";
import path from "path"; import path from "node:path";
import paths from "../paths"; import paths from "../paths.ts";
const npmPath = (...parts) => const npmPath = (...parts) =>
path.resolve(paths.root_dir, "node_modules", ...parts); path.resolve(paths.root_dir, "node_modules", ...parts);
@ -113,33 +113,33 @@ function copyZXingWasm(staticDir) {
); );
} }
gulp.task("copy-locale-data", async () => { task("copy-locale-data", async () => {
const staticDir = paths.app_output_static; const staticDir = paths.app_output_static;
copyLocaleData(staticDir); copyLocaleData(staticDir);
}); });
gulp.task("copy-translations-app", async () => { task("copy-translations-app", async () => {
const staticDir = paths.app_output_static; const staticDir = paths.app_output_static;
copyTranslations(staticDir); copyTranslations(staticDir);
}); });
gulp.task("copy-translations-supervisor", async () => { task("copy-translations-supervisor", async () => {
const staticDir = paths.hassio_output_static; const staticDir = paths.hassio_output_static;
copyTranslations(staticDir); copyTranslations(staticDir);
}); });
gulp.task("copy-translations-landing-page", async () => { task("copy-translations-landing-page", async () => {
const staticDir = paths.landingPage_output_static; const staticDir = paths.landingPage_output_static;
copyTranslations(staticDir); copyTranslations(staticDir);
}); });
gulp.task("copy-static-supervisor", async () => { task("copy-static-supervisor", async () => {
const staticDir = paths.hassio_output_static; const staticDir = paths.hassio_output_static;
copyLocaleData(staticDir); copyLocaleData(staticDir);
copyFonts(staticDir); copyFonts(staticDir);
}); });
gulp.task("copy-static-app", async () => { task("copy-static-app", async () => {
const staticDir = paths.app_output_static; const staticDir = paths.app_output_static;
// Basic static files // Basic static files
fs.copySync(polyPath("public"), paths.app_output_root); fs.copySync(polyPath("public"), paths.app_output_root);
@ -157,7 +157,7 @@ gulp.task("copy-static-app", async () => {
copyQrScannerWorker(staticDir); copyQrScannerWorker(staticDir);
}); });
gulp.task("copy-static-demo", async () => { task("copy-static-demo", async () => {
// Copy app static files // Copy app static files
fs.copySync( fs.copySync(
polyPath("public/static"), polyPath("public/static"),
@ -173,7 +173,7 @@ gulp.task("copy-static-demo", async () => {
copyMdiIcons(paths.demo_output_static); copyMdiIcons(paths.demo_output_static);
}); });
gulp.task("copy-static-cast", async () => { task("copy-static-cast", async () => {
// Copy app static files // Copy app static files
fs.copySync(polyPath("public/static"), paths.cast_output_static); fs.copySync(polyPath("public/static"), paths.cast_output_static);
// Copy cast static files // Copy cast static files
@ -186,7 +186,7 @@ gulp.task("copy-static-cast", async () => {
copyMdiIcons(paths.cast_output_static); copyMdiIcons(paths.cast_output_static);
}); });
gulp.task("copy-static-gallery", async () => { task("copy-static-gallery", async () => {
// Copy app static files // Copy app static files
fs.copySync(polyPath("public/static"), paths.gallery_output_static); fs.copySync(polyPath("public/static"), paths.gallery_output_static);
// Copy gallery static files // Copy gallery static files
@ -202,7 +202,7 @@ gulp.task("copy-static-gallery", async () => {
copyMdiIcons(paths.gallery_output_static); copyMdiIcons(paths.gallery_output_static);
}); });
gulp.task("copy-static-landing-page", async () => { task("copy-static-landing-page", async () => {
// Copy landing-page static files // Copy landing-page static files
fs.copySync( fs.copySync(
path.resolve(paths.landingPage_dir, "public"), path.resolve(paths.landingPage_dir, "public"),

View File

@ -1,8 +1,8 @@
import fs from "fs"; import fs from "node:fs";
import gulp from "gulp"; import { task } from "gulp";
import hash from "object-hash"; import hash from "object-hash";
import path from "path"; import path from "node:path";
import paths from "../paths"; import paths from "../paths.ts";
const ICON_PACKAGE_PATH = path.resolve("node_modules/@mdi/svg/"); const ICON_PACKAGE_PATH = path.resolve("node_modules/@mdi/svg/");
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json"); const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
@ -97,7 +97,7 @@ const findDifferentiator = (curString, prevString) => {
throw new Error(`Cannot find differentiator; ${curString}; ${prevString}`); throw new Error(`Cannot find differentiator; ${curString}; ${prevString}`);
}; };
gulp.task("gen-icons-json", (done) => { task("gen-icons-json", (done) => {
const meta = getMeta(); const meta = getMeta();
const metaAndRemoved = addRemovedMeta(meta); const metaAndRemoved = addRemovedMeta(meta);
@ -155,7 +155,7 @@ gulp.task("gen-icons-json", (done) => {
done(); done();
}); });
gulp.task("gen-dummy-icons-json", (done) => { task("gen-dummy-icons-json", (done) => {
if (!fs.existsSync(OUTPUT_DIR)) { if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true }); fs.mkdirSync(OUTPUT_DIR, { recursive: true });
} }

View File

@ -1,16 +1,16 @@
import gulp from "gulp"; import { series, task } from "gulp";
import env from "../env"; import { isTestBuild } from "../env.ts";
import "./clean.js"; import "./clean.ts";
import "./compress.js"; import "./compress.ts";
import "./entry-html.js"; import "./entry-html.ts";
import "./gather-static.js"; import "./gather-static.ts";
import "./gen-icons-json.js"; import "./gen-icons-json.ts";
import "./rspack.js"; import "./rspack.ts";
import "./translations.js"; import "./translations.ts";
gulp.task( task(
"develop-hassio", "develop-hassio",
gulp.series( series(
async function setEnv() { async function setEnv() {
process.env.NODE_ENV = "development"; process.env.NODE_ENV = "development";
}, },
@ -25,9 +25,9 @@ gulp.task(
) )
); );
gulp.task( task(
"build-hassio", "build-hassio",
gulp.series( series(
async function setEnv() { async function setEnv() {
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
}, },
@ -40,6 +40,6 @@ gulp.task(
"rspack-prod-hassio", "rspack-prod-hassio",
"gen-pages-hassio-prod", "gen-pages-hassio-prod",
...// Don't compress running tests ...// Don't compress running tests
(env.isTestBuild() ? [] : ["compress-hassio"]) (isTestBuild() ? [] : ["compress-hassio"])
) )
); );

View File

@ -1,15 +1,15 @@
import gulp from "gulp"; import { series, task } from "gulp";
import "./clean.js"; import "./clean.ts";
import "./compress.js"; import "./compress.ts";
import "./entry-html.js"; import "./entry-html.ts";
import "./gather-static.js"; import "./gather-static.ts";
import "./gen-icons-json.js"; import "./gen-icons-json.ts";
import "./rspack.js"; import "./rspack.ts";
import "./translations.js"; import "./translations.ts";
gulp.task( task(
"develop-landing-page", "develop-landing-page",
gulp.series( series(
async function setEnv() { async function setEnv() {
process.env.NODE_ENV = "development"; process.env.NODE_ENV = "development";
}, },
@ -24,9 +24,9 @@ gulp.task(
) )
); );
gulp.task( task(
"build-landing-page", "build-landing-page",
gulp.series( series(
async function setEnv() { async function setEnv() {
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
}, },

View File

@ -1,8 +1,8 @@
import { deleteSync } from "del"; import { deleteSync } from "del";
import { mkdir, readFile, writeFile } from "fs/promises"; import { mkdir, readFile, writeFile } from "node:fs/promises";
import gulp from "gulp"; import { series, task } from "gulp";
import { join, resolve } from "node:path"; import { join, resolve } from "node:path";
import paths from "../paths"; import paths from "../paths.ts";
const formatjsDir = join(paths.root_dir, "node_modules", "@formatjs"); const formatjsDir = join(paths.root_dir, "node_modules", "@formatjs");
const outDir = join(paths.build_dir, "locale-data"); const outDir = join(paths.build_dir, "locale-data");
@ -54,9 +54,9 @@ const convertToJSON = async (
await writeFile(join(outDir, `${pkg}/${lang}.json`), localeData); await writeFile(join(outDir, `${pkg}/${lang}.json`), localeData);
}; };
gulp.task("clean-locale-data", async () => deleteSync([outDir])); task("clean-locale-data", async () => deleteSync([outDir]));
gulp.task("create-locale-data", async () => { task("create-locale-data", async () => {
const translationMeta = JSON.parse( const translationMeta = JSON.parse(
await readFile( await readFile(
resolve(paths.translations_src, "translationMetadata.json"), resolve(paths.translations_src, "translationMetadata.json"),
@ -83,7 +83,4 @@ gulp.task("create-locale-data", async () => {
await Promise.all(conversions); await Promise.all(conversions);
}); });
gulp.task( task("build-locale-data", series("clean-locale-data", "create-locale-data"));
"build-locale-data",
gulp.series("clean-locale-data", "create-locale-data")
);

View File

@ -3,11 +3,10 @@
import rspack from "@rspack/core"; import rspack from "@rspack/core";
import { RspackDevServer } from "@rspack/dev-server"; import { RspackDevServer } from "@rspack/dev-server";
import log from "fancy-log"; import log from "fancy-log";
import fs from "fs"; import fs from "node:fs";
import gulp from "gulp"; import { task, watch, series } from "gulp";
import path from "path"; import path from "node:path";
import env from "../env"; import paths from "../paths.ts";
import paths from "../paths";
import { import {
createAppConfig, createAppConfig,
createCastConfig, createCastConfig,
@ -15,7 +14,8 @@ import {
createGalleryConfig, createGalleryConfig,
createHassioConfig, createHassioConfig,
createLandingPageConfig, createLandingPageConfig,
} from "../rspack"; } from "../rspack.ts";
import { isDevContainer, isStatsBuild, isTestBuild } from "../env.ts";
const bothBuilds = (createConfigFunc, params) => [ const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: true }), createConfigFunc({ ...params, latestBuild: true }),
@ -34,7 +34,7 @@ interface RunDevServer {
contentBase: string; contentBase: string;
port: number; port: number;
listenHost?: string; listenHost?: string;
proxy: any; proxy?: any;
} }
/** /**
@ -49,12 +49,12 @@ const runDevServer = async ({
compiler, compiler,
contentBase, contentBase,
port, port,
listenHost = undefined, listenHost,
proxy = undefined, proxy,
}: RunDevServer) => { }: RunDevServer) => {
if (listenHost === undefined) { if (listenHost === undefined) {
// For dev container, we need to listen on all hosts // For dev container, we need to listen on all hosts
listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost"; listenHost = isDevContainer() ? "0.0.0.0" : "localhost";
} }
const server = new RspackDevServer( const server = new RspackDevServer(
{ {
@ -105,30 +105,30 @@ const prodBuild = (conf) =>
); );
}); });
gulp.task("rspack-watch-app", () => { task("rspack-watch-app", () => {
// This command will run forever because we don't close compiler // This command will run forever because we don't close compiler
rspack( rspack(
process.env.ES5 process.env.ES5
? bothBuilds(createAppConfig, { isProdBuild: false }) ? bothBuilds(createAppConfig, { isProdBuild: false })
: createAppConfig({ isProdBuild: false, latestBuild: true }) : createAppConfig({ isProdBuild: false, latestBuild: true })
).watch({ poll: isWsl }, doneHandler()); ).watch({ poll: isWsl }, doneHandler());
gulp.watch( watch(
path.join(paths.translations_src, "en.json"), path.join(paths.translations_src, "en.json"),
gulp.series("build-translations", "copy-translations-app") series("build-translations", "copy-translations-app")
); );
}); });
gulp.task("rspack-prod-app", () => task("rspack-prod-app", () =>
prodBuild( prodBuild(
bothBuilds(createAppConfig, { bothBuilds(createAppConfig, {
isProdBuild: true, isProdBuild: true,
isStatsBuild: env.isStatsBuild(), isStatsBuild: isStatsBuild(),
isTestBuild: env.isTestBuild(), isTestBuild: isTestBuild(),
}) })
) )
); );
gulp.task("rspack-dev-server-demo", () => task("rspack-dev-server-demo", () =>
runDevServer({ runDevServer({
compiler: rspack( compiler: rspack(
createDemoConfig({ isProdBuild: false, latestBuild: true }) createDemoConfig({ isProdBuild: false, latestBuild: true })
@ -138,16 +138,16 @@ gulp.task("rspack-dev-server-demo", () =>
}) })
); );
gulp.task("rspack-prod-demo", () => task("rspack-prod-demo", () =>
prodBuild( prodBuild(
bothBuilds(createDemoConfig, { bothBuilds(createDemoConfig, {
isProdBuild: true, isProdBuild: true,
isStatsBuild: env.isStatsBuild(), isStatsBuild: isStatsBuild(),
}) })
) )
); );
gulp.task("rspack-dev-server-cast", () => task("rspack-dev-server-cast", () =>
runDevServer({ runDevServer({
compiler: rspack( compiler: rspack(
createCastConfig({ isProdBuild: false, latestBuild: true }) createCastConfig({ isProdBuild: false, latestBuild: true })
@ -159,7 +159,7 @@ gulp.task("rspack-dev-server-cast", () =>
}) })
); );
gulp.task("rspack-prod-cast", () => task("rspack-prod-cast", () =>
prodBuild( prodBuild(
bothBuilds(createCastConfig, { bothBuilds(createCastConfig, {
isProdBuild: true, isProdBuild: true,
@ -167,7 +167,7 @@ gulp.task("rspack-prod-cast", () =>
) )
); );
gulp.task("rspack-watch-hassio", () => { task("rspack-watch-hassio", () => {
// This command will run forever because we don't close compiler // This command will run forever because we don't close compiler
rspack( rspack(
createHassioConfig({ createHassioConfig({
@ -176,23 +176,23 @@ gulp.task("rspack-watch-hassio", () => {
}) })
).watch({ ignored: /build/, poll: isWsl }, doneHandler()); ).watch({ ignored: /build/, poll: isWsl }, doneHandler());
gulp.watch( watch(
path.join(paths.translations_src, "en.json"), path.join(paths.translations_src, "en.json"),
gulp.series("build-supervisor-translations", "copy-translations-supervisor") series("build-supervisor-translations", "copy-translations-supervisor")
); );
}); });
gulp.task("rspack-prod-hassio", () => task("rspack-prod-hassio", () =>
prodBuild( prodBuild(
bothBuilds(createHassioConfig, { bothBuilds(createHassioConfig, {
isProdBuild: true, isProdBuild: true,
isStatsBuild: env.isStatsBuild(), isStatsBuild: isStatsBuild(),
isTestBuild: env.isTestBuild(), isTestBuild: isTestBuild(),
}) })
) )
); );
gulp.task("rspack-dev-server-gallery", () => task("rspack-dev-server-gallery", () =>
runDevServer({ runDevServer({
compiler: rspack( compiler: rspack(
createGalleryConfig({ isProdBuild: false, latestBuild: true }) createGalleryConfig({ isProdBuild: false, latestBuild: true })
@ -203,7 +203,7 @@ gulp.task("rspack-dev-server-gallery", () =>
}) })
); );
gulp.task("rspack-prod-gallery", () => task("rspack-prod-gallery", () =>
prodBuild( prodBuild(
createGalleryConfig({ createGalleryConfig({
isProdBuild: true, isProdBuild: true,
@ -212,7 +212,7 @@ gulp.task("rspack-prod-gallery", () =>
) )
); );
gulp.task("rspack-watch-landing-page", () => { task("rspack-watch-landing-page", () => {
// This command will run forever because we don't close compiler // This command will run forever because we don't close compiler
rspack( rspack(
process.env.ES5 process.env.ES5
@ -220,21 +220,18 @@ gulp.task("rspack-watch-landing-page", () => {
: createLandingPageConfig({ isProdBuild: false, latestBuild: true }) : createLandingPageConfig({ isProdBuild: false, latestBuild: true })
).watch({ poll: isWsl }, doneHandler()); ).watch({ poll: isWsl }, doneHandler());
gulp.watch( watch(
path.join(paths.translations_src, "en.json"), path.join(paths.translations_src, "en.json"),
gulp.series( series("build-landing-page-translations", "copy-translations-landing-page")
"build-landing-page-translations",
"copy-translations-landing-page"
)
); );
}); });
gulp.task("rspack-prod-landing-page", () => task("rspack-prod-landing-page", () =>
prodBuild( prodBuild(
bothBuilds(createLandingPageConfig, { bothBuilds(createLandingPageConfig, {
isProdBuild: true, isProdBuild: true,
isStatsBuild: env.isStatsBuild(), isStatsBuild: isStatsBuild(),
isTestBuild: env.isTestBuild(), isTestBuild: isTestBuild(),
}) })
) )
); );

View File

@ -1,11 +1,11 @@
// Generate service workers // Generate service workers
import { deleteAsync } from "del"; import { deleteAsync } from "del";
import gulp from "gulp"; import { task } from "gulp";
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises"; import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
import { basename, join, relative } from "node:path"; import { basename, join, relative } from "node:path";
import { injectManifest } from "workbox-build"; import { injectManifest } from "workbox-build";
import paths from "../paths"; import paths from "../paths.ts";
const SW_MAP = { const SW_MAP = {
[paths.app_output_latest]: "modern", [paths.app_output_latest]: "modern",
@ -23,7 +23,7 @@ self.addEventListener('install', (event) => {
}); });
`.trim() + "\n"; `.trim() + "\n";
gulp.task("gen-service-worker-app-dev", async () => { task("gen-service-worker-app-dev", async () => {
await mkdir(paths.app_output_root, { recursive: true }); await mkdir(paths.app_output_root, { recursive: true });
await Promise.all( await Promise.all(
Object.values(SW_MAP).map((build) => Object.values(SW_MAP).map((build) =>
@ -34,7 +34,7 @@ gulp.task("gen-service-worker-app-dev", async () => {
); );
}); });
gulp.task("gen-service-worker-app-prod", () => task("gen-service-worker-app-prod", () =>
Promise.all( Promise.all(
Object.entries(SW_MAP).map(async ([outPath, build]) => { Object.entries(SW_MAP).map(async ([outPath, build]) => {
const manifest = JSON.parse( const manifest = JSON.parse(

View File

@ -1,9 +1,8 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable max-classes-per-file */ /* eslint-disable max-classes-per-file */
import { deleteAsync } from "del"; import { deleteAsync } from "del";
import { glob } from "glob"; import { glob } from "glob";
import gulp from "gulp"; import { src as glupSrc, dest as gulpDest, parallel, series, task } from "gulp";
import rename from "gulp-rename"; import rename from "gulp-rename";
import merge from "lodash.merge"; import merge from "lodash.merge";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
@ -11,8 +10,8 @@ import { mkdir, readFile } from "node:fs/promises";
import { basename, join } from "node:path"; import { basename, join } from "node:path";
import { PassThrough, Transform } from "node:stream"; import { PassThrough, Transform } from "node:stream";
import { finished } from "node:stream/promises"; import { finished } from "node:stream/promises";
import env from "../env"; import { isProdBuild } from "../env.ts";
import paths from "../paths"; import paths from "../paths.ts";
import "./fetch-nightly-translations.ts"; import "./fetch-nightly-translations.ts";
const inFrontendDir = "translations/frontend"; const inFrontendDir = "translations/frontend";
@ -24,9 +23,9 @@ const TEST_LOCALE = "en-x-test";
let mergeBackend = false; let mergeBackend = false;
gulp.task( task(
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel(async () => { parallel(async () => {
mergeBackend = true; mergeBackend = true;
}, "allow-setup-fetch-nightly-translations") }, "allow-setup-fetch-nightly-translations")
); );
@ -35,7 +34,11 @@ gulp.task(
// The provided function can either return a new object, or an array of // The provided function can either return a new object, or an array of
// [object, subdirectory] pairs for fragmentizing the JSON. // [object, subdirectory] pairs for fragmentizing the JSON.
class CustomJSON extends Transform { class CustomJSON extends Transform {
constructor(func, reviver = null) { _func: any;
_reviver: any;
constructor(func, reviver: any = null) {
super({ objectMode: true }); super({ objectMode: true });
this._func = func; this._func = func;
this._reviver = reviver; this._reviver = reviver;
@ -57,9 +60,17 @@ class CustomJSON extends Transform {
// Transform stream to merge Vinyl JSON files (buffer mode only). // Transform stream to merge Vinyl JSON files (buffer mode only).
class MergeJSON extends Transform { class MergeJSON extends Transform {
_objects = []; _objects: any[] = [];
constructor(stem, startObj = {}, reviver = null) { _stem: any;
_startObj: any;
_reviver: any;
_outFile: any;
constructor(stem, startObj = {}, reviver: any = null) {
super({ objectMode: true, allowHalfOpen: false }); super({ objectMode: true, allowHalfOpen: false });
this._stem = stem; this._stem = stem;
this._startObj = structuredClone(startObj); this._startObj = structuredClone(startObj);
@ -134,18 +145,17 @@ const lokaliseTransform = (data, path, original = data) => {
return output; return output;
}; };
gulp.task("clean-translations", () => deleteAsync([workDir])); task("clean-translations", () => deleteAsync([workDir]));
const makeWorkDir = () => mkdir(workDir, { recursive: true }); const makeWorkDir = () => mkdir(workDir, { recursive: true });
const createTestTranslation = () => const createTestTranslation = () =>
env.isProdBuild() isProdBuild()
? Promise.resolve() ? Promise.resolve()
: gulp : glupSrc(EN_SRC)
.src(EN_SRC)
.pipe(new CustomJSON(null, testReviver)) .pipe(new CustomJSON(null, testReviver))
.pipe(rename(`${TEST_LOCALE}.json`)) .pipe(rename(`${TEST_LOCALE}.json`))
.pipe(gulp.dest(workDir)); .pipe(gulpDest(workDir));
/** /**
* This task will build a master translation file, to be used as the base for * This task will build a master translation file, to be used as the base for
@ -157,11 +167,10 @@ const createTestTranslation = () =>
* the Lokalise update to translations/en.json will not happen immediately. * the Lokalise update to translations/en.json will not happen immediately.
*/ */
const createMasterTranslation = () => const createMasterTranslation = () =>
gulp glupSrc([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
.pipe(new CustomJSON(lokaliseTransform)) .pipe(new CustomJSON(lokaliseTransform))
.pipe(new MergeJSON("en")) .pipe(new MergeJSON("en"))
.pipe(gulp.dest(workDir)); .pipe(gulpDest(workDir));
const FRAGMENTS = ["base"]; const FRAGMENTS = ["base"];
@ -188,12 +197,12 @@ const createTranslations = async () => {
// each locale, then fragmentizes and flattens the data for final output. // each locale, then fragmentizes and flattens the data for final output.
const translationFiles = await glob([ const translationFiles = await glob([
`${inFrontendDir}/!(en).json`, `${inFrontendDir}/!(en).json`,
...(env.isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]), ...(isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]),
]); ]);
const hashStream = new Transform({ const hashStream = new Transform({
objectMode: true, objectMode: true,
transform: async (file, _, callback) => { transform: async (file, _, callback) => {
const hash = env.isProdBuild() const hash = isProdBuild()
? createHash("md5").update(file.contents).digest("hex") ? createHash("md5").update(file.contents).digest("hex")
: "dev"; : "dev";
HASHES.set(file.stem, hash); HASHES.set(file.stem, hash);
@ -232,7 +241,7 @@ const createTranslations = async () => {
}) })
) )
) )
.pipe(gulp.dest(outDir)); .pipe(gulpDest(outDir));
// Send the English master downstream first, then for each other locale // Send the English master downstream first, then for each other locale
// generate merged JSON data to continue piping. It begins with the master // generate merged JSON data to continue piping. It begins with the master
@ -242,9 +251,9 @@ const createTranslations = async () => {
// TODO: This is a naive interpretation of BCP47 that should be improved. // TODO: This is a naive interpretation of BCP47 that should be improved.
// Will be OK for now as long as we don't have anything more complicated // Will be OK for now as long as we don't have anything more complicated
// than a base translation + region. // than a base translation + region.
const masterStream = gulp const masterStream = glupSrc(`${workDir}/en.json`).pipe(
.src(`${workDir}/en.json`) new PassThrough({ objectMode: true })
.pipe(new PassThrough({ objectMode: true })); );
masterStream.pipe(hashStream, { end: false }); masterStream.pipe(hashStream, { end: false });
const mergesFinished = [finished(masterStream)]; const mergesFinished = [finished(masterStream)];
for (const translationFile of translationFiles) { for (const translationFile of translationFiles) {
@ -262,9 +271,9 @@ const createTranslations = async () => {
} }
} }
} }
const mergeStream = gulp const mergeStream = glupSrc(mergeFiles, { allowEmpty: true }).pipe(
.src(mergeFiles, { allowEmpty: true }) new MergeJSON(locale, enMaster, emptyReviver)
.pipe(new MergeJSON(locale, enMaster, emptyReviver)); );
mergesFinished.push(finished(mergeStream)); mergesFinished.push(finished(mergeStream));
mergeStream.pipe(hashStream, { end: false }); mergeStream.pipe(hashStream, { end: false });
} }
@ -277,12 +286,11 @@ const createTranslations = async () => {
}; };
const writeTranslationMetaData = () => const writeTranslationMetaData = () =>
gulp glupSrc([`${paths.translations_src}/translationMetadata.json`])
.src([`${paths.translations_src}/translationMetadata.json`])
.pipe( .pipe(
new CustomJSON((meta) => { new CustomJSON((meta) => {
// Add the test translation in development. // Add the test translation in development.
if (!env.isProdBuild()) { if (!isProdBuild()) {
meta[TEST_LOCALE] = { nativeName: "Translation Test" }; meta[TEST_LOCALE] = { nativeName: "Translation Test" };
} }
// Filter out locales without a native name, and add the hashes. // Filter out locales without a native name, and add the hashes.
@ -302,14 +310,14 @@ const writeTranslationMetaData = () =>
}; };
}) })
) )
.pipe(gulp.dest(workDir)); .pipe(gulpDest(workDir));
gulp.task( task(
"build-translations", "build-translations",
gulp.series( series(
gulp.parallel( parallel(
"fetch-nightly-translations", "fetch-nightly-translations",
gulp.series("clean-translations", makeWorkDir) series("clean-translations", makeWorkDir)
), ),
createTestTranslation, createTestTranslation,
createMasterTranslation, createMasterTranslation,
@ -318,12 +326,12 @@ gulp.task(
) )
); );
gulp.task( task(
"build-supervisor-translations", "build-supervisor-translations",
gulp.series(setFragment("supervisor"), "build-translations") series(setFragment("supervisor"), "build-translations")
); );
gulp.task( task(
"build-landing-page-translations", "build-landing-page-translations",
gulp.series(setFragment("landing-page"), "build-translations") series(setFragment("landing-page"), "build-translations")
); );

View File

@ -1,17 +1,15 @@
#!/usr/bin/env node #!/usr/bin/env node
// Script to print Babel plugins and Core JS polyfills that will be used by browserslist environments // Script to print Babel plugins and Core JS polyfills that will be used by browserslist environments
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-console */
import { version as babelVersion } from "@babel/core"; import { version as babelVersion } from "@babel/core";
import presetEnv from "@babel/preset-env"; import presetEnv from "@babel/preset-env";
import compilationTargets from "@babel/helper-compilation-targets"; import compilationTargets from "@babel/helper-compilation-targets";
import coreJSCompat from "core-js-compat"; import coreJSCompat from "core-js-compat";
// eslint-disable-next-line import/extensions
import { logPlugin } from "@babel/preset-env/lib/debug.js"; import { logPlugin } from "@babel/preset-env/lib/debug.js";
// eslint-disable-next-line import/no-relative-packages, import/extensions // eslint-disable-next-line import/no-relative-packages
import shippedPolyfills from "../node_modules/babel-plugin-polyfill-corejs3/lib/shipped-proposals.js"; import shippedPolyfills from "../node_modules/babel-plugin-polyfill-corejs3/lib/shipped-proposals.js";
import { babelOptions } from "./bundle"; import { babelOptions } from "./bundle.ts";
const detailsOpen = (heading) => const detailsOpen = (heading) =>
`<details>\n<summary><h4>${heading}</h4></summary>\n`; `<details>\n<summary><h4>${heading}</h4></summary>\n`;
@ -52,7 +50,7 @@ for (const buildType of ["Modern", "Legacy"]) {
const browserslistEnv = buildType.toLowerCase(); const browserslistEnv = buildType.toLowerCase();
const babelOpts = babelOptions({ latestBuild: browserslistEnv === "modern" }); const babelOpts = babelOptions({ latestBuild: browserslistEnv === "modern" });
const presetEnvOpts = babelOpts.presets[0][1]; const presetEnvOpts = babelOpts.presets[0][1];
if (typeof presetEnvOpts !== "object") { if (typeof presetEnvOpts !== "object") {
throw new Error( throw new Error(
"The first preset in babelOptions is not an object. This is unexpected." "The first preset in babelOptions is not an object. This is unexpected."

View File

@ -1,63 +1,63 @@
import path from "path"; import path, { dirname as pathDirname } from "node:path";
import { fileURLToPath } from "node:url";
export const dirname = pathDirname(fileURLToPath(import.meta.url));
export default { export default {
root_dir: path.resolve(__dirname, ".."), root_dir: path.resolve(dirname, ".."),
build_dir: path.resolve(__dirname, "../build"), build_dir: path.resolve(dirname, "../build"),
app_output_root: path.resolve(__dirname, "../hass_frontend"), app_output_root: path.resolve(dirname, "../hass_frontend"),
app_output_static: path.resolve(__dirname, "../hass_frontend/static"), app_output_static: path.resolve(dirname, "../hass_frontend/static"),
app_output_latest: path.resolve( app_output_latest: path.resolve(dirname, "../hass_frontend/frontend_latest"),
__dirname, app_output_es5: path.resolve(dirname, "../hass_frontend/frontend_es5"),
"../hass_frontend/frontend_latest"
),
app_output_es5: path.resolve(__dirname, "../hass_frontend/frontend_es5"),
demo_dir: path.resolve(__dirname, "../demo"), demo_dir: path.resolve(dirname, "../demo"),
demo_output_root: path.resolve(__dirname, "../demo/dist"), demo_output_root: path.resolve(dirname, "../demo/dist"),
demo_output_static: path.resolve(__dirname, "../demo/dist/static"), demo_output_static: path.resolve(dirname, "../demo/dist/static"),
demo_output_latest: path.resolve(__dirname, "../demo/dist/frontend_latest"), demo_output_latest: path.resolve(dirname, "../demo/dist/frontend_latest"),
demo_output_es5: path.resolve(__dirname, "../demo/dist/frontend_es5"), demo_output_es5: path.resolve(dirname, "../demo/dist/frontend_es5"),
cast_dir: path.resolve(__dirname, "../cast"), cast_dir: path.resolve(dirname, "../cast"),
cast_output_root: path.resolve(__dirname, "../cast/dist"), cast_output_root: path.resolve(dirname, "../cast/dist"),
cast_output_static: path.resolve(__dirname, "../cast/dist/static"), cast_output_static: path.resolve(dirname, "../cast/dist/static"),
cast_output_latest: path.resolve(__dirname, "../cast/dist/frontend_latest"), cast_output_latest: path.resolve(dirname, "../cast/dist/frontend_latest"),
cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"), cast_output_es5: path.resolve(dirname, "../cast/dist/frontend_es5"),
gallery_dir: path.resolve(__dirname, "../gallery"), gallery_dir: path.resolve(dirname, "../gallery"),
gallery_build: path.resolve(__dirname, "../gallery/build"), gallery_build: path.resolve(dirname, "../gallery/build"),
gallery_output_root: path.resolve(__dirname, "../gallery/dist"), gallery_output_root: path.resolve(dirname, "../gallery/dist"),
gallery_output_latest: path.resolve( gallery_output_latest: path.resolve(
__dirname, dirname,
"../gallery/dist/frontend_latest" "../gallery/dist/frontend_latest"
), ),
gallery_output_static: path.resolve(__dirname, "../gallery/dist/static"), gallery_output_static: path.resolve(dirname, "../gallery/dist/static"),
landingPage_dir: path.resolve(__dirname, "../landing-page"), landingPage_dir: path.resolve(dirname, "../landing-page"),
landingPage_build: path.resolve(__dirname, "../landing-page/build"), landingPage_build: path.resolve(dirname, "../landing-page/build"),
landingPage_output_root: path.resolve(__dirname, "../landing-page/dist"), landingPage_output_root: path.resolve(dirname, "../landing-page/dist"),
landingPage_output_latest: path.resolve( landingPage_output_latest: path.resolve(
__dirname, dirname,
"../landing-page/dist/frontend_latest" "../landing-page/dist/frontend_latest"
), ),
landingPage_output_es5: path.resolve( landingPage_output_es5: path.resolve(
__dirname, dirname,
"../landing-page/dist/frontend_es5" "../landing-page/dist/frontend_es5"
), ),
landingPage_output_static: path.resolve( landingPage_output_static: path.resolve(
__dirname, dirname,
"../landing-page/dist/static" "../landing-page/dist/static"
), ),
hassio_dir: path.resolve(__dirname, "../hassio"), hassio_dir: path.resolve(dirname, "../hassio"),
hassio_output_root: path.resolve(__dirname, "../hassio/build"), hassio_output_root: path.resolve(dirname, "../hassio/build"),
hassio_output_static: path.resolve(__dirname, "../hassio/build/static"), hassio_output_static: path.resolve(dirname, "../hassio/build/static"),
hassio_output_latest: path.resolve( hassio_output_latest: path.resolve(
__dirname, dirname,
"../hassio/build/frontend_latest" "../hassio/build/frontend_latest"
), ),
hassio_output_es5: path.resolve(__dirname, "../hassio/build/frontend_es5"), hassio_output_es5: path.resolve(dirname, "../hassio/build/frontend_es5"),
hassio_publicPath: "/api/hassio/app", hassio_publicPath: "/api/hassio/app",
translations_src: path.resolve(__dirname, "../src/translations"), translations_src: path.resolve(dirname, "../src/translations"),
}; };

View File

@ -1,17 +1,25 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/no-import-module-exports */
import filterStats from "@bundle-stats/plugin-webpack-filter"; import filterStats from "@bundle-stats/plugin-webpack-filter";
import { RsdoctorRspackPlugin } from "@rsdoctor/rspack-plugin"; import { RsdoctorRspackPlugin } from "@rsdoctor/rspack-plugin";
import * as rspack from "@rspack/core"; import { DefinePlugin, NormalModuleReplacementPlugin } from "@rspack/core";
import { defineConfig } from "@rspack/cli";
import log from "fancy-log"; import log from "fancy-log";
import { existsSync } from "fs"; import { existsSync } from "node:fs";
import path from "path"; import path from "node:path";
import { WebpackManifestPlugin } from "rspack-manifest-plugin"; import { WebpackManifestPlugin } from "rspack-manifest-plugin";
import TerserPlugin from "terser-webpack-plugin"; import TerserPlugin from "terser-webpack-plugin";
import { StatsWriterPlugin } from "webpack-stats-plugin"; import { StatsWriterPlugin } from "webpack-stats-plugin";
// @ts-ignore
import WebpackBar from "webpackbar/rspack"; import WebpackBar from "webpackbar/rspack";
import * as bundle from "./bundle"; import {
import * as paths from "./paths"; babelOptions,
config,
definedVars,
emptyPackages,
sourceMapURL,
swcOptions,
terserOptions,
} from "./bundle.ts";
import paths from "./paths.ts";
class LogStartCompilePlugin { class LogStartCompilePlugin {
ignoredFirst = false; ignoredFirst = false;
@ -39,12 +47,23 @@ export const createRspackConfig = ({
isTestBuild, isTestBuild,
isHassioBuild, isHassioBuild,
dontHash, dontHash,
}: {
name: string;
entry: any;
outputPath: string;
publicPath: string;
defineOverlay?: Record<string, any>;
isProdBuild?: boolean;
latestBuild?: boolean;
isStatsBuild?: boolean;
isTestBuild?: boolean;
isHassioBuild?: boolean;
dontHash?: Set<string>;
}) => { }) => {
if (!dontHash) { if (!dontHash) {
dontHash = new Set(); dontHash = new Set();
} }
const ignorePackages = bundle.ignorePackages({ latestBuild }); return defineConfig({
return {
name, name,
mode: isProdBuild ? "production" : "development", mode: isProdBuild ? "production" : "development",
target: `browserslist:${latestBuild ? "modern" : "legacy"}`, target: `browserslist:${latestBuild ? "modern" : "legacy"}`,
@ -67,7 +86,7 @@ export const createRspackConfig = ({
{ {
loader: "babel-loader", loader: "babel-loader",
options: { options: {
...bundle.babelOptions({ ...babelOptions({
latestBuild, latestBuild,
isProdBuild, isProdBuild,
isTestBuild, isTestBuild,
@ -79,7 +98,7 @@ export const createRspackConfig = ({
}, },
{ {
loader: "builtin:swc-loader", loader: "builtin:swc-loader",
options: bundle.swcOptions(), options: swcOptions(),
}, },
], ],
resolve: { resolve: {
@ -100,7 +119,7 @@ export const createRspackConfig = ({
new TerserPlugin({ new TerserPlugin({
parallel: true, parallel: true,
extractComments: true, extractComments: true,
terserOptions: bundle.terserOptions({ latestBuild, isTestBuild }), terserOptions: terserOptions({ latestBuild, isTestBuild }),
}), }),
], ],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
@ -119,7 +138,7 @@ export const createRspackConfig = ({
!chunk.canBeInitial() && !chunk.canBeInitial() &&
!new RegExp( !new RegExp(
`^.+-work${latestBuild ? "(?:let|er)" : "let"}$` `^.+-work${latestBuild ? "(?:let|er)" : "let"}$`
).test(chunk.name), ).test(chunk?.name || ""),
}, },
}, },
plugins: [ plugins: [
@ -128,44 +147,11 @@ export const createRspackConfig = ({
// Only include the JS of entrypoints // Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"), filter: (file) => file.isInitial && !file.name.endsWith(".map"),
}), }),
new rspack.DefinePlugin( new DefinePlugin(
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay }) definedVars({ isProdBuild, latestBuild, defineOverlay })
), ),
new rspack.IgnorePlugin({ new NormalModuleReplacementPlugin(
checkResource(resource, context) { new RegExp(emptyPackages({ isHassioBuild }).join("|")),
// Only use ignore to intercept imports that we don't control
// inside node_module dependencies.
if (
!context.includes("/node_modules/") ||
// calling define.amd will call require("!!webpack amd options")
resource.startsWith("!!webpack") ||
// loaded by webpack dev server but doesn't exist.
resource === "webpack/hot" ||
resource.startsWith("@swc/helpers")
) {
return false;
}
let fullPath;
try {
fullPath = resource.startsWith(".")
? path.resolve(context, resource)
: require.resolve(resource);
} catch (err) {
console.error(
"Error in Home Assistant ignore plugin",
resource,
context
);
throw err;
}
return ignorePackages.some((toIgnorePath) =>
fullPath.startsWith(toIgnorePath)
);
},
}),
new rspack.NormalModuleReplacementPlugin(
new RegExp(bundle.emptyPackages({ isHassioBuild }).join("|")),
path.resolve(paths.root_dir, "src/util/empty.js") path.resolve(paths.root_dir, "src/util/empty.js")
), ),
!isProdBuild && new LogStartCompilePlugin(), !isProdBuild && new LogStartCompilePlugin(),
@ -181,7 +167,9 @@ export const createRspackConfig = ({
isProdBuild && isProdBuild &&
isStatsBuild && isStatsBuild &&
new RsdoctorRspackPlugin({ new RsdoctorRspackPlugin({
reportDir: path.join(paths.build_dir, "rsdoctor"), output: {
reportDir: path.join(paths.build_dir, "rsdoctor"),
},
features: ["plugins", "bundle"], features: ["plugins", "bundle"],
supports: { supports: {
generateTileGraph: true, generateTileGraph: true,
@ -216,7 +204,9 @@ export const createRspackConfig = ({
output: { output: {
module: latestBuild, module: latestBuild,
filename: ({ chunk }) => filename: ({ chunk }) =>
!isProdBuild || isStatsBuild || dontHash.has(chunk.name) !isProdBuild ||
isStatsBuild ||
(chunk?.name && dontHash.has(chunk.name))
? "[name].js" ? "[name].js"
: "[name].[contenthash].js", : "[name].[contenthash].js",
chunkFilename: chunkFilename:
@ -247,7 +237,7 @@ export const createRspackConfig = ({
// dev tools, and they stay happy getting 404s with valid requests. // dev tools, and they stay happy getting 404s with valid requests.
return `/unknown${path.resolve("/", info.resourcePath)}`; return `/unknown${path.resolve("/", info.resourcePath)}`;
} }
return new URL(info.resourcePath, bundle.sourceMapURL()).href; return new URL(info.resourcePath, sourceMapURL()).href;
} }
: undefined, : undefined,
]) ])
@ -257,7 +247,7 @@ export const createRspackConfig = ({
layers: true, layers: true,
outputModule: true, outputModule: true,
}, },
}; });
}; };
export const createAppConfig = ({ export const createAppConfig = ({
@ -265,27 +255,43 @@ export const createAppConfig = ({
latestBuild, latestBuild,
isStatsBuild, isStatsBuild,
isTestBuild, isTestBuild,
}: {
isProdBuild?: boolean;
latestBuild?: boolean;
isStatsBuild?: boolean;
isTestBuild?: boolean;
}) => }) =>
createRspackConfig( createRspackConfig(
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild }) config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild })
); );
export const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => export const createDemoConfig = ({
createRspackConfig( isProdBuild,
bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild }) latestBuild,
); isStatsBuild,
}: {
isProdBuild?: boolean;
latestBuild?: boolean;
isStatsBuild?: boolean;
}) =>
createRspackConfig(config.demo({ isProdBuild, latestBuild, isStatsBuild }));
export const createCastConfig = ({ isProdBuild, latestBuild }) => export const createCastConfig = ({ isProdBuild, latestBuild }) =>
createRspackConfig(bundle.config.cast({ isProdBuild, latestBuild })); createRspackConfig(config.cast({ isProdBuild, latestBuild }));
export const createHassioConfig = ({ export const createHassioConfig = ({
isProdBuild, isProdBuild,
latestBuild, latestBuild,
isStatsBuild, isStatsBuild,
isTestBuild, isTestBuild,
}: {
isProdBuild?: boolean;
latestBuild?: boolean;
isStatsBuild?: boolean;
isTestBuild?: boolean;
}) => }) =>
createRspackConfig( createRspackConfig(
bundle.config.hassio({ config.hassio({
isProdBuild, isProdBuild,
latestBuild, latestBuild,
isStatsBuild, isStatsBuild,
@ -294,7 +300,7 @@ export const createHassioConfig = ({
); );
export const createGalleryConfig = ({ isProdBuild, latestBuild }) => export const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
createRspackConfig(bundle.config.gallery({ isProdBuild, latestBuild })); createRspackConfig(config.gallery({ isProdBuild, latestBuild }));
export const createLandingPageConfig = ({ isProdBuild, latestBuild }) => export const createLandingPageConfig = ({ isProdBuild, latestBuild }) =>
createRspackConfig(bundle.config.landingPage({ isProdBuild, latestBuild })); createRspackConfig(config.landingPage({ isProdBuild, latestBuild }));

View File

@ -20,8 +20,7 @@
"prepack": "pinst --disable", "prepack": "pinst --disable",
"postpack": "pinst --enable", "postpack": "pinst --enable",
"test": "vitest run --config test/vitest.config.ts", "test": "vitest run --config test/vitest.config.ts",
"test:coverage": "vitest run --config test/vitest.config.ts --coverage", "test:coverage": "vitest run --config test/vitest.config.ts --coverage"
"gulp": "node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js"
}, },
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)", "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@ -6,4 +6,4 @@ set -e
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
./node_modules/.bin/gulp build-app node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js build-app

View File

@ -6,4 +6,4 @@ set -e
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
./node_modules/.bin/gulp develop-app node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js develop-app

View File

@ -6,4 +6,4 @@ set -e
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
./node_modules/.bin/gulp setup-and-fetch-nightly-translations node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js setup-and-fetch-nightly-translations

View File

@ -6,4 +6,4 @@ set -e
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
./node_modules/.bin/gulp analyze-app node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js analyze-app

View File

@ -8,4 +8,4 @@ set -eu -o pipefail
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
./node_modules/.bin/gulp download-translations node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js download-translations

View File

@ -26,6 +26,7 @@
// Interop with CommonJS and other tools // Interop with CommonJS and other tools
"esModuleInterop": true, "esModuleInterop": true,
"isolatedModules": true, "isolatedModules": true,
"allowImportingTsExtensions": true,
"plugins": [ "plugins": [
{ {
"name": "ts-lit-plugin", "name": "ts-lit-plugin",

117
yarn.lock
View File

@ -4719,12 +4719,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/node@npm:*, @types/node@npm:>=10.0.0": "@types/node@npm:*, @types/node@npm:22.15.16, @types/node@npm:>=10.0.0":
version: 22.14.1 version: 22.15.16
resolution: "@types/node@npm:22.14.1" resolution: "@types/node@npm:22.15.16"
dependencies: dependencies:
undici-types: "npm:~6.21.0" undici-types: "npm:~6.21.0"
checksum: 10/561b1ad98ef5176d6da856ffbbe494f16655149f6a7d561de0423c8784910c81267d7d6459f59d68a97b3cbae9b5996b3b5dfe64f4de3de2239d295dcf4a4dcc checksum: 10/d8055a0ab033ed16368109183f7e11d5364e5d8d5bd9a12df7fa1673a624823aaaaa54c0afef1648d0bfa7e12ef20b600f9d006accebecdb9931d2b72d05c7be
languageName: node languageName: node
linkType: hard linkType: hard
@ -4735,15 +4735,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/node@npm:22.15.16":
version: 22.15.16
resolution: "@types/node@npm:22.15.16"
dependencies:
undici-types: "npm:~6.21.0"
checksum: 10/d8055a0ab033ed16368109183f7e11d5364e5d8d5bd9a12df7fa1673a624823aaaaa54c0afef1648d0bfa7e12ef20b600f9d006accebecdb9931d2b72d05c7be
languageName: node
linkType: hard
"@types/node@npm:^18.15.3": "@types/node@npm:^18.15.3":
version: 18.19.86 version: 18.19.86
resolution: "@types/node@npm:18.19.86" resolution: "@types/node@npm:18.19.86"
@ -7641,17 +7632,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"enhanced-resolve@npm:^0.9.1":
version: 0.9.1
resolution: "enhanced-resolve@npm:0.9.1"
dependencies:
graceful-fs: "npm:^4.1.2"
memory-fs: "npm:^0.2.0"
tapable: "npm:^0.1.8"
checksum: 10/0044dad5e27adb608ba6c87939aef39ac3131d7f189470e5e3643ce78ec6ed06ccaed9e887103c64a2d36718d38e4568c0ffbdce5eed2bc6921cafc38d250ddc
languageName: node
linkType: hard
"entities@npm:^4.2.0, entities@npm:^4.4.0, entities@npm:^4.5.0": "entities@npm:^4.2.0, entities@npm:^4.4.0, entities@npm:^4.5.0":
version: 4.5.0 version: 4.5.0
resolution: "entities@npm:4.5.0" resolution: "entities@npm:4.5.0"
@ -7977,27 +7957,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"eslint-import-resolver-webpack@npm:0.13.10":
version: 0.13.10
resolution: "eslint-import-resolver-webpack@npm:0.13.10"
dependencies:
debug: "npm:^3.2.7"
enhanced-resolve: "npm:^0.9.1"
find-root: "npm:^1.1.0"
hasown: "npm:^2.0.2"
interpret: "npm:^1.4.0"
is-core-module: "npm:^2.15.1"
is-regex: "npm:^1.2.0"
lodash: "npm:^4.17.21"
resolve: "npm:^2.0.0-next.5"
semver: "npm:^5.7.2"
peerDependencies:
eslint-plugin-import: ">=1.4.0"
webpack: ">=1.11.0"
checksum: 10/7d317bc96b2590ba83f0f2af2e64f67693452756bc95ffd77381e8177365ac10c2fb288776fc6620376d7fc811902a7efe4932010bc250a1ac5fd4171e2402fb
languageName: node
linkType: hard
"eslint-module-utils@npm:^2.12.0": "eslint-module-utils@npm:^2.12.0":
version: 2.12.0 version: 2.12.0
resolution: "eslint-module-utils@npm:2.12.0" resolution: "eslint-module-utils@npm:2.12.0"
@ -8692,13 +8651,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"find-root@npm:^1.1.0":
version: 1.1.0
resolution: "find-root@npm:1.1.0"
checksum: 10/caa799c976a14925ba7f31ca1a226fe73d3aa270f4f1b623fcfeb1c6e263111db4beb807d8acd31bd4d48d44c343b93688a9288dfbccca27463c36a0301b0bb9
languageName: node
linkType: hard
"find-up@npm:^4.1.0": "find-up@npm:^4.1.0":
version: 4.1.0 version: 4.1.0
resolution: "find-up@npm:4.1.0" resolution: "find-up@npm:4.1.0"
@ -9222,7 +9174,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.8": "graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.8":
version: 4.2.11 version: 4.2.11
resolution: "graceful-fs@npm:4.2.11" resolution: "graceful-fs@npm:4.2.11"
checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2
@ -9554,7 +9506,6 @@ __metadata:
eslint: "npm:9.26.0" eslint: "npm:9.26.0"
eslint-config-airbnb-base: "npm:15.0.0" eslint-config-airbnb-base: "npm:15.0.0"
eslint-config-prettier: "npm:10.1.2" eslint-config-prettier: "npm:10.1.2"
eslint-import-resolver-webpack: "npm:0.13.10"
eslint-plugin-import: "npm:2.31.0" eslint-plugin-import: "npm:2.31.0"
eslint-plugin-lit: "npm:2.1.1" eslint-plugin-lit: "npm:2.1.1"
eslint-plugin-lit-a11y: "npm:4.1.4" eslint-plugin-lit-a11y: "npm:4.1.4"
@ -9978,13 +9929,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"interpret@npm:^1.4.0":
version: 1.4.0
resolution: "interpret@npm:1.4.0"
checksum: 10/5beec568d3f60543d0f61f2c5969d44dffcb1a372fe5abcdb8013968114d4e4aaac06bc971a4c9f5bd52d150881d8ebad72a8c60686b1361f5f0522f39c0e1a3
languageName: node
linkType: hard
"interpret@npm:^3.1.1": "interpret@npm:^3.1.1":
version: 3.1.1 version: 3.1.1
resolution: "interpret@npm:3.1.1" resolution: "interpret@npm:3.1.1"
@ -10344,7 +10288,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"is-regex@npm:^1.2.0, is-regex@npm:^1.2.1": "is-regex@npm:^1.2.1":
version: 1.2.1 version: 1.2.1
resolution: "is-regex@npm:1.2.1" resolution: "is-regex@npm:1.2.1"
dependencies: dependencies:
@ -11336,13 +11280,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"memory-fs@npm:^0.2.0":
version: 0.2.0
resolution: "memory-fs@npm:0.2.0"
checksum: 10/67ff4642b7767bf00159c248dbaa1203369866e9224579f8a7c7c0c3b0ed0a6e5eaa38b4753a9aa59e0f063bd09e2c9a2a97a8e593ec395b06ce216f75fc573d
languageName: node
linkType: hard
"merge-descriptors@npm:1.0.3": "merge-descriptors@npm:1.0.3":
version: 1.0.3 version: 1.0.3
resolution: "merge-descriptors@npm:1.0.3" resolution: "merge-descriptors@npm:1.0.3"
@ -13004,19 +12941,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"resolve@npm:^2.0.0-next.5":
version: 2.0.0-next.5
resolution: "resolve@npm:2.0.0-next.5"
dependencies:
is-core-module: "npm:^2.13.0"
path-parse: "npm:^1.0.7"
supports-preserve-symlinks-flag: "npm:^1.0.0"
bin:
resolve: bin/resolve
checksum: 10/2d6fd28699f901744368e6f2032b4268b4c7b9185fd8beb64f68c93ac6b22e52ae13560ceefc96241a665b985edf9ffd393ae26d2946a7d3a07b7007b7d51e79
languageName: node
linkType: hard
"resolve@patch:resolve@npm%3A^1.14.2#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>": "resolve@patch:resolve@npm%3A^1.14.2#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>":
version: 1.22.10 version: 1.22.10
resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin<compat/resolve>::version=1.22.10&hash=c3c19d" resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin<compat/resolve>::version=1.22.10&hash=c3c19d"
@ -13030,19 +12954,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin<compat/resolve>":
version: 2.0.0-next.5
resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin<compat/resolve>::version=2.0.0-next.5&hash=c3c19d"
dependencies:
is-core-module: "npm:^2.13.0"
path-parse: "npm:^1.0.7"
supports-preserve-symlinks-flag: "npm:^1.0.0"
bin:
resolve: bin/resolve
checksum: 10/05fa778de9d0347c8b889eb7a18f1f06bf0f801b0eb4610b4871a4b2f22e220900cf0ad525e94f990bb8d8921c07754ab2122c0c225ab4cdcea98f36e64fa4c2
languageName: node
linkType: hard
"restore-cursor@npm:^5.0.0": "restore-cursor@npm:^5.0.0":
version: 5.1.0 version: 5.1.0
resolution: "restore-cursor@npm:5.1.0" resolution: "restore-cursor@npm:5.1.0"
@ -13345,15 +13256,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"semver@npm:^5.7.2":
version: 5.7.2
resolution: "semver@npm:5.7.2"
bin:
semver: bin/semver
checksum: 10/fca14418a174d4b4ef1fecb32c5941e3412d52a4d3d85165924ce3a47fbc7073372c26faf7484ceb4bbc2bde25880c6b97e492473dc7e9708fdfb1c6a02d546e
languageName: node
linkType: hard
"semver@npm:^6.3.0, semver@npm:^6.3.1": "semver@npm:^6.3.0, semver@npm:^6.3.1":
version: 6.3.1 version: 6.3.1
resolution: "semver@npm:6.3.1" resolution: "semver@npm:6.3.1"
@ -14279,13 +14181,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"tapable@npm:^0.1.8":
version: 0.1.10
resolution: "tapable@npm:0.1.10"
checksum: 10/c1059b232ff4626dd032bc5620348983071c90edee5ebf779fb11bab59b31663d9d474da0d7c43c2004145f6dac260634a60b8dbd20691f514fc5d84fe5eda1f
languageName: node
linkType: hard
"tar@npm:7.4.3, tar@npm:^7.4.3": "tar@npm:7.4.3, tar@npm:^7.4.3":
version: 7.4.3 version: 7.4.3
resolution: "tar@npm:7.4.3" resolution: "tar@npm:7.4.3"