mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-10-31 14:39:38 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			update-typ
			...
			fix-backgr
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | cc1a0b24f0 | 
| @@ -1,36 +1,39 @@ | |||||||
| [modern] | [modern] | ||||||
| # Modern builds target recent browsers supporting the latest features to minimize transpilation, polyfills, etc. | # Support for dynamic import is the main litmus test for serving modern builds. | ||||||
| # It is served to browsers meeting the following requirements: | # Although officially a ES2020 feature, browsers implemented it early, so this | ||||||
| # - released in the last year + current alpha/beta versions | # enables all of ES2017 and some features in ES2018. | ||||||
| # - Firefox extended support release (ESR) | supports es6-module-dynamic-import | ||||||
| # - with global utilization at or above 0.5% |  | ||||||
| # - exclude dead browsers (no security maintenance for 2+ years) | # Exclude Safari 11-12 because of a bug in tagged template literals | ||||||
| # - exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data | # https://bugs.webkit.org/show_bug.cgi?id=190756 | ||||||
| unreleased versions | # Note: Dropping version 11 also enables several more ES2018 features | ||||||
| last 1 year | not Safari < 13 | ||||||
| Firefox ESR | not iOS < 13 | ||||||
| >= 0.5% |  | ||||||
| not dead | # Exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data | ||||||
|  | # Babel ignores these automatically, but we need here for Webpack to output ESM with dynamic imports | ||||||
| not KaiOS > 0 | not KaiOS > 0 | ||||||
| not QQAndroid > 0 | not QQAndroid > 0 | ||||||
| not UCAndroid > 0 | not UCAndroid > 0 | ||||||
|  |  | ||||||
|  | # Exclude unsupported browsers | ||||||
|  | not dead | ||||||
|  |  | ||||||
| [legacy] | [legacy] | ||||||
| # Legacy builds are served when modern requirements are not met and support browsers: | # Legacy builds are served when modern requirements are not met and support browsers: | ||||||
| # - released in the last 7 years + current alpha/beta versionss | # - released in the last 7 years + current alpha/beta versionss | ||||||
| # - with global utilization at or above 0.05% | # - with global utilization above 0.05% | ||||||
| # - exclude dead browsers (no security maintenance for 2+ years) | # The lattermost query ensures that support for popular old browsers is not dropped too early | ||||||
| # - exclude Opera Mini which does not support web sockets | # (e.g. IE 11, Android 4.4, or Samsung 4). | ||||||
|  | # | ||||||
|  | # In addition, legacy browsers must support some minimum features that cannot be polyfilled: | ||||||
|  | # - ES5 (strict mode) | ||||||
|  | # - web sockets to communicate with backend | ||||||
|  | # - inline SVG used widely in buttons, widgets, etc. | ||||||
|  | # - custom events used for most user interactions | ||||||
|  | # - CSS flexbox used in the majority of the layout | ||||||
|  | # Nearly all of these are redundant with the above rules. | ||||||
|  | # As of May 2023, only web sockets must be added to the query. | ||||||
| unreleased versions | unreleased versions | ||||||
| last 7 years | last 7 years | ||||||
| >= 0.05% | > 0.05% and supports websockets | ||||||
| not dead |  | ||||||
| not op_mini all |  | ||||||
|  |  | ||||||
| [legacy-sw] |  | ||||||
| # Same as legacy plus supports service workers |  | ||||||
| unreleased versions |  | ||||||
| last 7 years |  | ||||||
| >= 0.05% and supports serviceworkers |  | ||||||
| not dead |  | ||||||
| not op_mini all |  | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| FROM mcr.microsoft.com/devcontainers/python:1-3.13 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile | ||||||
|  | FROM mcr.microsoft.com/devcontainers/python:3.12 | ||||||
|  |  | ||||||
| ENV \ | ENV \ | ||||||
|   DEBIAN_FRONTEND=noninteractive \ |   DEBIAN_FRONTEND=noninteractive \ | ||||||
|   | |||||||
| @@ -5,15 +5,11 @@ | |||||||
|     "context": ".." |     "context": ".." | ||||||
|   }, |   }, | ||||||
|   "appPort": "8124:8123", |   "appPort": "8124:8123", | ||||||
|   "postCreateCommand": "./.devcontainer/post_create.sh", |   "postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev", | ||||||
|   "postStartCommand": "script/bootstrap", |   "postStartCommand": "script/bootstrap", | ||||||
|   "containerEnv": { |   "containerEnv": { | ||||||
|     "DEV_CONTAINER": "1", |  | ||||||
|     "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" |     "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" | ||||||
|   }, |   }, | ||||||
|   "remoteEnv": { |  | ||||||
|     "NODE_OPTIONS": "--max_old_space_size=8192" |  | ||||||
|   }, |  | ||||||
|   "customizations": { |   "customizations": { | ||||||
|     "vscode": { |     "vscode": { | ||||||
|       "extensions": [ |       "extensions": [ | ||||||
| @@ -21,8 +17,7 @@ | |||||||
|         "esbenp.prettier-vscode", |         "esbenp.prettier-vscode", | ||||||
|         "runem.lit-plugin", |         "runem.lit-plugin", | ||||||
|         "github.vscode-pull-request-github", |         "github.vscode-pull-request-github", | ||||||
|         "eamodio.gitlens", |         "eamodio.gitlens" | ||||||
|         "yeion7.styled-global-variables-autocomplete" |  | ||||||
|       ], |       ], | ||||||
|       "settings": { |       "settings": { | ||||||
|         "files.eol": "\n", |         "files.eol": "\n", | ||||||
|   | |||||||
| @@ -1,22 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| # This script will run after the container is created |  | ||||||
|  |  | ||||||
| # add github cli |  | ||||||
| (type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) \ |  | ||||||
| 	&& sudo mkdir -p -m 755 /etc/apt/keyrings \ |  | ||||||
|         && out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \ |  | ||||||
|         && cat $out | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ |  | ||||||
| 	&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ |  | ||||||
| 	&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null |  | ||||||
|  |  | ||||||
| # Update package lists |  | ||||||
| sudo apt-get update |  | ||||||
|  |  | ||||||
| sudo apt upgrade -y |  | ||||||
|  |  | ||||||
| # Install necessary packages |  | ||||||
| sudo apt-get install -y libpcap-dev gh |  | ||||||
|  |  | ||||||
| # Display a message |  | ||||||
| echo "Post-create script has been executed successfully." |  | ||||||
							
								
								
									
										130
									
								
								.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | |||||||
|  | { | ||||||
|  |   "extends": [ | ||||||
|  |     "airbnb-base", | ||||||
|  |     "airbnb-typescript/base", | ||||||
|  |     "plugin:@typescript-eslint/recommended", | ||||||
|  |     "plugin:wc/recommended", | ||||||
|  |     "plugin:lit/all", | ||||||
|  |     "plugin:lit-a11y/recommended", | ||||||
|  |     "prettier" | ||||||
|  |   ], | ||||||
|  |   "parser": "@typescript-eslint/parser", | ||||||
|  |   "parserOptions": { | ||||||
|  |     "ecmaVersion": 2020, | ||||||
|  |     "ecmaFeatures": { | ||||||
|  |       "modules": true | ||||||
|  |     }, | ||||||
|  |     "sourceType": "module", | ||||||
|  |     "project": "./tsconfig.json" | ||||||
|  |   }, | ||||||
|  |   "settings": { | ||||||
|  |     "import/resolver": { | ||||||
|  |       "webpack": { | ||||||
|  |         "config": "./webpack.config.cjs" | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "globals": { | ||||||
|  |     "__DEV__": false, | ||||||
|  |     "__DEMO__": false, | ||||||
|  |     "__BUILD__": false, | ||||||
|  |     "__VERSION__": false, | ||||||
|  |     "__STATIC_PATH__": false, | ||||||
|  |     "__SUPERVISOR__": false, | ||||||
|  |     "Polymer": true | ||||||
|  |   }, | ||||||
|  |   "env": { | ||||||
|  |     "browser": true, | ||||||
|  |     "es6": true | ||||||
|  |   }, | ||||||
|  |   "rules": { | ||||||
|  |     "class-methods-use-this": "off", | ||||||
|  |     "new-cap": "off", | ||||||
|  |     "prefer-template": "off", | ||||||
|  |     "object-shorthand": "off", | ||||||
|  |     "func-names": "off", | ||||||
|  |     "no-underscore-dangle": "off", | ||||||
|  |     "strict": "off", | ||||||
|  |     "no-plusplus": "off", | ||||||
|  |     "no-bitwise": "error", | ||||||
|  |     "comma-dangle": "off", | ||||||
|  |     "vars-on-top": "off", | ||||||
|  |     "no-continue": "off", | ||||||
|  |     "no-param-reassign": "off", | ||||||
|  |     "no-multi-assign": "off", | ||||||
|  |     "no-console": "error", | ||||||
|  |     "radix": "off", | ||||||
|  |     "no-alert": "off", | ||||||
|  |     "no-nested-ternary": "off", | ||||||
|  |     "prefer-destructuring": "off", | ||||||
|  |     "no-restricted-globals": [2, "event"], | ||||||
|  |     "prefer-promise-reject-errors": "off", | ||||||
|  |     "import/prefer-default-export": "off", | ||||||
|  |     "import/no-default-export": "off", | ||||||
|  |     "import/no-unresolved": "off", | ||||||
|  |     "import/no-cycle": "off", | ||||||
|  |     "import/extensions": [ | ||||||
|  |       "error", | ||||||
|  |       "ignorePackages", | ||||||
|  |       { | ||||||
|  |         "ts": "never", | ||||||
|  |         "js": "never" | ||||||
|  |       } | ||||||
|  |     ], | ||||||
|  |     "no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"], | ||||||
|  |     "object-curly-newline": "off", | ||||||
|  |     "default-case": "off", | ||||||
|  |     "wc/no-self-class": "off", | ||||||
|  |     "no-shadow": "off", | ||||||
|  |     "@typescript-eslint/camelcase": "off", | ||||||
|  |     "@typescript-eslint/ban-ts-comment": "off", | ||||||
|  |     "@typescript-eslint/no-use-before-define": "off", | ||||||
|  |     "@typescript-eslint/no-non-null-assertion": "off", | ||||||
|  |     "@typescript-eslint/no-explicit-any": "off", | ||||||
|  |     "@typescript-eslint/explicit-function-return-type": "off", | ||||||
|  |     "@typescript-eslint/explicit-module-boundary-types": "off", | ||||||
|  |     "@typescript-eslint/no-shadow": ["error"], | ||||||
|  |     "@typescript-eslint/naming-convention": [ | ||||||
|  |       "off", | ||||||
|  |       { | ||||||
|  |         "selector": "default", | ||||||
|  |         "format": ["camelCase", "snake_case"], | ||||||
|  |         "leadingUnderscore": "allow", | ||||||
|  |         "trailingUnderscore": "allow" | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "selector": ["variable"], | ||||||
|  |         "format": ["camelCase", "snake_case", "UPPER_CASE"], | ||||||
|  |         "leadingUnderscore": "allow", | ||||||
|  |         "trailingUnderscore": "allow" | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "selector": "typeLike", | ||||||
|  |         "format": ["PascalCase"] | ||||||
|  |       } | ||||||
|  |     ], | ||||||
|  |     "@typescript-eslint/no-unused-vars": "off", | ||||||
|  |     "unused-imports/no-unused-vars": [ | ||||||
|  |       "error", | ||||||
|  |       { | ||||||
|  |         "vars": "all", | ||||||
|  |         "varsIgnorePattern": "^_", | ||||||
|  |         "args": "after-used", | ||||||
|  |         "argsIgnorePattern": "^_", | ||||||
|  |         "ignoreRestSiblings": true | ||||||
|  |       } | ||||||
|  |     ], | ||||||
|  |     "unused-imports/no-unused-imports": "error", | ||||||
|  |     "lit/attribute-value-entities": "off", | ||||||
|  |     "lit/no-template-map": "off", | ||||||
|  |     "lit/no-native-attributes": "warn", | ||||||
|  |     "lit/no-this-assign-in-render": "warn", | ||||||
|  |     "lit-a11y/click-events-have-key-events": ["off"], | ||||||
|  |     "lit-a11y/no-autofocus": "off", | ||||||
|  |     "lit-a11y/alt-text": "warn", | ||||||
|  |     "lit-a11y/anchor-is-valid": "warn", | ||||||
|  |     "lit-a11y/role-has-required-aria-attrs": "warn" | ||||||
|  |   }, | ||||||
|  |   "plugins": ["disable", "unused-imports"], | ||||||
|  |   "processor": "disable/disable" | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							| @@ -7,7 +7,7 @@ body: | |||||||
|       value: | |       value: | | ||||||
|         Make sure you are running the [latest version of Home Assistant][releases] before reporting an issue. |         Make sure you are running the [latest version of Home Assistant][releases] before reporting an issue. | ||||||
|  |  | ||||||
|         If you have a feature or enhancement request for the frontend, please [start a discussion][fr] instead of creating an issue. |         If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue. | ||||||
|  |  | ||||||
|         **Please do not report issues for custom cards.** |         **Please do not report issues for custom cards.** | ||||||
|  |  | ||||||
| @@ -74,7 +74,7 @@ body: | |||||||
|         If known, otherwise leave blank. |         If known, otherwise leave blank. | ||||||
|   - type: input |   - type: input | ||||||
|     attributes: |     attributes: | ||||||
|       label: In which browser are you experiencing the issue? |       label: In which browser are you experiencing the issue with? | ||||||
|       placeholder: Google Chrome 88.0.4324.150 |       placeholder: Google Chrome 88.0.4324.150 | ||||||
|       description: > |       description: > | ||||||
|         Provide the full name and don't forget to add the version! |         Provide the full name and don't forget to add the version! | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ blank_issues_enabled: false | |||||||
| contact_links: | contact_links: | ||||||
|   - name: Request a feature for the UI / Dashboards |   - name: Request a feature for the UI / Dashboards | ||||||
|     url: https://github.com/home-assistant/frontend/discussions/category_choices |     url: https://github.com/home-assistant/frontend/discussions/category_choices | ||||||
|     about: Request a new feature for the Home Assistant frontend. |     about: Request an new feature for the Home Assistant frontend. | ||||||
|   - name: Report a bug that is NOT related to the UI / Dashboards |   - name: Report a bug that is NOT related to the UI / Dashboards | ||||||
|     url: https://github.com/home-assistant/core/issues |     url: https://github.com/home-assistant/core/issues | ||||||
|     about: This is the issue tracker for our frontend. Please report other issues in the backend ("core") repository. |     about: This is the issue tracker for our frontend. Please report other issues in the backend ("core") repository. | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -21,12 +21,12 @@ jobs: | |||||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} |       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|         with: |         with: | ||||||
|           ref: dev |           ref: dev | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -41,8 +41,9 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Deploy to Netlify |       - name: Deploy to Netlify | ||||||
|         id: deploy |         id: deploy | ||||||
|         run: | |         uses: netlify/actions/cli@master | ||||||
|           npx -y netlify-cli deploy --dir=cast/dist --alias dev |         with: | ||||||
|  |           args: deploy --dir=cast/dist --alias dev | ||||||
|         env: |         env: | ||||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} |           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }} |           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }} | ||||||
| @@ -56,12 +57,12 @@ jobs: | |||||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} |       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|         with: |         with: | ||||||
|           ref: master |           ref: master | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -76,8 +77,9 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Deploy to Netlify |       - name: Deploy to Netlify | ||||||
|         id: deploy |         id: deploy | ||||||
|         run: | |         uses: netlify/actions/cli@master | ||||||
|           npx -y netlify-cli deploy --dir=cast/dist --prod |         with: | ||||||
|  |           args: deploy --dir=cast/dist --prod | ||||||
|         env: |         env: | ||||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} |           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }} |           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }} | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -24,9 +24,9 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -37,7 +37,7 @@ jobs: | |||||||
|       - name: Build resources |       - name: Build resources | ||||||
|         run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages |         run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages | ||||||
|       - name: Setup lint cache |       - name: Setup lint cache | ||||||
|         uses: actions/cache@v4.2.3 |         uses: actions/cache@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           path: | |           path: | | ||||||
|             node_modules/.cache/prettier |             node_modules/.cache/prettier | ||||||
| @@ -58,9 +58,9 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -76,9 +76,9 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -89,7 +89,7 @@ jobs: | |||||||
|         env: |         env: | ||||||
|           IS_TEST: "true" |           IS_TEST: "true" | ||||||
|       - name: Upload bundle stats |       - name: Upload bundle stats | ||||||
|         uses: actions/upload-artifact@v4.6.2 |         uses: actions/upload-artifact@v4.3.1 | ||||||
|         with: |         with: | ||||||
|           name: frontend-bundle-stats |           name: frontend-bundle-stats | ||||||
|           path: build/stats/*.json |           path: build/stats/*.json | ||||||
| @@ -100,9 +100,9 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -113,7 +113,7 @@ jobs: | |||||||
|         env: |         env: | ||||||
|           IS_TEST: "true" |           IS_TEST: "true" | ||||||
|       - name: Upload bundle stats |       - name: Upload bundle stats | ||||||
|         uses: actions/upload-artifact@v4.6.2 |         uses: actions/upload-artifact@v4.3.1 | ||||||
|         with: |         with: | ||||||
|           name: supervisor-bundle-stats |           name: supervisor-bundle-stats | ||||||
|           path: build/stats/*.json |           path: build/stats/*.json | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,7 +23,7 @@ jobs: | |||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout repository |       - name: Checkout repository | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|         with: |         with: | ||||||
|           # We must fetch at least the immediate parents so that if this is |           # We must fetch at least the immediate parents so that if this is | ||||||
|           # a pull request then we can checkout the head. |           # a pull request then we can checkout the head. | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -22,12 +22,12 @@ jobs: | |||||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} |       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|         with: |         with: | ||||||
|           ref: dev |           ref: dev | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -42,8 +42,9 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Deploy to Netlify |       - name: Deploy to Netlify | ||||||
|         id: deploy |         id: deploy | ||||||
|         run: | |         uses: netlify/actions/cli@master | ||||||
|           npx -y netlify-cli deploy --dir=demo/dist --prod |         with: | ||||||
|  |           args: deploy --dir=demo/dist --prod | ||||||
|         env: |         env: | ||||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} |           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }} |           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }} | ||||||
| @@ -57,12 +58,12 @@ jobs: | |||||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} |       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|         with: |         with: | ||||||
|           ref: master |           ref: master | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -77,8 +78,9 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Deploy to Netlify |       - name: Deploy to Netlify | ||||||
|         id: deploy |         id: deploy | ||||||
|         run: | |         uses: netlify/actions/cli@master | ||||||
|           npx -y netlify-cli deploy --dir=demo/dist --prod |         with: | ||||||
|  |           args: deploy --dir=demo/dist --prod | ||||||
|         env: |         env: | ||||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} |           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_SITE_ID }} |           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_SITE_ID }} | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -16,10 +16,10 @@ jobs: | |||||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} |       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -34,8 +34,9 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Deploy to Netlify |       - name: Deploy to Netlify | ||||||
|         id: deploy |         id: deploy | ||||||
|         run: | |         uses: netlify/actions/cli@master | ||||||
|           npx -y netlify-cli deploy --dir=gallery/dist --prod |         with: | ||||||
|  |           args: deploy --dir=gallery/dist --prod | ||||||
|         env: |         env: | ||||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} |           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }} |           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }} | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -21,10 +21,10 @@ jobs: | |||||||
|     if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview') |     if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview') | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -39,14 +39,13 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Deploy preview to Netlify |       - name: Deploy preview to Netlify | ||||||
|         id: deploy |         id: deploy | ||||||
|         run: | |         uses: netlify/actions/cli@master | ||||||
|           npx -y netlify-cli deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}" \ |         with: | ||||||
|             --json > deploy_output.json |           args: deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}" | ||||||
|         env: |         env: | ||||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} |           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }} |           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }} | ||||||
|  |  | ||||||
|       - name: Generate summary |       - name: Generate summary | ||||||
|         run: | |         run: | | ||||||
|           NETLIFY_LIVE_URL=$(jq -r '.deploy_url' deploy_output.json) |           echo "${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}" >> "$GITHUB_STEP_SUMMARY" | ||||||
|           echo "$NETLIFY_LIVE_URL" >> "$GITHUB_STEP_SUMMARY" |  | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -6,7 +6,7 @@ on: | |||||||
|     - cron: "0 1 * * *" |     - cron: "0 1 * * *" | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   PYTHON_VERSION: "3.13" |   PYTHON_VERSION: "3.12" | ||||||
|   NODE_OPTIONS: --max_old_space_size=6144 |   NODE_OPTIONS: --max_old_space_size=6144 | ||||||
|  |  | ||||||
| permissions: | permissions: | ||||||
| @@ -20,7 +20,7 @@ jobs: | |||||||
|       contents: write |       contents: write | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout the repository |       - name: Checkout the repository | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|  |  | ||||||
|       - name: Set up Python ${{ env.PYTHON_VERSION }} |       - name: Set up Python ${{ env.PYTHON_VERSION }} | ||||||
|         uses: actions/setup-python@v5 |         uses: actions/setup-python@v5 | ||||||
| @@ -28,7 +28,7 @@ jobs: | |||||||
|           python-version: ${{ env.PYTHON_VERSION }} |           python-version: ${{ env.PYTHON_VERSION }} | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -57,14 +57,14 @@ jobs: | |||||||
|         run: tar -czvf translations.tar.gz translations |         run: tar -czvf translations.tar.gz translations | ||||||
|  |  | ||||||
|       - name: Upload build artifacts |       - name: Upload build artifacts | ||||||
|         uses: actions/upload-artifact@v4.6.2 |         uses: actions/upload-artifact@v4.3.1 | ||||||
|         with: |         with: | ||||||
|           name: wheels |           name: wheels | ||||||
|           path: dist/home_assistant_frontend*.whl |           path: dist/home_assistant_frontend*.whl | ||||||
|           if-no-files-found: error |           if-no-files-found: error | ||||||
|  |  | ||||||
|       - name: Upload translations |       - name: Upload translations | ||||||
|         uses: actions/upload-artifact@v4.6.2 |         uses: actions/upload-artifact@v4.3.1 | ||||||
|         with: |         with: | ||||||
|           name: translations |           name: translations | ||||||
|           path: translations.tar.gz |           path: translations.tar.gz | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/relative-ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/relative-ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -17,7 +17,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Send bundle stats and build information to RelativeCI |       - name: Send bundle stats and build information to RelativeCI | ||||||
|         uses: relative-ci/agent-action@v2.2.0 |         uses: relative-ci/agent-action@v2.1.10 | ||||||
|         with: |         with: | ||||||
|           key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }} |           key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }} | ||||||
|           token: ${{ github.token }} |           token: ${{ github.token }} | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/release-drafter.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release-drafter.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -18,6 +18,6 @@ jobs: | |||||||
|       pull-requests: read |       pull-requests: read | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: release-drafter/release-drafter@v6.1.0 |       - uses: release-drafter/release-drafter@v6.0.0 | ||||||
|         env: |         env: | ||||||
|           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||
|   | |||||||
							
								
								
									
										76
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										76
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -6,7 +6,7 @@ on: | |||||||
|       - published |       - published | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   PYTHON_VERSION: "3.13" |   PYTHON_VERSION: "3.12" | ||||||
|   NODE_OPTIONS: --max_old_space_size=6144 |   NODE_OPTIONS: --max_old_space_size=6144 | ||||||
|  |  | ||||||
| # Set default workflow permissions | # Set default workflow permissions | ||||||
| @@ -23,18 +23,18 @@ jobs: | |||||||
|       contents: write # Required to upload release assets |       contents: write # Required to upload release assets | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout the repository |       - name: Checkout the repository | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|  |  | ||||||
|  |       - name: Verify version | ||||||
|  |         uses: home-assistant/actions/helpers/verify-version@master | ||||||
|  |  | ||||||
|       - name: Set up Python ${{ env.PYTHON_VERSION }} |       - name: Set up Python ${{ env.PYTHON_VERSION }} | ||||||
|         uses: actions/setup-python@v5 |         uses: actions/setup-python@v5 | ||||||
|         with: |         with: | ||||||
|           python-version: ${{ env.PYTHON_VERSION }} |           python-version: ${{ env.PYTHON_VERSION }} | ||||||
|  |  | ||||||
|       - name: Verify version |  | ||||||
|         uses: home-assistant/actions/helpers/verify-version@master |  | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.4.0 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -55,7 +55,7 @@ jobs: | |||||||
|           script/release |           script/release | ||||||
|  |  | ||||||
|       - name: Upload release assets |       - name: Upload release assets | ||||||
|         uses: softprops/action-gh-release@v2.2.2 |         uses: softprops/action-gh-release@v2.0.4 | ||||||
|         with: |         with: | ||||||
|           files: | |           files: | | ||||||
|             dist/*.whl |             dist/*.whl | ||||||
| @@ -74,68 +74,10 @@ jobs: | |||||||
|           echo "home-assistant-frontend==$version" > ./requirements.txt |           echo "home-assistant-frontend==$version" > ./requirements.txt | ||||||
|  |  | ||||||
|       - name: Build wheels |       - name: Build wheels | ||||||
|         uses: home-assistant/wheels@2025.03.0 |         uses: home-assistant/wheels@2024.01.0 | ||||||
|         with: |         with: | ||||||
|           abi: cp313 |           abi: cp311 | ||||||
|           tag: musllinux_1_2 |           tag: musllinux_1_2 | ||||||
|           arch: amd64 |           arch: amd64 | ||||||
|           wheels-key: ${{ secrets.WHEELS_KEY }} |           wheels-key: ${{ secrets.WHEELS_KEY }} | ||||||
|           requirements: "requirements.txt" |           requirements: "requirements.txt" | ||||||
|  |  | ||||||
|   release-landing-page: |  | ||||||
|     name: Release landing-page frontend |  | ||||||
|     if: github.event.release.prerelease == false |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     permissions: |  | ||||||
|       contents: write # Required to upload release assets |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout the repository |  | ||||||
|         uses: actions/checkout@v4.2.2 |  | ||||||
|       - name: Setup Node |  | ||||||
|         uses: actions/setup-node@v4.4.0 |  | ||||||
|         with: |  | ||||||
|           node-version-file: ".nvmrc" |  | ||||||
|           cache: yarn |  | ||||||
|       - name: Install dependencies |  | ||||||
|         run: yarn install |  | ||||||
|       - name: Download Translations |  | ||||||
|         run: ./script/translations_download |  | ||||||
|         env: |  | ||||||
|           LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} |  | ||||||
|       - name: Build landing-page |  | ||||||
|         run: landing-page/script/build_landing_page |  | ||||||
|       - name: Tar folder |  | ||||||
|         run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist . |  | ||||||
|       - name: Upload release asset |  | ||||||
|         uses: softprops/action-gh-release@v2.2.2 |  | ||||||
|         with: |  | ||||||
|           files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz |  | ||||||
|  |  | ||||||
|   release-supervisor: |  | ||||||
|     name: Release supervisor frontend |  | ||||||
|     if: github.event.release.prerelease == false |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     permissions: |  | ||||||
|       contents: write # Required to upload release assets |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout the repository |  | ||||||
|         uses: actions/checkout@v4.2.2 |  | ||||||
|       - name: Setup Node |  | ||||||
|         uses: actions/setup-node@v4.4.0 |  | ||||||
|         with: |  | ||||||
|           node-version-file: ".nvmrc" |  | ||||||
|           cache: yarn |  | ||||||
|       - name: Install dependencies |  | ||||||
|         run: yarn install |  | ||||||
|       - name: Download Translations |  | ||||||
|         run: ./script/translations_download |  | ||||||
|         env: |  | ||||||
|           LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} |  | ||||||
|       - name: Build supervisor |  | ||||||
|         run: hassio/script/build_hassio |  | ||||||
|       - name: Tar folder |  | ||||||
|         run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build . |  | ||||||
|       - name: Upload release asset |  | ||||||
|         uses: softprops/action-gh-release@v2.2.2 |  | ||||||
|         with: |  | ||||||
|           files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,7 +10,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: 90 days stale policy |       - name: 90 days stale policy | ||||||
|         uses: actions/stale@v9.1.0 |         uses: actions/stale@v9.0.0 | ||||||
|         with: |         with: | ||||||
|           repo-token: ${{ secrets.GITHUB_TOKEN }} |           repo-token: ${{ secrets.GITHUB_TOKEN }} | ||||||
|           days-before-stale: 90 |           days-before-stale: 90 | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -13,7 +13,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout the repository |       - name: Checkout the repository | ||||||
|         uses: actions/checkout@v4.2.2 |         uses: actions/checkout@v4.1.2 | ||||||
|  |  | ||||||
|       - name: Upload Translations |       - name: Upload Translations | ||||||
|         run: | |         run: | | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -50,6 +50,3 @@ src/cast/dev_const.ts | |||||||
|  |  | ||||||
| # Jetbrains | # Jetbrains | ||||||
| /.idea/ | /.idea/ | ||||||
|  |  | ||||||
| # test coverage |  | ||||||
| test/coverage/ |  | ||||||
|   | |||||||
| @@ -1 +1,4 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | . "$(dirname -- "$0")/_/husky.sh" | ||||||
|  |  | ||||||
| yarn run lint-staged --relative --shell "/bin/bash" | yarn run lint-staged --relative --shell "/bin/bash" | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							| @@ -4,8 +4,6 @@ | |||||||
|     "esbenp.prettier-vscode", |     "esbenp.prettier-vscode", | ||||||
|     "runem.lit-plugin", |     "runem.lit-plugin", | ||||||
|     "github.vscode-pull-request-github", |     "github.vscode-pull-request-github", | ||||||
|     "eamodio.gitlens", |     "eamodio.gitlens" | ||||||
|     "vitest.explorer", |  | ||||||
|     "yeion7.styled-global-variables-autocomplete" |  | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										74
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										74
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,42 +1,6 @@ | |||||||
| { | { | ||||||
|   "version": "2.0.0", |   "version": "2.0.0", | ||||||
|   "tasks": [ |   "tasks": [ | ||||||
|     { |  | ||||||
|       "label": "Develop and serve Frontend", |  | ||||||
|       "type": "shell", |  | ||||||
|       "command": "script/develop_and_serve -c ${input:coreUrl}", |  | ||||||
|       // Sync changes here to other tasks until issue resolved |  | ||||||
|       // https://github.com/Microsoft/vscode/issues/61497 |  | ||||||
|       "problemMatcher": { |  | ||||||
|         "owner": "ha-build", |  | ||||||
|         "source": "ha-build", |  | ||||||
|         "fileLocation": "absolute", |  | ||||||
|         "severity": "error", |  | ||||||
|         "pattern": [ |  | ||||||
|           { |  | ||||||
|             "regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)", |  | ||||||
|             "severity": 1, |  | ||||||
|             "file": 2, |  | ||||||
|             "message": 3, |  | ||||||
|             "line": 4, |  | ||||||
|             "column": 5 |  | ||||||
|           } |  | ||||||
|         ], |  | ||||||
|         "background": { |  | ||||||
|           "activeOnStart": true, |  | ||||||
|           "beginsPattern": "Changes detected. Starting compilation", |  | ||||||
|           "endsPattern": "Build done @" |  | ||||||
|         } |  | ||||||
|       }, |  | ||||||
|       "isBackground": true, |  | ||||||
|       "group": { |  | ||||||
|         "kind": "build", |  | ||||||
|         "isDefault": true |  | ||||||
|       }, |  | ||||||
|       "runOptions": { |  | ||||||
|         "instanceLimit": 1 |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     { |     { | ||||||
|       "label": "Develop Frontend", |       "label": "Develop Frontend", | ||||||
|       "type": "gulp", |       "type": "gulp", | ||||||
| @@ -136,38 +100,6 @@ | |||||||
|         "instanceLimit": 1 |         "instanceLimit": 1 | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     { |  | ||||||
|       "label": "Develop Landing Page", |  | ||||||
|       "type": "gulp", |  | ||||||
|       "task": "develop-landing-page", |  | ||||||
|       "problemMatcher": { |  | ||||||
|         "owner": "ha-build", |  | ||||||
|         "source": "ha-build", |  | ||||||
|         "fileLocation": "absolute", |  | ||||||
|         "severity": "error", |  | ||||||
|         "pattern": [ |  | ||||||
|           { |  | ||||||
|             "regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)", |  | ||||||
|             "severity": 1, |  | ||||||
|             "file": 2, |  | ||||||
|             "message": 3, |  | ||||||
|             "line": 4, |  | ||||||
|             "column": 5 |  | ||||||
|           } |  | ||||||
|         ], |  | ||||||
|         "background": { |  | ||||||
|           "activeOnStart": true, |  | ||||||
|           "beginsPattern": "Changes detected. Starting compilation", |  | ||||||
|           "endsPattern": "Build done @" |  | ||||||
|         } |  | ||||||
|       }, |  | ||||||
|  |  | ||||||
|       "isBackground": true, |  | ||||||
|       "group": "build", |  | ||||||
|       "runOptions": { |  | ||||||
|         "instanceLimit": 1 |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     { |     { | ||||||
|       "label": "Develop Demo", |       "label": "Develop Demo", | ||||||
|       "type": "gulp", |       "type": "gulp", | ||||||
| @@ -277,12 +209,6 @@ | |||||||
|       "id": "supervisorToken", |       "id": "supervisorToken", | ||||||
|       "type": "promptString", |       "type": "promptString", | ||||||
|       "description": "The token for the Remote API proxy add-on" |       "description": "The token for the Remote API proxy add-on" | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       "id": "coreUrl", |  | ||||||
|       "type": "promptString", |  | ||||||
|       "description": "The URL of the Home Assistant Core instance", |  | ||||||
|       "default": "http://127.0.0.1:8123" |  | ||||||
|     } |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,22 +0,0 @@ | |||||||
| diff --git a/mwc-formfield-base.js b/mwc-formfield-base.js |  | ||||||
| index 7b763326d7d51835ad52646bfbc80fe21989abd3..f2baa8224e6d03df1fdb0b9fd03f5c6d77fc8747 100644 |  | ||||||
| --- a/mwc-formfield-base.js |  | ||||||
| +++ b/mwc-formfield-base.js |  | ||||||
| @@ -9,7 +9,7 @@ import { BaseElement } from '@material/mwc-base/base-element.js'; |  | ||||||
|  import { FormElement } from '@material/mwc-base/form-element.js'; |  | ||||||
|  import { observer } from '@material/mwc-base/observer.js'; |  | ||||||
|  import { html } from 'lit'; |  | ||||||
| -import { property, query, queryAssignedNodes } from 'lit/decorators.js'; |  | ||||||
| +import { property, query, queryAssignedElements } from 'lit/decorators.js'; |  | ||||||
|  import { classMap } from 'lit/directives/class-map.js'; |  | ||||||
|  export class FormfieldBase extends BaseElement { |  | ||||||
|      constructor() { |  | ||||||
| @@ -96,7 +96,7 @@ __decorate([ |  | ||||||
|      query('.mdc-form-field') |  | ||||||
|  ], FormfieldBase.prototype, "mdcRoot", void 0); |  | ||||||
|  __decorate([ |  | ||||||
| -    queryAssignedNodes('', true, '*') |  | ||||||
| +    queryAssignedElements({ slot: "", flatten: true, selector: "*" }) |  | ||||||
|  ], FormfieldBase.prototype, "slottedInputs", void 0); |  | ||||||
|  __decorate([ |  | ||||||
|      query('label') |  | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| diff --git a/mwc-list-base.js b/mwc-list-base.js |  | ||||||
| index 1ba95b6a01dcecea4d85b5cbbbcc3dfb04c40d5f..dced13fdb7929c490d6661b1bbe7e9f96dcd2285 100644 |  | ||||||
| --- a/mwc-list-base.js |  | ||||||
| +++ b/mwc-list-base.js |  | ||||||
| @@ -11,7 +11,7 @@ import { BaseElement } from '@material/mwc-base/base-element.js'; |  | ||||||
|  import { observer } from '@material/mwc-base/observer.js'; |  | ||||||
|  import { deepActiveElementPath, doesElementContainFocus, isNodeElement } from '@material/mwc-base/utils.js'; |  | ||||||
|  import { html } from 'lit'; |  | ||||||
| -import { property, query, queryAssignedNodes } from 'lit/decorators.js'; |  | ||||||
| +import { property, query, queryAssignedElements } from 'lit/decorators.js'; |  | ||||||
|  import { ifDefined } from 'lit/directives/if-defined.js'; |  | ||||||
|  import MDCListFoundation, { isIndexSet } from './mwc-list-foundation.js'; |  | ||||||
|  export { createSetFromIndex, isEventMulti, isIndexSet } from './mwc-list-foundation.js'; |  | ||||||
| @@ -425,10 +425,10 @@ __decorate([ |  | ||||||
|      query('.mdc-deprecated-list') |  | ||||||
|  ], ListBase.prototype, "mdcRoot", void 0); |  | ||||||
|  __decorate([ |  | ||||||
| -    queryAssignedNodes('', true, '*') |  | ||||||
| +    queryAssignedElements({ flatten: true, selector: "*" }) |  | ||||||
|  ], ListBase.prototype, "assignedElements", void 0); |  | ||||||
|  __decorate([ |  | ||||||
| -    queryAssignedNodes('', true, '[tabindex="0"]') |  | ||||||
| +    queryAssignedElements({ flatten: true, selector: '[tabindex="0"]' }) |  | ||||||
|  ], ListBase.prototype, "tabbableElements", void 0); |  | ||||||
|  __decorate([ |  | ||||||
|      property({ type: Boolean }), |  | ||||||
							
								
								
									
										34
									
								
								.yarn/patches/@polymer/polymer/pr-5569.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								.yarn/patches/@polymer/polymer/pr-5569.patch
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | diff --git a/lib/legacy/class.js b/lib/legacy/class.js | ||||||
|  | index aee2511be1cd9bf900ee552bc98190c1631c57c0..f2f499d68bf52034cac9c28307c99e8ce6b8417d 100644 | ||||||
|  | --- a/lib/legacy/class.js | ||||||
|  | +++ b/lib/legacy/class.js | ||||||
|  | @@ -304,17 +304,23 @@ function GenerateClassFromInfo(info, Base, behaviors) { | ||||||
|  |        // only proceed if the generated class' prototype has not been registered. | ||||||
|  |        const generatedProto = PolymerGenerated.prototype; | ||||||
|  |        if (!generatedProto.hasOwnProperty(JSCompiler_renameProperty('__hasRegisterFinished', generatedProto))) { | ||||||
|  | -        generatedProto.__hasRegisterFinished = true; | ||||||
|  | +        // make sure legacy lifecycle is called on the *element*'s prototype | ||||||
|  | +        // and not the generated class prototype; if the element has been | ||||||
|  | +        // extended, these are *not* the same. | ||||||
|  | +        const proto = Object.getPrototypeOf(this); | ||||||
|  | +        // Only set flag when generated prototype itself is registered, | ||||||
|  | +        // as this element may be extended from, and needs to run `registered` | ||||||
|  | +        // on all behaviors on the subclass as well. | ||||||
|  | +        if (proto === generatedProto) { | ||||||
|  | +          generatedProto.__hasRegisterFinished = true; | ||||||
|  | +        } | ||||||
|  |          // ensure superclass is registered first. | ||||||
|  |          super._registered(); | ||||||
|  |          // copy properties onto the generated class lazily if we're optimizing, | ||||||
|  | -        if (legacyOptimizations) { | ||||||
|  | +        if (legacyOptimizations && !Object.hasOwnProperty(generatedProto, '__hasCopiedProperties')) { | ||||||
|  | +          generatedProto.__hasCopiedProperties = true; | ||||||
|  |            copyPropertiesToProto(generatedProto); | ||||||
|  |          } | ||||||
|  | -        // make sure legacy lifecycle is called on the *element*'s prototype | ||||||
|  | -        // and not the generated class prototype; if the element has been | ||||||
|  | -        // extended, these are *not* the same. | ||||||
|  | -        const proto = Object.getPrototypeOf(this); | ||||||
|  |          let list = lifecycle.beforeRegister; | ||||||
|  |          if (list) { | ||||||
|  |            for (let i=0; i < list.length; i++) { | ||||||
| @@ -1,7 +1,16 @@ | |||||||
| diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
 | diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
 | ||||||
| index 8b5e49b011713c8859c669069fbe85ce53974e1d..6a0afc92787157b8a31c38cc5f67dfa526090a00 100644
 | index 93ba17509e2e8583ab241fea6845fbe714c584a2..de0651ddb5dced30d36f7d764da0dd0b441f523f 100644
 | ||||||
| --- a/modular/sortable.core.esm.js
 | --- a/modular/sortable.core.esm.js
 | ||||||
| +++ b/modular/sortable.core.esm.js
 | +++ b/modular/sortable.core.esm.js
 | ||||||
|  | @@ -1461,7 +1461,7 @@ Sortable.prototype = /** @lends Sortable.prototype */{
 | ||||||
|  |            } | ||||||
|  |            target = parent; // store last element | ||||||
|  |          } | ||||||
|  | -        /* jshint boss:true */ while (parent = parent.parentNode);
 | ||||||
|  | +        /* jshint boss:true */ while (parent = parent.parentNode || parent.getRootNode().host);
 | ||||||
|  |        } | ||||||
|  |        _unhideGhostForTarget(); | ||||||
|  |      } | ||||||
| @@ -1781,11 +1781,16 @@ Sortable.prototype = /** @lends Sortable.prototype */{
 | @@ -1781,11 +1781,16 @@ Sortable.prototype = /** @lends Sortable.prototype */{
 | ||||||
|          } |          } | ||||||
|          if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) { |          if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) { | ||||||
| @@ -24,7 +33,7 @@ index 8b5e49b011713c8859c669069fbe85ce53974e1d..6a0afc92787157b8a31c38cc5f67dfa5 | |||||||
|            } |            } | ||||||
|            parentEl = el; // actualization |            parentEl = el; // actualization | ||||||
|   |   | ||||||
| @@ -1802,7 +1807,12 @@ Sortable.prototype = /** @lends Sortable.prototype */{
 | @@ -1802,7 +1807,13 @@ Sortable.prototype = /** @lends Sortable.prototype */{
 | ||||||
|          targetRect = getRect(target); |          targetRect = getRect(target); | ||||||
|          if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, false) !== false) { |          if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, false) !== false) { | ||||||
|            capture(); |            capture(); | ||||||
| @@ -35,10 +44,11 @@ index 8b5e49b011713c8859c669069fbe85ce53974e1d..6a0afc92787157b8a31c38cc5f67dfa5 | |||||||
| +          catch(err) {
 | +          catch(err) {
 | ||||||
| +            return completed(false);
 | +            return completed(false);
 | ||||||
| +          }
 | +          }
 | ||||||
|  | +          
 | ||||||
|            parentEl = el; // actualization |            parentEl = el; // actualization | ||||||
|   |   | ||||||
|            changed(); |            changed(); | ||||||
| @@ -1849,10 +1859,15 @@ Sortable.prototype = /** @lends Sortable.prototype */{
 | @@ -1849,12 +1860,17 @@ Sortable.prototype = /** @lends Sortable.prototype */{
 | ||||||
|            _silent = true; |            _silent = true; | ||||||
|            setTimeout(_unsilent, 30); |            setTimeout(_unsilent, 30); | ||||||
|            capture(); |            capture(); | ||||||
| @@ -46,6 +56,8 @@ index 8b5e49b011713c8859c669069fbe85ce53974e1d..6a0afc92787157b8a31c38cc5f67dfa5 | |||||||
| -            el.appendChild(dragEl);
 | -            el.appendChild(dragEl);
 | ||||||
| -          } else {
 | -          } else {
 | ||||||
| -            target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
 | -            target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
 | ||||||
|  | -          }
 | ||||||
|  |   | ||||||
| +          try {
 | +          try {
 | ||||||
| +            if (after && !nextSibling) {
 | +            if (after && !nextSibling) {
 | ||||||
| +              el.appendChild(dragEl);
 | +              el.appendChild(dragEl);
 | ||||||
| @@ -55,6 +67,7 @@ index 8b5e49b011713c8859c669069fbe85ce53974e1d..6a0afc92787157b8a31c38cc5f67dfa5 | |||||||
| +          }
 | +          }
 | ||||||
| +          catch(err) {
 | +          catch(err) {
 | ||||||
| +            return completed(false);
 | +            return completed(false);
 | ||||||
|            } | +          }
 | ||||||
|   |  | ||||||
|            // Undo chrome's scroll adjustment (has no effect on other browsers) |            // Undo chrome's scroll adjustment (has no effect on other browsers) | ||||||
|  |            if (scrolledPastTop) { | ||||||
|  |              scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop); | ||||||
| @@ -1,55 +0,0 @@ | |||||||
| diff --git a/build/inject-manifest.js b/build/inject-manifest.js |  | ||||||
| index 60e3d2bb51c11a19fbbedbad65e101082ec41c36..fed6026630f43f86e25446383982cf6fb694313b 100644 |  | ||||||
| --- a/build/inject-manifest.js |  | ||||||
| +++ b/build/inject-manifest.js |  | ||||||
| @@ -104,7 +104,7 @@ async function injectManifest(config) { |  | ||||||
|              replaceString: manifestString, |  | ||||||
|              searchString: options.injectionPoint, |  | ||||||
|          }); |  | ||||||
| -        filesToWrite[options.swDest] = source; |  | ||||||
| +        filesToWrite[options.swDest] = source.replace(url, encodeURI(upath_1.default.basename(destPath))); |  | ||||||
|          filesToWrite[destPath] = map; |  | ||||||
|      } |  | ||||||
|      else { |  | ||||||
| diff --git a/build/lib/translate-url-to-sourcemap-paths.js b/build/lib/translate-url-to-sourcemap-paths.js |  | ||||||
| index 3220c5474eeac6e8a56ca9b2ac2bd9be48529e43..5f003879a904d4840529a42dd056d288fd213771 100644 |  | ||||||
| --- a/build/lib/translate-url-to-sourcemap-paths.js |  | ||||||
| +++ b/build/lib/translate-url-to-sourcemap-paths.js |  | ||||||
| @@ -22,7 +22,7 @@ function translateURLToSourcemapPaths(url, swSrc, swDest) { |  | ||||||
|          const possibleSrcPath = upath_1.default.resolve(upath_1.default.dirname(swSrc), url); |  | ||||||
|          if (fs_extra_1.default.existsSync(possibleSrcPath)) { |  | ||||||
|              srcPath = possibleSrcPath; |  | ||||||
| -            destPath = upath_1.default.resolve(upath_1.default.dirname(swDest), url); |  | ||||||
| +            destPath = `${swDest}.map`; |  | ||||||
|          } |  | ||||||
|          else { |  | ||||||
|              warning = `${errors_1.errors['cant-find-sourcemap']} ${possibleSrcPath}`; |  | ||||||
| diff --git a/src/inject-manifest.ts b/src/inject-manifest.ts |  | ||||||
| index 8795ddcaa77aea7b0356417e4bc4b19e2b3f860c..fcdc68342d9ac53936c9ed40a9ccfc2f5070cad3 100644 |  | ||||||
| --- a/src/inject-manifest.ts |  | ||||||
| +++ b/src/inject-manifest.ts |  | ||||||
| @@ -129,7 +129,10 @@ export async function injectManifest( |  | ||||||
|        searchString: options.injectionPoint!, |  | ||||||
|      }); |  | ||||||
|   |  | ||||||
| -    filesToWrite[options.swDest] = source; |  | ||||||
| +    filesToWrite[options.swDest] = source.replace( |  | ||||||
| +      url!, |  | ||||||
| +      encodeURI(upath.basename(destPath)), |  | ||||||
| +    ); |  | ||||||
|      filesToWrite[destPath] = map; |  | ||||||
|    } else { |  | ||||||
|      // If there's no sourcemap associated with swSrc, a simple string |  | ||||||
| diff --git a/src/lib/translate-url-to-sourcemap-paths.ts b/src/lib/translate-url-to-sourcemap-paths.ts |  | ||||||
| index 072eac40d4ef5d095a01cb7f7e392a9e034853bd..f0bbe69e88ef3a415de18a7e9cb264daea273d71 100644 |  | ||||||
| --- a/src/lib/translate-url-to-sourcemap-paths.ts |  | ||||||
| +++ b/src/lib/translate-url-to-sourcemap-paths.ts |  | ||||||
| @@ -28,7 +28,7 @@ export function translateURLToSourcemapPaths( |  | ||||||
|      const possibleSrcPath = upath.resolve(upath.dirname(swSrc), url); |  | ||||||
|      if (fse.existsSync(possibleSrcPath)) { |  | ||||||
|        srcPath = possibleSrcPath; |  | ||||||
| -      destPath = upath.resolve(upath.dirname(swDest), url); |  | ||||||
| +      destPath = `${swDest}.map`; |  | ||||||
|      } else { |  | ||||||
|        warning = `${errors['cant-find-sourcemap']} ${possibleSrcPath}`; |  | ||||||
|      } |  | ||||||
							
								
								
									
										893
									
								
								.yarn/releases/yarn-4.1.1.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										893
									
								
								.yarn/releases/yarn-4.1.1.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										948
									
								
								.yarn/releases/yarn-4.9.1.cjs
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										948
									
								
								.yarn/releases/yarn-4.9.1.cjs
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -6,4 +6,4 @@ enableGlobalCache: false | |||||||
|  |  | ||||||
| nodeLinker: node-modules | nodeLinker: node-modules | ||||||
|  |  | ||||||
| yarnPath: .yarn/releases/yarn-4.9.1.cjs | yarnPath: .yarn/releases/yarn-4.1.1.cjs | ||||||
|   | |||||||
| @@ -27,5 +27,3 @@ A complete guide can be found at the following [link](https://www.home-assistant | |||||||
| Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects. | Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects. | ||||||
|  |  | ||||||
| We use [BrowserStack](https://www.browserstack.com) to test Home Assistant on a large variety of devices. | We use [BrowserStack](https://www.browserstack.com) to test Home Assistant on a large variety of devices. | ||||||
|  |  | ||||||
| [](https://www.openhomefoundation.org/) |  | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								build-scripts/.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								build-scripts/.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | { | ||||||
|  |   "extends": "../.eslintrc.json", | ||||||
|  |   "rules": { | ||||||
|  |     "no-console": "off", | ||||||
|  |     "import/no-extraneous-dependencies": "off", | ||||||
|  |     "import/extensions": "off", | ||||||
|  |     "import/no-dynamic-require": "off", | ||||||
|  |     "global-require": "off", | ||||||
|  |     "@typescript-eslint/no-var-requires": "off", | ||||||
|  |     "prefer-arrow-callback": "off" | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -15,7 +15,7 @@ The Home Assistant build pipeline contains various steps to prepare a build. | |||||||
|  |  | ||||||
| Currently in Home Assistant we use a bundler to convert TypeScript, CSS and JSON files to JavaScript files that the browser understands. | Currently in Home Assistant we use a bundler to convert TypeScript, CSS and JSON files to JavaScript files that the browser understands. | ||||||
|  |  | ||||||
| We currently rely on Webpack. Both of these programs bundle the converted files in both production and development. | We currently rely on Webpack but also have experimental Rollup support. Both of these programs bundle the converted files in both production and development. | ||||||
|  |  | ||||||
| For development, bundling is optional. We just want to get the right files in the browser. | For development, bundling is optional. We just want to get the right files in the browser. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,82 +1,28 @@ | |||||||
| import defineProvider from "@babel/helper-define-polyfill-provider"; | import defineProvider from "@babel/helper-define-polyfill-provider"; | ||||||
| import { join } from "node:path"; |  | ||||||
| import paths from "../paths.cjs"; |  | ||||||
|  |  | ||||||
| const POLYFILL_DIR = join(paths.root_dir, "src/resources/polyfills"); |  | ||||||
|  |  | ||||||
| // List of polyfill keys with supported browser targets for the functionality | // List of polyfill keys with supported browser targets for the functionality | ||||||
| const polyfillSupport = { | const PolyfillSupport = { | ||||||
|   // Note states and shadowRoot properties should be supported. |   fetch: { | ||||||
|   "element-internals": { |     android: 42, | ||||||
|     android: 90, |     chrome: 42, | ||||||
|     chrome: 90, |     edge: 14, | ||||||
|     edge: 90, |     firefox: 39, | ||||||
|     firefox: 126, |  | ||||||
|     ios: 17.4, |  | ||||||
|     opera: 76, |  | ||||||
|     opera_mobile: 64, |  | ||||||
|     safari: 17.4, |  | ||||||
|     samsung: 15.0, |  | ||||||
|   }, |  | ||||||
|   "element-getattributenames": { |  | ||||||
|     android: 61, |  | ||||||
|     chrome: 61, |  | ||||||
|     edge: 18, |  | ||||||
|     firefox: 45, |  | ||||||
|     ios: 10.3, |     ios: 10.3, | ||||||
|     opera: 48, |     opera: 29, | ||||||
|     opera_mobile: 45, |     opera_mobile: 29, | ||||||
|     safari: 10.1, |     safari: 10.1, | ||||||
|     samsung: 8.0, |     samsung: 4.0, | ||||||
|   }, |   }, | ||||||
|   "element-toggleattribute": { |   proxy: { | ||||||
|     android: 69, |     android: 49, | ||||||
|     chrome: 69, |     chrome: 49, | ||||||
|     edge: 18, |     edge: 12, | ||||||
|     firefox: 63, |     firefox: 18, | ||||||
|     ios: 12.0, |     ios: 10.0, | ||||||
|     opera: 56, |     opera: 36, | ||||||
|     opera_mobile: 48, |     opera_mobile: 36, | ||||||
|     safari: 12.0, |     safari: 10.0, | ||||||
|     samsung: 10.0, |     samsung: 5.0, | ||||||
|   }, |  | ||||||
|   // FormatJS polyfill detects fix for https://bugs.chromium.org/p/v8/issues/detail?id=10682, |  | ||||||
|   // so adjusted to several months after that was marked fixed |  | ||||||
|   "intl-getcanonicallocales": { |  | ||||||
|     android: 90, |  | ||||||
|     chrome: 90, |  | ||||||
|     edge: 90, |  | ||||||
|     firefox: 48, |  | ||||||
|     ios: 10.3, |  | ||||||
|     opera: 76, |  | ||||||
|     opera_mobile: 64, |  | ||||||
|     safari: 10.1, |  | ||||||
|     samsung: 15.0, |  | ||||||
|   }, |  | ||||||
|   "intl-locale": { |  | ||||||
|     android: 74, |  | ||||||
|     chrome: 74, |  | ||||||
|     edge: 79, |  | ||||||
|     firefox: 75, |  | ||||||
|     ios: 14.0, |  | ||||||
|     opera: 62, |  | ||||||
|     opera_mobile: 53, |  | ||||||
|     safari: 14.0, |  | ||||||
|     samsung: 11.0, |  | ||||||
|   }, |  | ||||||
|   "intl-other": { |  | ||||||
|     // Not specified (i.e. always try polyfill) since compatibility depends on supported locales |  | ||||||
|   }, |  | ||||||
|   "resize-observer": { |  | ||||||
|     android: 64, |  | ||||||
|     chrome: 64, |  | ||||||
|     edge: 79, |  | ||||||
|     firefox: 69, |  | ||||||
|     ios: 13.4, |  | ||||||
|     opera: 51, |  | ||||||
|     opera_mobile: 47, |  | ||||||
|     safari: 13.1, |  | ||||||
|     samsung: 9.0, |  | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -84,49 +30,11 @@ const polyfillSupport = { | |||||||
| // corresponding polyfill key and actual module to import | // corresponding polyfill key and actual module to import | ||||||
| const polyfillMap = { | const polyfillMap = { | ||||||
|   global: { |   global: { | ||||||
|     ResizeObserver: { |     Proxy: { key: "proxy", module: "proxy-polyfill" }, | ||||||
|       key: "resize-observer", |     fetch: { key: "fetch", module: "unfetch/polyfill" }, | ||||||
|       module: join(POLYFILL_DIR, "resize-observer.ts"), |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   instance: { |  | ||||||
|     attachInternals: { |  | ||||||
|       key: "element-internals", |  | ||||||
|       module: "element-internals-polyfill", |  | ||||||
|     }, |  | ||||||
|     ...Object.fromEntries( |  | ||||||
|       ["getAttributeNames", "toggleAttribute"].map((prop) => { |  | ||||||
|         const key = `element-${prop.toLowerCase()}`; |  | ||||||
|         return [prop, { key, module: join(POLYFILL_DIR, `${key}.ts`) }]; |  | ||||||
|       }) |  | ||||||
|     ), |  | ||||||
|   }, |  | ||||||
|   static: { |  | ||||||
|     Intl: { |  | ||||||
|       getCanonicalLocales: { |  | ||||||
|         key: "intl-getcanonicallocales", |  | ||||||
|         module: join(POLYFILL_DIR, "intl-polyfill.ts"), |  | ||||||
|       }, |  | ||||||
|       Locale: { |  | ||||||
|         key: "intl-locale", |  | ||||||
|         module: join(POLYFILL_DIR, "intl-polyfill.ts"), |  | ||||||
|       }, |  | ||||||
|       ...Object.fromEntries( |  | ||||||
|         [ |  | ||||||
|           "DateTimeFormat", |  | ||||||
|           "DurationFormat", |  | ||||||
|           "DisplayNames", |  | ||||||
|           "ListFormat", |  | ||||||
|           "NumberFormat", |  | ||||||
|           "PluralRules", |  | ||||||
|           "RelativeTimeFormat", |  | ||||||
|         ].map((obj) => [ |  | ||||||
|           obj, |  | ||||||
|           { key: "intl-other", module: join(POLYFILL_DIR, "intl-polyfill.ts") }, |  | ||||||
|         ]) |  | ||||||
|       ), |  | ||||||
|     }, |  | ||||||
|   }, |   }, | ||||||
|  |   instance: {}, | ||||||
|  |   static: {}, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // Create plugin using the same factory as for CoreJS | // Create plugin using the same factory as for CoreJS | ||||||
| @@ -134,16 +42,14 @@ export default defineProvider( | |||||||
|   ({ createMetaResolver, debug, shouldInjectPolyfill }) => { |   ({ createMetaResolver, debug, shouldInjectPolyfill }) => { | ||||||
|     const resolvePolyfill = createMetaResolver(polyfillMap); |     const resolvePolyfill = createMetaResolver(polyfillMap); | ||||||
|     return { |     return { | ||||||
|       name: "custom-polyfill", |       name: "HA Custom", | ||||||
|       polyfills: polyfillSupport, |       polyfills: PolyfillSupport, | ||||||
|       usageGlobal(meta, utils) { |       usageGlobal(meta, utils) { | ||||||
|         const polyfill = resolvePolyfill(meta); |         const polyfill = resolvePolyfill(meta); | ||||||
|         if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) { |         if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) { | ||||||
|           debug(polyfill.desc.key); |           debug(polyfill.desc.key); | ||||||
|           utils.injectGlobalImport(polyfill.desc.module); |           utils.injectGlobalImport(polyfill.desc.module); | ||||||
|           return true; |  | ||||||
|         } |         } | ||||||
|         return false; |  | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -3,8 +3,6 @@ const env = require("./env.cjs"); | |||||||
| const paths = require("./paths.cjs"); | const paths = require("./paths.cjs"); | ||||||
| const { dependencies } = require("../package.json"); | const { dependencies } = require("../package.json"); | ||||||
|  |  | ||||||
| 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 | ||||||
| module.exports.sourceMapURL = () => { | module.exports.sourceMapURL = () => { | ||||||
| @@ -18,35 +16,41 @@ module.exports.sourceMapURL = () => { | |||||||
| module.exports.ignorePackages = () => []; | module.exports.ignorePackages = () => []; | ||||||
|  |  | ||||||
| // Files from NPM packages that we should replace with empty file | // Files from NPM packages that we should replace with empty file | ||||||
| module.exports.emptyPackages = ({ isHassioBuild }) => | module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) => | ||||||
|   [ |   [ | ||||||
|  |     // Contains all color definitions for all material color sets. | ||||||
|  |     // We don't use it | ||||||
|  |     require.resolve("@polymer/paper-styles/color.js"), | ||||||
|  |     require.resolve("@polymer/paper-styles/default-theme.js"), | ||||||
|  |     // Loads stuff from a CDN | ||||||
|  |     require.resolve("@polymer/font-roboto/roboto.js"), | ||||||
|     require.resolve("@vaadin/vaadin-material-styles/typography.js"), |     require.resolve("@vaadin/vaadin-material-styles/typography.js"), | ||||||
|     require.resolve("@vaadin/vaadin-material-styles/font-icons.js"), |     require.resolve("@vaadin/vaadin-material-styles/font-icons.js"), | ||||||
|  |     // Compatibility not needed for latest builds | ||||||
|  |     latestBuild && | ||||||
|  |       // wrapped in require.resolve so it blows up if file no longer exists | ||||||
|  |       require.resolve( | ||||||
|  |         path.resolve(paths.polymer_dir, "src/resources/compatibility.ts") | ||||||
|  |       ), | ||||||
|     // 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( |       require.resolve( | ||||||
|         path.resolve(paths.root_dir, "src/components/ha-icon.ts") |         path.resolve(paths.polymer_dir, "src/components/ha-icon.ts") | ||||||
|       ), |       ), | ||||||
|     isHassioBuild && |     isHassioBuild && | ||||||
|       require.resolve( |       require.resolve( | ||||||
|         path.resolve(paths.root_dir, "src/components/ha-icon-picker.ts") |         path.resolve(paths.polymer_dir, "src/components/ha-icon-picker.ts") | ||||||
|       ), |       ), | ||||||
|   ].filter(Boolean); |   ].filter(Boolean); | ||||||
|  |  | ||||||
| module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ | module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ | ||||||
|   __DEV__: !isProdBuild, |   __DEV__: !isProdBuild, | ||||||
|   __BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"), |   __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), | ||||||
|   __VERSION__: JSON.stringify(env.version()), |   __VERSION__: JSON.stringify(env.version()), | ||||||
|   __DEMO__: false, |   __DEMO__: false, | ||||||
|   __SUPERVISOR__: false, |   __SUPERVISOR__: false, | ||||||
|   __BACKWARDS_COMPAT__: false, |   __BACKWARDS_COMPAT__: false, | ||||||
|   __STATIC_PATH__: "/static/", |   __STATIC_PATH__: "/static/", | ||||||
|   __HASS_URL__: `\`${ |  | ||||||
|     "HASS_URL" in process.env |  | ||||||
|       ? process.env.HASS_URL |  | ||||||
|       : // eslint-disable-next-line no-template-curly-in-string |  | ||||||
|         "${location.protocol}//${location.host}" |  | ||||||
|   }\``, |  | ||||||
|   "process.env.NODE_ENV": JSON.stringify( |   "process.env.NODE_ENV": JSON.stringify( | ||||||
|     isProdBuild ? "production" : "development" |     isProdBuild ? "production" : "development" | ||||||
|   ), |   ), | ||||||
| @@ -73,25 +77,7 @@ module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({ | |||||||
|   sourceMap: !isTestBuild, |   sourceMap: !isTestBuild, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| /** @type {import('@rspack/core').SwcLoaderOptions} */ | module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({ | ||||||
| module.exports.swcOptions = () => ({ |  | ||||||
|   jsc: { |  | ||||||
|     loose: true, |  | ||||||
|     externalHelpers: true, |  | ||||||
|     target: "ES2021", |  | ||||||
|     parser: { |  | ||||||
|       syntax: "typescript", |  | ||||||
|       decorators: true, |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| module.exports.babelOptions = ({ |  | ||||||
|   latestBuild, |  | ||||||
|   isProdBuild, |  | ||||||
|   isTestBuild, |  | ||||||
|   sw, |  | ||||||
| }) => ({ |  | ||||||
|   babelrc: false, |   babelrc: false, | ||||||
|   compact: false, |   compact: false, | ||||||
|   assumptions: { |   assumptions: { | ||||||
| @@ -99,26 +85,37 @@ module.exports.babelOptions = ({ | |||||||
|     setPublicClassFields: true, |     setPublicClassFields: true, | ||||||
|     setSpreadProperties: true, |     setSpreadProperties: true, | ||||||
|   }, |   }, | ||||||
|   browserslistEnv: latestBuild ? "modern" : `legacy${sw ? "-sw" : ""}`, |   browserslistEnv: latestBuild ? "modern" : "legacy", | ||||||
|   presets: [ |   presets: [ | ||||||
|     [ |     [ | ||||||
|       "@babel/preset-env", |       "@babel/preset-env", | ||||||
|       { |       { | ||||||
|         useBuiltIns: "usage", |         useBuiltIns: latestBuild ? false : "usage", | ||||||
|         corejs: dependencies["core-js"], |         corejs: latestBuild ? false : dependencies["core-js"], | ||||||
|         bugfixes: true, |         bugfixes: true, | ||||||
|         shippedProposals: true, |         shippedProposals: true, | ||||||
|       }, |       }, | ||||||
|     ], |     ], | ||||||
|  |     "@babel/preset-typescript", | ||||||
|   ], |   ], | ||||||
|   plugins: [ |   plugins: [ | ||||||
|     [ |     [ | ||||||
|       path.join(BABEL_PLUGINS, "inline-constants-plugin.cjs"), |       path.resolve( | ||||||
|  |         paths.polymer_dir, | ||||||
|  |         "build-scripts/babel-plugins/inline-constants-plugin.cjs" | ||||||
|  |       ), | ||||||
|       { |       { | ||||||
|         modules: ["@mdi/js"], |         modules: ["@mdi/js"], | ||||||
|         ignoreModuleNotFound: true, |         ignoreModuleNotFound: true, | ||||||
|       }, |       }, | ||||||
|     ], |     ], | ||||||
|  |     [ | ||||||
|  |       path.resolve( | ||||||
|  |         paths.polymer_dir, | ||||||
|  |         "build-scripts/babel-plugins/custom-polyfill-plugin.js" | ||||||
|  |       ), | ||||||
|  |       { method: "usage-global" }, | ||||||
|  |     ], | ||||||
|     // Minify template literals for production |     // Minify template literals for production | ||||||
|     isProdBuild && [ |     isProdBuild && [ | ||||||
|       "template-html-minifier", |       "template-html-minifier", | ||||||
| @@ -146,36 +143,16 @@ module.exports.babelOptions = ({ | |||||||
|       "@babel/plugin-transform-runtime", |       "@babel/plugin-transform-runtime", | ||||||
|       { version: dependencies["@babel/runtime"] }, |       { version: dependencies["@babel/runtime"] }, | ||||||
|     ], |     ], | ||||||
|     "@babel/plugin-transform-class-properties", |     // Support  some proposals still in TC39 process | ||||||
|     "@babel/plugin-transform-private-methods", |     ["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], | ||||||
|   ].filter(Boolean), |   ].filter(Boolean), | ||||||
|   exclude: [ |   exclude: [ | ||||||
|     // \\ for Windows, / for Mac OS and Linux |     // \\ for Windows, / for Mac OS and Linux | ||||||
|     /node_modules[\\/]core-js/, |     /node_modules[\\/]core-js/, | ||||||
|  |     /node_modules[\\/]webpack[\\/]buildin/, | ||||||
|   ], |   ], | ||||||
|   sourceMaps: !isTestBuild, |   sourceMaps: !isTestBuild, | ||||||
|   overrides: [ |   overrides: [ | ||||||
|     { |  | ||||||
|       // Add plugin to inject various polyfills, excluding the polyfills |  | ||||||
|       // themselves to prevent self-injection. |  | ||||||
|       plugins: [ |  | ||||||
|         [ |  | ||||||
|           path.join(BABEL_PLUGINS, "custom-polyfill-plugin.js"), |  | ||||||
|           { method: "usage-global" }, |  | ||||||
|         ], |  | ||||||
|       ], |  | ||||||
|       exclude: [ |  | ||||||
|         path.join(paths.root_dir, "src/resources/polyfills"), |  | ||||||
|         ...[ |  | ||||||
|           "@formatjs/(?:ecma402-abstract|intl-\\w+)", |  | ||||||
|           "@lit-labs/virtualizer/polyfills", |  | ||||||
|           "@webcomponents/scoped-custom-element-registry", |  | ||||||
|           "element-internals-polyfill", |  | ||||||
|           "proxy-polyfill", |  | ||||||
|           "unfetch", |  | ||||||
|         ].map((p) => new RegExp(`/node_modules/${p}/`)), |  | ||||||
|       ], |  | ||||||
|     }, |  | ||||||
|     { |     { | ||||||
|       // Use unambiguous for dependencies so that require() is correctly injected into CommonJS files |       // Use unambiguous for dependencies so that require() is correctly injected into CommonJS files | ||||||
|       // Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills |       // Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills | ||||||
| @@ -183,7 +160,6 @@ module.exports.babelOptions = ({ | |||||||
|       include: /\/node_modules\//, |       include: /\/node_modules\//, | ||||||
|       exclude: [ |       exclude: [ | ||||||
|         "element-internals-polyfill", |         "element-internals-polyfill", | ||||||
|         "@shoelace-style", |  | ||||||
|         "@?lit(?:-labs|-element|-html)?", |         "@?lit(?:-labs|-element|-html)?", | ||||||
|       ].map((p) => new RegExp(`/node_modules/${p}/`)), |       ].map((p) => new RegExp(`/node_modules/${p}/`)), | ||||||
|     }, |     }, | ||||||
| @@ -226,12 +202,7 @@ module.exports.config = { | |||||||
|     return { |     return { | ||||||
|       name: "frontend" + nameSuffix(latestBuild), |       name: "frontend" + nameSuffix(latestBuild), | ||||||
|       entry: { |       entry: { | ||||||
|         "service-worker": !latestBuild |         service_worker: "./src/entrypoints/service_worker.ts", | ||||||
|           ? { |  | ||||||
|               import: "./src/entrypoints/service-worker.ts", |  | ||||||
|               layer: "sw", |  | ||||||
|             } |  | ||||||
|           : "./src/entrypoints/service-worker.ts", |  | ||||||
|         app: "./src/entrypoints/app.ts", |         app: "./src/entrypoints/app.ts", | ||||||
|         authorize: "./src/entrypoints/authorize.ts", |         authorize: "./src/entrypoints/authorize.ts", | ||||||
|         onboarding: "./src/entrypoints/onboarding.ts", |         onboarding: "./src/entrypoints/onboarding.ts", | ||||||
| @@ -327,17 +298,4 @@ module.exports.config = { | |||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   landingPage({ isProdBuild, latestBuild }) { |  | ||||||
|     return { |  | ||||||
|       name: "landing-page" + nameSuffix(latestBuild), |  | ||||||
|       entry: { |  | ||||||
|         entrypoint: path.resolve(paths.landingPage_dir, "src/entrypoint.js"), |  | ||||||
|       }, |  | ||||||
|       outputPath: outputPath(paths.landingPage_output_root, latestBuild), |  | ||||||
|       publicPath: publicPath(latestBuild), |  | ||||||
|       isProdBuild, |  | ||||||
|       latestBuild, |  | ||||||
|     }; |  | ||||||
|   }, |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -2,33 +2,34 @@ const fs = require("fs"); | |||||||
| const path = require("path"); | const path = require("path"); | ||||||
| const paths = require("./paths.cjs"); | const paths = require("./paths.cjs"); | ||||||
|  |  | ||||||
| const isTrue = (value) => value === "1" || value?.toLowerCase() === "true"; |  | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|  |   useRollup() { | ||||||
|  |     return process.env.ROLLUP === "1"; | ||||||
|  |   }, | ||||||
|  |   useWDS() { | ||||||
|  |     return process.env.WDS === "1"; | ||||||
|  |   }, | ||||||
|   isProdBuild() { |   isProdBuild() { | ||||||
|     return ( |     return ( | ||||||
|       process.env.NODE_ENV === "production" || module.exports.isStatsBuild() |       process.env.NODE_ENV === "production" || module.exports.isStatsBuild() | ||||||
|     ); |     ); | ||||||
|   }, |   }, | ||||||
|   isStatsBuild() { |   isStatsBuild() { | ||||||
|     return isTrue(process.env.STATS); |     return process.env.STATS === "1"; | ||||||
|   }, |   }, | ||||||
|   isTestBuild() { |   isTestBuild() { | ||||||
|     return isTrue(process.env.IS_TEST); |     return process.env.IS_TEST === "true"; | ||||||
|   }, |   }, | ||||||
|   isNetlify() { |   isNetlify() { | ||||||
|     return isTrue(process.env.NETLIFY); |     return process.env.NETLIFY === "true"; | ||||||
|   }, |   }, | ||||||
|   version() { |   version() { | ||||||
|     const version = fs |     const version = fs | ||||||
|       .readFileSync(path.resolve(paths.root_dir, "pyproject.toml"), "utf8") |       .readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8") | ||||||
|       .match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/); |       .match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/); | ||||||
|     if (!version) { |     if (!version) { | ||||||
|       throw Error("Version not found"); |       throw Error("Version not found"); | ||||||
|     } |     } | ||||||
|     return version[1]; |     return version[1]; | ||||||
|   }, |   }, | ||||||
|   isDevContainer() { |  | ||||||
|     return isTrue(process.env.DEV_CONTAINER); |  | ||||||
|   }, |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,16 +0,0 @@ | |||||||
| // @ts-check |  | ||||||
|  |  | ||||||
| import tseslint from "typescript-eslint"; |  | ||||||
| import rootConfig from "../eslint.config.mjs"; |  | ||||||
|  |  | ||||||
| export default tseslint.config(...rootConfig, { |  | ||||||
|   rules: { |  | ||||||
|     "no-console": "off", |  | ||||||
|     "import/no-extraneous-dependencies": "off", |  | ||||||
|     "import/extensions": "off", |  | ||||||
|     "import/no-dynamic-require": "off", |  | ||||||
|     "global-require": "off", |  | ||||||
|     "@typescript-eslint/no-require-imports": "off", |  | ||||||
|     "prefer-arrow-callback": "off", |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
| @@ -6,9 +6,11 @@ import "./entry-html.js"; | |||||||
| import "./gather-static.js"; | import "./gather-static.js"; | ||||||
| import "./gen-icons-json.js"; | import "./gen-icons-json.js"; | ||||||
| import "./locale-data.js"; | import "./locale-data.js"; | ||||||
|  | import "./rollup.js"; | ||||||
| import "./service-worker.js"; | import "./service-worker.js"; | ||||||
| import "./translations.js"; | import "./translations.js"; | ||||||
| import "./rspack.js"; | import "./wds.js"; | ||||||
|  | import "./webpack.js"; | ||||||
|  |  | ||||||
| gulp.task( | gulp.task( | ||||||
|   "develop-app", |   "develop-app", | ||||||
| @@ -25,7 +27,11 @@ gulp.task( | |||||||
|       "build-locale-data" |       "build-locale-data" | ||||||
|     ), |     ), | ||||||
|     "copy-static-app", |     "copy-static-app", | ||||||
|     "rspack-watch-app" |     env.useWDS() | ||||||
|  |       ? "wds-watch-app" | ||||||
|  |       : env.useRollup() | ||||||
|  |         ? "rollup-watch-app" | ||||||
|  |         : "webpack-watch-app" | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| @@ -38,20 +44,9 @@ gulp.task( | |||||||
|     "clean", |     "clean", | ||||||
|     gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), |     gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), | ||||||
|     "copy-static-app", |     "copy-static-app", | ||||||
|     "rspack-prod-app", |     env.useRollup() ? "rollup-prod-app" : "webpack-prod-app", | ||||||
|     gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod"), |     gulp.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"]) |     ...(env.isTestBuild() ? [] : ["compress-app"]) | ||||||
|   ) |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| gulp.task( |  | ||||||
|   "analyze-app", |  | ||||||
|   gulp.series( |  | ||||||
|     async function setEnv() { |  | ||||||
|       process.env.STATS = "1"; |  | ||||||
|     }, |  | ||||||
|     "clean", |  | ||||||
|     "rspack-prod-app" |  | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
|   | |||||||
| @@ -1,10 +1,12 @@ | |||||||
| import gulp from "gulp"; | import gulp from "gulp"; | ||||||
|  | import env from "../env.cjs"; | ||||||
| import "./clean.js"; | import "./clean.js"; | ||||||
| import "./entry-html.js"; | import "./entry-html.js"; | ||||||
| import "./gather-static.js"; | import "./gather-static.js"; | ||||||
|  | import "./rollup.js"; | ||||||
| import "./service-worker.js"; | import "./service-worker.js"; | ||||||
| import "./translations.js"; | import "./translations.js"; | ||||||
| import "./rspack.js"; | import "./webpack.js"; | ||||||
|  |  | ||||||
| gulp.task( | gulp.task( | ||||||
|   "develop-cast", |   "develop-cast", | ||||||
| @@ -17,7 +19,7 @@ gulp.task( | |||||||
|     gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), |     gulp.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" |     env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast" | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| @@ -31,7 +33,7 @@ gulp.task( | |||||||
|     "translations-enable-merge-backend", |     "translations-enable-merge-backend", | ||||||
|     gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), |     gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), | ||||||
|     "copy-static-cast", |     "copy-static-cast", | ||||||
|     "rspack-prod-cast", |     env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast", | ||||||
|     "gen-pages-cast-prod" |     "gen-pages-cast-prod" | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
|   | |||||||
| @@ -38,14 +38,3 @@ gulp.task( | |||||||
|     ]) |     ]) | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| gulp.task( |  | ||||||
|   "clean-landing-page", |  | ||||||
|   gulp.parallel("clean-translations", async () => |  | ||||||
|     deleteSync([ |  | ||||||
|       paths.landingPage_output_root, |  | ||||||
|       paths.landingPage_build, |  | ||||||
|       paths.build_dir, |  | ||||||
|     ]) |  | ||||||
|   ) |  | ||||||
| ); |  | ||||||
|   | |||||||
| @@ -1,86 +1,19 @@ | |||||||
| // Tasks to compress | // Tasks to compress | ||||||
|  |  | ||||||
| import { constants } from "node:zlib"; |  | ||||||
| import gulp from "gulp"; | import gulp from "gulp"; | ||||||
| import brotli from "gulp-brotli"; |  | ||||||
| import zopfli from "gulp-zopfli-green"; | import zopfli from "gulp-zopfli-green"; | ||||||
| import paths from "../paths.cjs"; | import paths from "../paths.cjs"; | ||||||
|  |  | ||||||
| const filesGlob = "*.{js,json,css,svg,xml}"; |  | ||||||
| const brotliOptions = { |  | ||||||
|   skipLarger: true, |  | ||||||
|   params: { |  | ||||||
|     [constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY, |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
| const zopfliOptions = { threshold: 150 }; | const zopfliOptions = { threshold: 150 }; | ||||||
|  |  | ||||||
| const compressModern = (rootDir, modernDir, compress) => | const compressDist = (rootDir) => | ||||||
|   gulp |   gulp | ||||||
|     .src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], { |     .src([ | ||||||
|       base: rootDir, |       `${rootDir}/**/*.{js,json,css,svg,xml}`, | ||||||
|       allowEmpty: true, |  | ||||||
|     }) |  | ||||||
|     .pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions)) |  | ||||||
|     .pipe(gulp.dest(rootDir)); |  | ||||||
|  |  | ||||||
| const compressOther = (rootDir, modernDir, compress) => |  | ||||||
|   gulp |  | ||||||
|     .src( |  | ||||||
|       [ |  | ||||||
|         `${rootDir}/**/${filesGlob}`, |  | ||||||
|         `!${modernDir}/**/${filesGlob}`, |  | ||||||
|         `!${rootDir}/{sw-modern,service_worker}.js`, |  | ||||||
|       `${rootDir}/{authorize,onboarding}.html`, |       `${rootDir}/{authorize,onboarding}.html`, | ||||||
|       ], |     ]) | ||||||
|       { base: rootDir, allowEmpty: true } |     .pipe(zopfli(zopfliOptions)) | ||||||
|     ) |  | ||||||
|     .pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions)) |  | ||||||
|     .pipe(gulp.dest(rootDir)); |     .pipe(gulp.dest(rootDir)); | ||||||
|  |  | ||||||
| const compressAppModernBrotli = () => | gulp.task("compress-app", () => compressDist(paths.app_output_root)); | ||||||
|   compressModern(paths.app_output_root, paths.app_output_latest, "brotli"); | gulp.task("compress-hassio", () => compressDist(paths.hassio_output_root)); | ||||||
| const compressAppModernZopfli = () => |  | ||||||
|   compressModern(paths.app_output_root, paths.app_output_latest, "zopfli"); |  | ||||||
|  |  | ||||||
| const compressHassioModernBrotli = () => |  | ||||||
|   compressModern( |  | ||||||
|     paths.hassio_output_root, |  | ||||||
|     paths.hassio_output_latest, |  | ||||||
|     "brotli" |  | ||||||
|   ); |  | ||||||
| const compressHassioModernZopfli = () => |  | ||||||
|   compressModern( |  | ||||||
|     paths.hassio_output_root, |  | ||||||
|     paths.hassio_output_latest, |  | ||||||
|     "zopfli" |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
| const compressAppOtherBrotli = () => |  | ||||||
|   compressOther(paths.app_output_root, paths.app_output_latest, "brotli"); |  | ||||||
| const compressAppOtherZopfli = () => |  | ||||||
|   compressOther(paths.app_output_root, paths.app_output_latest, "zopfli"); |  | ||||||
|  |  | ||||||
| const compressHassioOtherBrotli = () => |  | ||||||
|   compressOther(paths.hassio_output_root, paths.hassio_output_latest, "brotli"); |  | ||||||
| const compressHassioOtherZopfli = () => |  | ||||||
|   compressOther(paths.hassio_output_root, paths.hassio_output_latest, "zopfli"); |  | ||||||
|  |  | ||||||
| gulp.task( |  | ||||||
|   "compress-app", |  | ||||||
|   gulp.parallel( |  | ||||||
|     compressAppModernBrotli, |  | ||||||
|     compressAppOtherBrotli, |  | ||||||
|     compressAppModernZopfli, |  | ||||||
|     compressAppOtherZopfli |  | ||||||
|   ) |  | ||||||
| ); |  | ||||||
| gulp.task( |  | ||||||
|   "compress-hassio", |  | ||||||
|   gulp.parallel( |  | ||||||
|     compressHassioModernBrotli, |  | ||||||
|     compressHassioOtherBrotli, |  | ||||||
|     compressHassioModernZopfli, |  | ||||||
|     compressHassioOtherZopfli |  | ||||||
|   ) |  | ||||||
| ); |  | ||||||
|   | |||||||
| @@ -1,11 +1,13 @@ | |||||||
| import gulp from "gulp"; | import gulp from "gulp"; | ||||||
|  | import env from "../env.cjs"; | ||||||
| import "./clean.js"; | import "./clean.js"; | ||||||
| import "./entry-html.js"; | import "./entry-html.js"; | ||||||
| import "./gather-static.js"; | import "./gather-static.js"; | ||||||
| import "./gen-icons-json.js"; | import "./gen-icons-json.js"; | ||||||
|  | import "./rollup.js"; | ||||||
| import "./service-worker.js"; | import "./service-worker.js"; | ||||||
| import "./translations.js"; | import "./translations.js"; | ||||||
| import "./rspack.js"; | import "./webpack.js"; | ||||||
|  |  | ||||||
| gulp.task( | gulp.task( | ||||||
|   "develop-demo", |   "develop-demo", | ||||||
| @@ -22,7 +24,7 @@ gulp.task( | |||||||
|       "build-locale-data" |       "build-locale-data" | ||||||
|     ), |     ), | ||||||
|     "copy-static-demo", |     "copy-static-demo", | ||||||
|     "rspack-dev-server-demo" |     env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo" | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| @@ -37,18 +39,7 @@ gulp.task( | |||||||
|     "translations-enable-merge-backend", |     "translations-enable-merge-backend", | ||||||
|     gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), |     gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), | ||||||
|     "copy-static-demo", |     "copy-static-demo", | ||||||
|     "rspack-prod-demo", |     env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo", | ||||||
|     "gen-pages-demo-prod" |     "gen-pages-demo-prod" | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| gulp.task( |  | ||||||
|   "analyze-demo", |  | ||||||
|   gulp.series( |  | ||||||
|     async function setEnv() { |  | ||||||
|       process.env.STATS = "1"; |  | ||||||
|     }, |  | ||||||
|     "clean", |  | ||||||
|     "rspack-prod-demo" |  | ||||||
|   ) |  | ||||||
| ); |  | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ const srcMeta = "src/translations/translationMetadata.json"; | |||||||
| const encoding = "utf8"; | const encoding = "utf8"; | ||||||
|  |  | ||||||
| function hasHtml(data) { | function hasHtml(data) { | ||||||
|   return /<\S*>/i.test(data); |   return /<[a-z][\s\S]*>/i.test(data); | ||||||
| } | } | ||||||
|  |  | ||||||
| function recursiveCheckHasHtml(file, data, errors, recKey) { | function recursiveCheckHasHtml(file, data, errors, recKey) { | ||||||
| @@ -127,7 +127,6 @@ gulp.task("fetch-lokalise", async function () { | |||||||
|           replace_breaks: false, |           replace_breaks: false, | ||||||
|           json_unescaped_slashes: true, |           json_unescaped_slashes: true, | ||||||
|           export_empty_as: "skip", |           export_empty_as: "skip", | ||||||
|           filter_data: ["verified"], |  | ||||||
|         }) |         }) | ||||||
|         .then((download) => fetch(download.bundle_url)) |         .then((download) => fetch(download.bundle_url)) | ||||||
|         .then((response) => { |         .then((response) => { | ||||||
|   | |||||||
| @@ -1,74 +1,28 @@ | |||||||
| // Tasks to generate entry HTML | // Tasks to generate entry HTML | ||||||
|  |  | ||||||
| import { |  | ||||||
|   applyVersionsToRegexes, |  | ||||||
|   compileRegex, |  | ||||||
|   getPreUserAgentRegexes, |  | ||||||
| } from "browserslist-useragent-regexp"; |  | ||||||
| import fs from "fs-extra"; | import fs from "fs-extra"; | ||||||
| import gulp from "gulp"; | import gulp 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 path from "path"; | ||||||
| import { htmlMinifierOptions, terserOptions } from "../bundle.cjs"; | import { htmlMinifierOptions, terserOptions } from "../bundle.cjs"; | ||||||
|  | import env from "../env.cjs"; | ||||||
| import paths from "../paths.cjs"; | import paths from "../paths.cjs"; | ||||||
|  |  | ||||||
| // 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 |  | ||||||
| // to serve modern based on a minimum macOS version. We take the minimum Safari |  | ||||||
| // major version from browserslist and manually map that to a supported macOS |  | ||||||
| // version. Note this assumes the user has kept Safari updated. |  | ||||||
| const HA_MACOS_REGEX = |  | ||||||
|   /Home Assistant\/[\d.]+ \(.+; macOS (\d+)\.(\d+)(?:\.(\d+))?\)/; |  | ||||||
| const SAFARI_TO_MACOS = { |  | ||||||
|   15: [10, 15, 0], |  | ||||||
|   16: [11, 0, 0], |  | ||||||
|   17: [12, 0, 0], |  | ||||||
|   18: [13, 0, 0], |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const getCommonTemplateVars = () => { |  | ||||||
|   const browserRegexes = getPreUserAgentRegexes({ |  | ||||||
|     env: "modern", |  | ||||||
|     allowHigherVersions: true, |  | ||||||
|     mobileToDesktop: true, |  | ||||||
|     throwOnMissing: true, |  | ||||||
|   }); |  | ||||||
|   const minSafariVersion = browserRegexes.find( |  | ||||||
|     (regex) => regex.family === "safari" |  | ||||||
|   )?.matchedVersions[0][0]; |  | ||||||
|   const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion]; |  | ||||||
|   if (!minMacOSVersion) { |  | ||||||
|     throw Error( |  | ||||||
|       `Could not find minimum MacOS version for Safari ${minSafariVersion}.` |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|   const haMacOSRegex = applyVersionsToRegexes( |  | ||||||
|     [ |  | ||||||
|       { |  | ||||||
|         family: "ha_macos", |  | ||||||
|         regex: HA_MACOS_REGEX, |  | ||||||
|         matchedVersions: [minMacOSVersion], |  | ||||||
|         requestVersions: [minMacOSVersion], |  | ||||||
|       }, |  | ||||||
|     ], |  | ||||||
|     { ignorePatch: true, allowHigherVersions: true } |  | ||||||
|   ); |  | ||||||
|   return { |  | ||||||
|     modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(), |  | ||||||
|     hassUrl: process.env.HASS_URL || "", |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const renderTemplate = (templateFile, data = {}) => { | const renderTemplate = (templateFile, data = {}) => { | ||||||
|   const compiled = template( |   const compiled = template( | ||||||
|     fs.readFileSync(templateFile, { encoding: "utf-8" }) |     fs.readFileSync(templateFile, { encoding: "utf-8" }) | ||||||
|   ); |   ); | ||||||
|   return compiled({ |   return compiled({ | ||||||
|     ...data, |     ...data, | ||||||
|  |     useRollup: env.useRollup(), | ||||||
|  |     useWDS: env.useWDS(), | ||||||
|     // Resolve any child/nested templates relative to the parent and pass the same data |     // Resolve any child/nested templates relative to the parent and pass the same data | ||||||
|     renderTemplate: (childTemplate) => |     renderTemplate: (childTemplate) => | ||||||
|       renderTemplate(resolve(dirname(templateFile), childTemplate), data), |       renderTemplate( | ||||||
|  |         path.resolve(path.dirname(templateFile), childTemplate), | ||||||
|  |         data | ||||||
|  |       ), | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -91,32 +45,36 @@ const minifyHtml = (content, ext) => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| // Function to generate a dev task for each project's configuration | // Function to generate a dev task for each project's configuration | ||||||
|  | // Note Currently WDS paths are hard-coded to only work for app | ||||||
| const genPagesDevTask = | const genPagesDevTask = | ||||||
|   ( |   ( | ||||||
|     pageEntries, |     pageEntries, | ||||||
|     inputRoot, |     inputRoot, | ||||||
|     outputRoot, |     outputRoot, | ||||||
|  |     useWDS = false, | ||||||
|     inputSub = "src/html", |     inputSub = "src/html", | ||||||
|     publicRoot = "" |     publicRoot = "" | ||||||
|   ) => |   ) => | ||||||
|   async () => { |   async () => { | ||||||
|     const commonVars = getCommonTemplateVars(); |  | ||||||
|     for (const [page, entries] of Object.entries(pageEntries)) { |     for (const [page, entries] of Object.entries(pageEntries)) { | ||||||
|       const content = renderTemplate( |       const content = renderTemplate( | ||||||
|         resolve(inputRoot, inputSub, `${page}.template`), |         path.resolve(inputRoot, inputSub, `${page}.template`), | ||||||
|         { |         { | ||||||
|           ...commonVars, |           latestEntryJS: entries.map((entry) => | ||||||
|           latestEntryJS: entries.map( |             useWDS | ||||||
|             (entry) => `${publicRoot}/frontend_latest/${entry}.js` |               ? `http://localhost:8000/src/entrypoints/${entry}.ts` | ||||||
|  |               : `${publicRoot}/frontend_latest/${entry}.js` | ||||||
|           ), |           ), | ||||||
|           es5EntryJS: entries.map( |           es5EntryJS: entries.map( | ||||||
|             (entry) => `${publicRoot}/frontend_es5/${entry}.js` |             (entry) => `${publicRoot}/frontend_es5/${entry}.js` | ||||||
|           ), |           ), | ||||||
|           latestCustomPanelJS: `${publicRoot}/frontend_latest/custom-panel.js`, |           latestCustomPanelJS: useWDS | ||||||
|  |             ? "http://localhost:8000/src/entrypoints/custom-panel.ts" | ||||||
|  |             : `${publicRoot}/frontend_latest/custom-panel.js`, | ||||||
|           es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`, |           es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`, | ||||||
|         } |         } | ||||||
|       ); |       ); | ||||||
|       fs.outputFileSync(resolve(outputRoot, page), content); |       fs.outputFileSync(path.resolve(outputRoot, page), content); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
| @@ -133,18 +91,16 @@ const genPagesProdTask = | |||||||
|   ) => |   ) => | ||||||
|   async () => { |   async () => { | ||||||
|     const latestManifest = fs.readJsonSync( |     const latestManifest = fs.readJsonSync( | ||||||
|       resolve(outputLatest, "manifest.json") |       path.resolve(outputLatest, "manifest.json") | ||||||
|     ); |     ); | ||||||
|     const es5Manifest = outputES5 |     const es5Manifest = outputES5 | ||||||
|       ? fs.readJsonSync(resolve(outputES5, "manifest.json")) |       ? fs.readJsonSync(path.resolve(outputES5, "manifest.json")) | ||||||
|       : {}; |       : {}; | ||||||
|     const commonVars = getCommonTemplateVars(); |  | ||||||
|     const minifiedHTML = []; |     const minifiedHTML = []; | ||||||
|     for (const [page, entries] of Object.entries(pageEntries)) { |     for (const [page, entries] of Object.entries(pageEntries)) { | ||||||
|       const content = renderTemplate( |       const content = renderTemplate( | ||||||
|         resolve(inputRoot, inputSub, `${page}.template`), |         path.resolve(inputRoot, inputSub, `${page}.template`), | ||||||
|         { |         { | ||||||
|           ...commonVars, |  | ||||||
|           latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]), |           latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]), | ||||||
|           es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]), |           es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]), | ||||||
|           latestCustomPanelJS: latestManifest["custom-panel.js"], |           latestCustomPanelJS: latestManifest["custom-panel.js"], | ||||||
| @@ -152,8 +108,8 @@ const genPagesProdTask = | |||||||
|         } |         } | ||||||
|       ); |       ); | ||||||
|       minifiedHTML.push( |       minifiedHTML.push( | ||||||
|         minifyHtml(content, extname(page)).then((minified) => |         minifyHtml(content, path.extname(page)).then((minified) => | ||||||
|           fs.outputFileSync(resolve(outputRoot, page), minified) |           fs.outputFileSync(path.resolve(outputRoot, page), minified) | ||||||
|         ) |         ) | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| @@ -169,14 +125,19 @@ const APP_PAGE_ENTRIES = { | |||||||
|  |  | ||||||
| gulp.task( | gulp.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.polymer_dir, | ||||||
|  |     paths.app_output_root, | ||||||
|  |     env.useWDS() | ||||||
|  |   ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| gulp.task( | gulp.task( | ||||||
|   "gen-pages-app-prod", |   "gen-pages-app-prod", | ||||||
|   genPagesProdTask( |   genPagesProdTask( | ||||||
|     APP_PAGE_ENTRIES, |     APP_PAGE_ENTRIES, | ||||||
|     paths.root_dir, |     paths.polymer_dir, | ||||||
|     paths.app_output_root, |     paths.app_output_root, | ||||||
|     paths.app_output_latest, |     paths.app_output_latest, | ||||||
|     paths.app_output_es5 |     paths.app_output_es5 | ||||||
| @@ -245,28 +206,6 @@ gulp.task( | |||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| const LANDING_PAGE_PAGE_ENTRIES = { "index.html": ["entrypoint"] }; |  | ||||||
|  |  | ||||||
| gulp.task( |  | ||||||
|   "gen-pages-landing-page-dev", |  | ||||||
|   genPagesDevTask( |  | ||||||
|     LANDING_PAGE_PAGE_ENTRIES, |  | ||||||
|     paths.landingPage_dir, |  | ||||||
|     paths.landingPage_output_root |  | ||||||
|   ) |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| gulp.task( |  | ||||||
|   "gen-pages-landing-page-prod", |  | ||||||
|   genPagesProdTask( |  | ||||||
|     LANDING_PAGE_PAGE_ENTRIES, |  | ||||||
|     paths.landingPage_dir, |  | ||||||
|     paths.landingPage_output_root, |  | ||||||
|     paths.landingPage_output_latest, |  | ||||||
|     paths.landingPage_output_es5 |  | ||||||
|   ) |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] }; | const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] }; | ||||||
|  |  | ||||||
| gulp.task( | gulp.task( | ||||||
| @@ -275,6 +214,7 @@ gulp.task( | |||||||
|     HASSIO_PAGE_ENTRIES, |     HASSIO_PAGE_ENTRIES, | ||||||
|     paths.hassio_dir, |     paths.hassio_dir, | ||||||
|     paths.hassio_output_root, |     paths.hassio_output_root, | ||||||
|  |     undefined, | ||||||
|     "src", |     "src", | ||||||
|     paths.hassio_publicPath |     paths.hassio_publicPath | ||||||
|   ) |   ) | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ import gulp from "gulp"; | |||||||
| import jszip from "jszip"; | import jszip from "jszip"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import process from "process"; | import process from "process"; | ||||||
| import { extract } from "tar"; | import tar from "tar"; | ||||||
|  |  | ||||||
| const MAX_AGE = 24; // hours | const MAX_AGE = 24; // hours | ||||||
| const OWNER = "home-assistant"; | const OWNER = "home-assistant"; | ||||||
| @@ -66,7 +66,7 @@ gulp.task("fetch-nightly-translations", async function () { | |||||||
|       tokenAuth = JSON.parse(await readFile(TOKEN_FILE, "utf-8")); |       tokenAuth = JSON.parse(await readFile(TOKEN_FILE, "utf-8")); | ||||||
|     } catch { |     } catch { | ||||||
|       if (!allowTokenSetup) { |       if (!allowTokenSetup) { | ||||||
|         console.log("No token found so build will continue with English only"); |         console.log("No token found so  build wil continue with English only"); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       const auth = createOAuthDeviceAuth({ |       const auth = createOAuthDeviceAuth({ | ||||||
| @@ -156,7 +156,7 @@ gulp.task("fetch-nightly-translations", async function () { | |||||||
|   console.log("Unpacking downloaded translations..."); |   console.log("Unpacking downloaded translations..."); | ||||||
|   const zip = await jszip.loadAsync(downloadResponse.data); |   const zip = await jszip.loadAsync(downloadResponse.data); | ||||||
|   await deleteCurrent; |   await deleteCurrent; | ||||||
|   const extractStream = zip.file(/.*/)[0].nodeStream().pipe(extract()); |   const extractStream = zip.file(/.*/)[0].nodeStream().pipe(tar.extract()); | ||||||
|   await new Promise((resolve, reject) => { |   await new Promise((resolve, reject) => { | ||||||
|     extractStream.on("close", resolve).on("error", reject); |     extractStream.on("close", resolve).on("error", reject); | ||||||
|   }); |   }); | ||||||
|   | |||||||
| @@ -4,14 +4,16 @@ import gulp 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 "path"; | ||||||
|  | import env from "../env.cjs"; | ||||||
| import paths from "../paths.cjs"; | import paths from "../paths.cjs"; | ||||||
| import "./clean.js"; | import "./clean.js"; | ||||||
| import "./entry-html.js"; | import "./entry-html.js"; | ||||||
| import "./gather-static.js"; | import "./gather-static.js"; | ||||||
| import "./gen-icons-json.js"; | import "./gen-icons-json.js"; | ||||||
|  | import "./rollup.js"; | ||||||
| import "./service-worker.js"; | import "./service-worker.js"; | ||||||
| import "./translations.js"; | import "./translations.js"; | ||||||
| import "./rspack.js"; | import "./webpack.js"; | ||||||
|  |  | ||||||
| gulp.task("gather-gallery-pages", async function gatherPages() { | gulp.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"); | ||||||
| @@ -156,7 +158,9 @@ gulp.task( | |||||||
|     "copy-static-gallery", |     "copy-static-gallery", | ||||||
|     "gen-pages-gallery-dev", |     "gen-pages-gallery-dev", | ||||||
|     gulp.parallel( |     gulp.parallel( | ||||||
|       "rspack-dev-server-gallery", |       env.useRollup() | ||||||
|  |         ? "rollup-dev-server-gallery" | ||||||
|  |         : "webpack-dev-server-gallery", | ||||||
|       async function watchMarkdownFiles() { |       async function watchMarkdownFiles() { | ||||||
|         gulp.watch( |         gulp.watch( | ||||||
|           [ |           [ | ||||||
| @@ -185,7 +189,7 @@ gulp.task( | |||||||
|       "gather-gallery-pages" |       "gather-gallery-pages" | ||||||
|     ), |     ), | ||||||
|     "copy-static-gallery", |     "copy-static-gallery", | ||||||
|     "rspack-prod-gallery", |     env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery", | ||||||
|     "gen-pages-gallery-prod" |     "gen-pages-gallery-prod" | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
|   | |||||||
| @@ -4,10 +4,11 @@ import fs from "fs-extra"; | |||||||
| import gulp from "gulp"; | import gulp from "gulp"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import paths from "../paths.cjs"; | import paths from "../paths.cjs"; | ||||||
|  | import env from "../env.cjs"; | ||||||
|  |  | ||||||
| const npmPath = (...parts) => | const npmPath = (...parts) => | ||||||
|   path.resolve(paths.root_dir, "node_modules", ...parts); |   path.resolve(paths.polymer_dir, "node_modules", ...parts); | ||||||
| const polyPath = (...parts) => path.resolve(paths.root_dir, ...parts); | const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts); | ||||||
|  |  | ||||||
| const copyFileDir = (fromFile, toDir) => | const copyFileDir = (fromFile, toDir) => | ||||||
|   fs.copySync(fromFile, path.join(toDir, path.basename(fromFile))); |   fs.copySync(fromFile, path.join(toDir, path.basename(fromFile))); | ||||||
| @@ -59,17 +60,15 @@ function copyPolyfills(staticDir) { | |||||||
|     npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js.map"), |     npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js.map"), | ||||||
|     staticPath("polyfills/") |     staticPath("polyfills/") | ||||||
|   ); |   ); | ||||||
|   // Lit polyfill support | } | ||||||
|   fs.copySync( |  | ||||||
|     npmPath("lit/polyfill-support.js"), |  | ||||||
|     path.join(staticPath("polyfills/"), "lit-polyfill-support.js") |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   // dialog-polyfill css | function copyLoaderJS(staticDir) { | ||||||
|   copyFileDir( |   if (!env.useRollup()) { | ||||||
|     npmPath("dialog-polyfill/dialog-polyfill.css"), |     return; | ||||||
|     staticPath("polyfills/") |   } | ||||||
|   ); |   const staticPath = genStaticPath(staticDir); | ||||||
|  |   copyFileDir(npmPath("systemjs/dist/s.min.js"), staticPath("js")); | ||||||
|  |   copyFileDir(npmPath("systemjs/dist/s.min.js.map"), staticPath("js")); | ||||||
| } | } | ||||||
|  |  | ||||||
| function copyFonts(staticDir) { | function copyFonts(staticDir) { | ||||||
| @@ -95,24 +94,12 @@ function copyMapPanel(staticDir) { | |||||||
|     npmPath("leaflet/dist/leaflet.css"), |     npmPath("leaflet/dist/leaflet.css"), | ||||||
|     staticPath("images/leaflet/") |     staticPath("images/leaflet/") | ||||||
|   ); |   ); | ||||||
|   copyFileDir( |  | ||||||
|     npmPath("leaflet.markercluster/dist/MarkerCluster.css"), |  | ||||||
|     staticPath("images/leaflet/") |  | ||||||
|   ); |  | ||||||
|   fs.copySync( |   fs.copySync( | ||||||
|     npmPath("leaflet/dist/images"), |     npmPath("leaflet/dist/images"), | ||||||
|     staticPath("images/leaflet/images/") |     staticPath("images/leaflet/images/") | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| function copyZXingWasm(staticDir) { |  | ||||||
|   const staticPath = genStaticPath(staticDir); |  | ||||||
|   copyFileDir( |  | ||||||
|     npmPath("zxing-wasm/dist/reader/zxing_reader.wasm"), |  | ||||||
|     staticPath("js") |  | ||||||
|   ); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| gulp.task("copy-locale-data", async () => { | gulp.task("copy-locale-data", async () => { | ||||||
|   const staticDir = paths.app_output_static; |   const staticDir = paths.app_output_static; | ||||||
|   copyLocaleData(staticDir); |   copyLocaleData(staticDir); | ||||||
| @@ -128,11 +115,6 @@ gulp.task("copy-translations-supervisor", async () => { | |||||||
|   copyTranslations(staticDir); |   copyTranslations(staticDir); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| gulp.task("copy-translations-landing-page", async () => { |  | ||||||
|   const staticDir = paths.landingPage_output_static; |  | ||||||
|   copyTranslations(staticDir); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| gulp.task("copy-static-supervisor", async () => { | gulp.task("copy-static-supervisor", async () => { | ||||||
|   const staticDir = paths.hassio_output_static; |   const staticDir = paths.hassio_output_static; | ||||||
|   copyLocaleData(staticDir); |   copyLocaleData(staticDir); | ||||||
| @@ -143,6 +125,8 @@ gulp.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); | ||||||
|  |  | ||||||
|  |   copyLoaderJS(staticDir); | ||||||
|   copyPolyfills(staticDir); |   copyPolyfills(staticDir); | ||||||
|   copyFonts(staticDir); |   copyFonts(staticDir); | ||||||
|   copyTranslations(staticDir); |   copyTranslations(staticDir); | ||||||
| @@ -153,7 +137,6 @@ gulp.task("copy-static-app", async () => { | |||||||
|   copyMapPanel(staticDir); |   copyMapPanel(staticDir); | ||||||
|  |  | ||||||
|   // Qr Scanner assets |   // Qr Scanner assets | ||||||
|   copyZXingWasm(staticDir); |  | ||||||
|   copyQrScannerWorker(staticDir); |   copyQrScannerWorker(staticDir); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| @@ -165,6 +148,8 @@ gulp.task("copy-static-demo", async () => { | |||||||
|   ); |   ); | ||||||
|   // Copy demo static files |   // Copy demo static files | ||||||
|   fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_output_root); |   fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_output_root); | ||||||
|  |  | ||||||
|  |   copyLoaderJS(paths.demo_output_static); | ||||||
|   copyPolyfills(paths.demo_output_static); |   copyPolyfills(paths.demo_output_static); | ||||||
|   copyMapPanel(paths.demo_output_static); |   copyMapPanel(paths.demo_output_static); | ||||||
|   copyFonts(paths.demo_output_static); |   copyFonts(paths.demo_output_static); | ||||||
| @@ -178,6 +163,8 @@ gulp.task("copy-static-cast", async () => { | |||||||
|   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 | ||||||
|   fs.copySync(path.resolve(paths.cast_dir, "public"), paths.cast_output_root); |   fs.copySync(path.resolve(paths.cast_dir, "public"), paths.cast_output_root); | ||||||
|  |  | ||||||
|  |   copyLoaderJS(paths.cast_output_static); | ||||||
|   copyPolyfills(paths.cast_output_static); |   copyPolyfills(paths.cast_output_static); | ||||||
|   copyMapPanel(paths.cast_output_static); |   copyMapPanel(paths.cast_output_static); | ||||||
|   copyFonts(paths.cast_output_static); |   copyFonts(paths.cast_output_static); | ||||||
| @@ -201,14 +188,3 @@ gulp.task("copy-static-gallery", async () => { | |||||||
|   copyLocaleData(paths.gallery_output_static); |   copyLocaleData(paths.gallery_output_static); | ||||||
|   copyMdiIcons(paths.gallery_output_static); |   copyMdiIcons(paths.gallery_output_static); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| gulp.task("copy-static-landing-page", async () => { |  | ||||||
|   // Copy landing-page static files |  | ||||||
|   fs.copySync( |  | ||||||
|     path.resolve(paths.landingPage_dir, "public"), |  | ||||||
|     paths.landingPage_output_root |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   copyFonts(paths.landingPage_output_static); |  | ||||||
|   copyTranslations(paths.landingPage_output_static); |  | ||||||
| }); |  | ||||||
|   | |||||||
| @@ -5,8 +5,9 @@ import "./compress.js"; | |||||||
| import "./entry-html.js"; | import "./entry-html.js"; | ||||||
| import "./gather-static.js"; | import "./gather-static.js"; | ||||||
| import "./gen-icons-json.js"; | import "./gen-icons-json.js"; | ||||||
|  | import "./rollup.js"; | ||||||
| import "./translations.js"; | import "./translations.js"; | ||||||
| import "./rspack.js"; | import "./webpack.js"; | ||||||
|  |  | ||||||
| gulp.task( | gulp.task( | ||||||
|   "develop-hassio", |   "develop-hassio", | ||||||
| @@ -21,7 +22,7 @@ gulp.task( | |||||||
|     "copy-translations-supervisor", |     "copy-translations-supervisor", | ||||||
|     "build-locale-data", |     "build-locale-data", | ||||||
|     "copy-static-supervisor", |     "copy-static-supervisor", | ||||||
|     "rspack-watch-hassio" |     env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio" | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| @@ -37,7 +38,7 @@ gulp.task( | |||||||
|     "copy-translations-supervisor", |     "copy-translations-supervisor", | ||||||
|     "build-locale-data", |     "build-locale-data", | ||||||
|     "copy-static-supervisor", |     "copy-static-supervisor", | ||||||
|     "rspack-prod-hassio", |     env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio", | ||||||
|     "gen-pages-hassio-prod", |     "gen-pages-hassio-prod", | ||||||
|     ...// Don't compress running tests |     ...// Don't compress running tests | ||||||
|     (env.isTestBuild() ? [] : ["compress-hassio"]) |     (env.isTestBuild() ? [] : ["compress-hassio"]) | ||||||
|   | |||||||
| @@ -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"; |  | ||||||
| @@ -1,41 +0,0 @@ | |||||||
| import gulp from "gulp"; |  | ||||||
| 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"; |  | ||||||
|  |  | ||||||
| gulp.task( |  | ||||||
|   "develop-landing-page", |  | ||||||
|   gulp.series( |  | ||||||
|     async function setEnv() { |  | ||||||
|       process.env.NODE_ENV = "development"; |  | ||||||
|     }, |  | ||||||
|     "clean-landing-page", |  | ||||||
|     "translations-enable-merge-backend", |  | ||||||
|     "build-landing-page-translations", |  | ||||||
|     "copy-translations-landing-page", |  | ||||||
|     "build-locale-data", |  | ||||||
|     "copy-static-landing-page", |  | ||||||
|     "gen-pages-landing-page-dev", |  | ||||||
|     "rspack-watch-landing-page" |  | ||||||
|   ) |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| gulp.task( |  | ||||||
|   "build-landing-page", |  | ||||||
|   gulp.series( |  | ||||||
|     async function setEnv() { |  | ||||||
|       process.env.NODE_ENV = "production"; |  | ||||||
|     }, |  | ||||||
|     "clean-landing-page", |  | ||||||
|     "build-landing-page-translations", |  | ||||||
|     "copy-translations-landing-page", |  | ||||||
|     "build-locale-data", |  | ||||||
|     "copy-static-landing-page", |  | ||||||
|     "rspack-prod-landing-page", |  | ||||||
|     "gen-pages-landing-page-prod" |  | ||||||
|   ) |  | ||||||
| ); |  | ||||||
| @@ -4,7 +4,7 @@ import gulp from "gulp"; | |||||||
| import { join, resolve } from "node:path"; | import { join, resolve } from "node:path"; | ||||||
| import paths from "../paths.cjs"; | import paths from "../paths.cjs"; | ||||||
|  |  | ||||||
| const formatjsDir = join(paths.root_dir, "node_modules", "@formatjs"); | const formatjsDir = join(paths.polymer_dir, "node_modules", "@formatjs"); | ||||||
| const outDir = join(paths.build_dir, "locale-data"); | const outDir = join(paths.build_dir, "locale-data"); | ||||||
|  |  | ||||||
| const INTL_POLYFILLS = { | const INTL_POLYFILLS = { | ||||||
| @@ -24,11 +24,8 @@ const convertToJSON = async ( | |||||||
| ) => { | ) => { | ||||||
|   let localeData; |   let localeData; | ||||||
|   try { |   try { | ||||||
|     // use "pt" for "pt-BR", because "pt-BR" is unsupported by @formatjs |  | ||||||
|     const language = lang === "pt-BR" ? "pt" : lang; |  | ||||||
|  |  | ||||||
|     localeData = await readFile( |     localeData = await readFile( | ||||||
|       join(formatjsDir, pkg, subDir, `${language}.js`), |       join(formatjsDir, pkg, subDir, `${lang}.js`), | ||||||
|       "utf-8" |       "utf-8" | ||||||
|     ); |     ); | ||||||
|   } catch (e) { |   } catch (e) { | ||||||
|   | |||||||
							
								
								
									
										147
									
								
								build-scripts/gulp/rollup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								build-scripts/gulp/rollup.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | |||||||
|  | // Tasks to run Rollup | ||||||
|  |  | ||||||
|  | import log from "fancy-log"; | ||||||
|  | import gulp from "gulp"; | ||||||
|  | import http from "http"; | ||||||
|  | import open from "open"; | ||||||
|  | import path from "path"; | ||||||
|  | import { rollup } from "rollup"; | ||||||
|  | import handler from "serve-handler"; | ||||||
|  | import paths from "../paths.cjs"; | ||||||
|  | import rollupConfig from "../rollup.cjs"; | ||||||
|  |  | ||||||
|  | const bothBuilds = (createConfigFunc, params) => | ||||||
|  |   gulp.series( | ||||||
|  |     async function buildLatest() { | ||||||
|  |       await buildRollup( | ||||||
|  |         createConfigFunc({ | ||||||
|  |           ...params, | ||||||
|  |           latestBuild: true, | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }, | ||||||
|  |     async function buildES5() { | ||||||
|  |       await buildRollup( | ||||||
|  |         createConfigFunc({ | ||||||
|  |           ...params, | ||||||
|  |           latestBuild: false, | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  | function createServer(serveOptions) { | ||||||
|  |   const server = http.createServer((request, response) => | ||||||
|  |     handler(request, response, { | ||||||
|  |       public: serveOptions.root, | ||||||
|  |     }) | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   server.listen( | ||||||
|  |     serveOptions.port, | ||||||
|  |     serveOptions.networkAccess ? "0.0.0.0" : undefined, | ||||||
|  |     () => { | ||||||
|  |       log.info(`Available at http://localhost:${serveOptions.port}`); | ||||||
|  |       open(`http://localhost:${serveOptions.port}`); | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function watchRollup(createConfig, extraWatchSrc = [], serveOptions = null) { | ||||||
|  |   const { inputOptions, outputOptions } = createConfig({ | ||||||
|  |     isProdBuild: false, | ||||||
|  |     latestBuild: true, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const watcher = rollup.watch({ | ||||||
|  |     ...inputOptions, | ||||||
|  |     output: [outputOptions], | ||||||
|  |     watch: { | ||||||
|  |       include: ["src/**"] + extraWatchSrc, | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   let startedHttp = false; | ||||||
|  |  | ||||||
|  |   watcher.on("event", (event) => { | ||||||
|  |     if (event.code === "BUNDLE_END") { | ||||||
|  |       log(`Build done @ ${new Date().toLocaleTimeString()}`); | ||||||
|  |     } else if (event.code === "ERROR") { | ||||||
|  |       log.error(event.error); | ||||||
|  |     } else if (event.code === "END") { | ||||||
|  |       if (startedHttp || !serveOptions) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       startedHttp = true; | ||||||
|  |       createServer(serveOptions); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   gulp.watch( | ||||||
|  |     path.join(paths.translations_src, "en.json"), | ||||||
|  |     gulp.series("build-translations", "copy-translations-app") | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function buildRollup(config) { | ||||||
|  |   const bundle = await rollup.rollup(config.inputOptions); | ||||||
|  |   await bundle.write(config.outputOptions); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | gulp.task("rollup-watch-app", () => { | ||||||
|  |   watchRollup(rollupConfig.createAppConfig); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | gulp.task("rollup-watch-hassio", () => { | ||||||
|  |   watchRollup(rollupConfig.createHassioConfig, ["hassio/src/**"]); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | gulp.task("rollup-dev-server-demo", () => { | ||||||
|  |   watchRollup(rollupConfig.createDemoConfig, ["demo/src/**"], { | ||||||
|  |     root: paths.demo_output_root, | ||||||
|  |     port: 8090, | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | gulp.task("rollup-dev-server-cast", () => { | ||||||
|  |   watchRollup(rollupConfig.createCastConfig, ["cast/src/**"], { | ||||||
|  |     root: paths.cast_output_root, | ||||||
|  |     port: 8080, | ||||||
|  |     networkAccess: true, | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | gulp.task("rollup-dev-server-gallery", () => { | ||||||
|  |   watchRollup(rollupConfig.createGalleryConfig, ["gallery/src/**"], { | ||||||
|  |     root: paths.gallery_output_root, | ||||||
|  |     port: 8100, | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | gulp.task( | ||||||
|  |   "rollup-prod-app", | ||||||
|  |   bothBuilds(rollupConfig.createAppConfig, { isProdBuild: true }) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | gulp.task( | ||||||
|  |   "rollup-prod-demo", | ||||||
|  |   bothBuilds(rollupConfig.createDemoConfig, { isProdBuild: true }) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | gulp.task( | ||||||
|  |   "rollup-prod-cast", | ||||||
|  |   bothBuilds(rollupConfig.createCastConfig, { isProdBuild: true }) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | gulp.task("rollup-prod-hassio", () => | ||||||
|  |   bothBuilds(rollupConfig.createHassioConfig, { isProdBuild: true }) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | gulp.task("rollup-prod-gallery", () => | ||||||
|  |   buildRollup( | ||||||
|  |     rollupConfig.createGalleryConfig({ | ||||||
|  |       isProdBuild: true, | ||||||
|  |       latestBuild: true, | ||||||
|  |     }) | ||||||
|  |   ) | ||||||
|  | ); | ||||||
| @@ -1,18 +1,19 @@ | |||||||
| // Generate service workers | // Generate service worker. | ||||||
|  | // Based on manifest, create a file with the content as service_worker.js | ||||||
|  |  | ||||||
| import { deleteAsync } from "del"; | import fs from "fs-extra"; | ||||||
| import gulp from "gulp"; | import gulp from "gulp"; | ||||||
| import { mkdir, readFile, symlink, writeFile } from "node:fs/promises"; | import path from "path"; | ||||||
| import { basename, join, relative } from "node:path"; | import sourceMapUrl from "source-map-url"; | ||||||
| import { injectManifest } from "workbox-build"; | import workboxBuild from "workbox-build"; | ||||||
| import paths from "../paths.cjs"; | import paths from "../paths.cjs"; | ||||||
|  |  | ||||||
| const SW_MAP = { | const swDest = path.resolve(paths.app_output_root, "service_worker.js"); | ||||||
|   [paths.app_output_latest]: "modern", |  | ||||||
|   [paths.app_output_es5]: "legacy", |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const SW_DEV = | const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n"); | ||||||
|  |  | ||||||
|  | gulp.task("gen-service-worker-app-dev", (done) => { | ||||||
|  |   writeSW( | ||||||
|     ` |     ` | ||||||
| console.debug('Service worker disabled in development'); | console.debug('Service worker disabled in development'); | ||||||
|  |  | ||||||
| @@ -21,40 +22,46 @@ self.addEventListener('install', (event) => { | |||||||
|   // removing any prod service worker the dev might have running |   // removing any prod service worker the dev might have running | ||||||
|   self.skipWaiting(); |   self.skipWaiting(); | ||||||
| }); | }); | ||||||
|   `.trim() + "\n"; |   ` | ||||||
|  |  | ||||||
| gulp.task("gen-service-worker-app-dev", async () => { |  | ||||||
|   await mkdir(paths.app_output_root, { recursive: true }); |  | ||||||
|   await Promise.all( |  | ||||||
|     Object.values(SW_MAP).map((build) => |  | ||||||
|       writeFile(join(paths.app_output_root, `sw-${build}.js`), SW_DEV, { |  | ||||||
|         encoding: "utf-8", |  | ||||||
|       }) |  | ||||||
|     ) |  | ||||||
|   ); |   ); | ||||||
|  |   done(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| gulp.task("gen-service-worker-app-prod", () => | gulp.task("gen-service-worker-app-prod", async () => { | ||||||
|   Promise.all( |   // Read bundled source file | ||||||
|     Object.entries(SW_MAP).map(async ([outPath, build]) => { |   const bundleManifestLatest = fs.readJsonSync( | ||||||
|       const manifest = JSON.parse( |     path.resolve(paths.app_output_latest, "manifest.json") | ||||||
|         await readFile(join(outPath, "manifest.json"), "utf-8") |  | ||||||
|   ); |   ); | ||||||
|       const swSrc = join(paths.app_output_root, manifest["service-worker.js"]); |   let serviceWorkerContent = fs.readFileSync( | ||||||
|       const swDest = join(paths.app_output_root, `sw-${build}.js`); |     paths.app_output_root + bundleManifestLatest["service_worker.js"], | ||||||
|       const buildDir = relative(paths.app_output_root, outPath); |     "utf-8" | ||||||
|       const { warnings } = await injectManifest({ |   ); | ||||||
|         swSrc, |  | ||||||
|         swDest, |   // Delete old file from frontend_latest so manifest won't pick it up | ||||||
|         injectionPoint: "__WB_MANIFEST__", |   fs.removeSync( | ||||||
|  |     paths.app_output_root + bundleManifestLatest["service_worker.js"] | ||||||
|  |   ); | ||||||
|  |   fs.removeSync( | ||||||
|  |     paths.app_output_root + bundleManifestLatest["service_worker.js.map"] | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   // Remove ES5 | ||||||
|  |   const bundleManifestES5 = fs.readJsonSync( | ||||||
|  |     path.resolve(paths.app_output_es5, "manifest.json") | ||||||
|  |   ); | ||||||
|  |   fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]); | ||||||
|  |   fs.removeSync( | ||||||
|  |     paths.app_output_root + bundleManifestES5["service_worker.js.map"] | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const workboxManifest = await workboxBuild.getManifest({ | ||||||
|     // Files that mach this pattern will be considered unique and skip revision check |     // Files that mach this pattern will be considered unique and skip revision check | ||||||
|     // ignore JS files + translation files |     // ignore JS files + translation files | ||||||
|         dontCacheBustURLsMatching: new RegExp( |     dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/, | ||||||
|           `(?:${buildDir}/.+|static/translations/.+)` |  | ||||||
|         ), |  | ||||||
|     globDirectory: paths.app_output_root, |     globDirectory: paths.app_output_root, | ||||||
|     globPatterns: [ |     globPatterns: [ | ||||||
|           `${buildDir}/*.js`, |       "frontend_latest/*.js", | ||||||
|       // Cache all English translations because we catch them as fallback |       // Cache all English translations because we catch them as fallback | ||||||
|       // Using pattern to match hash instead of * to avoid caching en-GB |       // Using pattern to match hash instead of * to avoid caching en-GB | ||||||
|       // 'v' added as valid hash letter because in dev we hash with 'dev' |       // 'v' added as valid hash letter because in dev we hash with 'dev' | ||||||
| @@ -68,20 +75,19 @@ gulp.task("gen-service-worker-app-prod", () => | |||||||
|       "static/fonts/roboto/Roboto-Regular.woff2", |       "static/fonts/roboto/Roboto-Regular.woff2", | ||||||
|       "static/fonts/roboto/Roboto-Bold.woff2", |       "static/fonts/roboto/Roboto-Bold.woff2", | ||||||
|     ], |     ], | ||||||
|         globIgnores: [`${buildDir}/service-worker*`], |  | ||||||
|   }); |   }); | ||||||
|       if (warnings.length > 0) { |  | ||||||
|         console.warn( |   for (const warning of workboxManifest.warnings) { | ||||||
|           `Problems while injecting ${build} service worker:\n`, |     console.warn(warning); | ||||||
|           warnings.join("\n") |   } | ||||||
|  |  | ||||||
|  |   // remove source map and add WB manifest | ||||||
|  |   serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent); | ||||||
|  |   serviceWorkerContent = serviceWorkerContent.replace( | ||||||
|  |     "WB_MANIFEST", | ||||||
|  |     JSON.stringify(workboxManifest.manifestEntries) | ||||||
|   ); |   ); | ||||||
|       } |  | ||||||
|       await deleteAsync(`${swSrc}?(.map)`); |   // Write new file to root | ||||||
|       // Needed to install new SW from a cached HTML |   fs.writeFileSync(swDest, serviceWorkerContent); | ||||||
|       if (build === "modern") { | }); | ||||||
|         const swOld = join(paths.app_output_root, "service_worker.js"); |  | ||||||
|         await symlink(basename(swDest), swOld); |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
|   ) |  | ||||||
| ); |  | ||||||
|   | |||||||
| @@ -1,103 +1,92 @@ | |||||||
| /* eslint-disable max-classes-per-file */ | import { createHash } from "crypto"; | ||||||
|  | import { deleteSync } from "del"; | ||||||
| import { deleteAsync } from "del"; | import { mkdirSync, readdirSync, readFileSync, renameSync } from "fs"; | ||||||
| import { glob } from "glob"; | import { writeFile } from "node:fs/promises"; | ||||||
| import gulp from "gulp"; | import gulp from "gulp"; | ||||||
|  | import flatmap from "gulp-flatmap"; | ||||||
|  | import transform from "gulp-json-transform"; | ||||||
|  | import merge from "gulp-merge-json"; | ||||||
| import rename from "gulp-rename"; | import rename from "gulp-rename"; | ||||||
| import merge from "lodash.merge"; | import path from "path"; | ||||||
| import { createHash } from "node:crypto"; | import vinylBuffer from "vinyl-buffer"; | ||||||
| import { mkdir, readFile } from "node:fs/promises"; | import source from "vinyl-source-stream"; | ||||||
| import { basename, join } from "node:path"; |  | ||||||
| import { PassThrough, Transform } from "node:stream"; |  | ||||||
| import { finished } from "node:stream/promises"; |  | ||||||
| import env from "../env.cjs"; | import env from "../env.cjs"; | ||||||
| import paths from "../paths.cjs"; | import paths from "../paths.cjs"; | ||||||
|  | import { mapFiles } from "../util.cjs"; | ||||||
| import "./fetch-nightly-translations.js"; | import "./fetch-nightly-translations.js"; | ||||||
|  |  | ||||||
| const inFrontendDir = "translations/frontend"; | const inFrontendDir = "translations/frontend"; | ||||||
| const inBackendDir = "translations/backend"; | const inBackendDir = "translations/backend"; | ||||||
| const workDir = "build/translations"; | const workDir = "build/translations"; | ||||||
| const outDir = join(workDir, "output"); | const fullDir = workDir + "/full"; | ||||||
| const EN_SRC = join(paths.translations_src, "en.json"); | const coreDir = workDir + "/core"; | ||||||
| const TEST_LOCALE = "en-x-test"; | const outDir = workDir + "/output"; | ||||||
|  |  | ||||||
| let mergeBackend = false; | let mergeBackend = false; | ||||||
|  |  | ||||||
| gulp.task( | gulp.task( | ||||||
|   "translations-enable-merge-backend", |   "translations-enable-merge-backend", | ||||||
|   gulp.parallel(async () => { |   gulp.parallel((done) => { | ||||||
|     mergeBackend = true; |     mergeBackend = true; | ||||||
|  |     done(); | ||||||
|   }, "allow-setup-fetch-nightly-translations") |   }, "allow-setup-fetch-nightly-translations") | ||||||
| ); | ); | ||||||
|  |  | ||||||
| // Transform stream to apply a function on Vinyl JSON files (buffer mode only). | // Panel translations which should be split from the core translations. | ||||||
| // The provided function can either return a new object, or an array of | const TRANSLATION_FRAGMENTS = Object.keys( | ||||||
| // [object, subdirectory] pairs for fragmentizing the JSON. |   JSON.parse( | ||||||
| class CustomJSON extends Transform { |     readFileSync( | ||||||
|   constructor(func, reviver = null) { |       path.resolve(paths.polymer_dir, "src/translations/en.json"), | ||||||
|     super({ objectMode: true }); |       "utf-8" | ||||||
|     this._func = func; |     ) | ||||||
|     this._reviver = reviver; |   ).ui.panel | ||||||
|   } | ); | ||||||
|  |  | ||||||
|   // eslint-disable-next-line @typescript-eslint/naming-convention | function recursiveFlatten(prefix, data) { | ||||||
|   async _transform(file, _, callback) { |   let output = {}; | ||||||
|     let obj = JSON.parse(file.contents.toString(), this._reviver); |   Object.keys(data).forEach((key) => { | ||||||
|     if (this._func) obj = this._func(obj, file.path); |     if (typeof data[key] === "object") { | ||||||
|     for (const [outObj, dir] of Array.isArray(obj) ? obj : [[obj, ""]]) { |       output = { | ||||||
|       const outFile = file.clone({ contents: false }); |         ...output, | ||||||
|       outFile.contents = Buffer.from(JSON.stringify(outObj)); |         ...recursiveFlatten(prefix + key + ".", data[key]), | ||||||
|       outFile.dirname += `/${dir}`; |       }; | ||||||
|       this.push(outFile); |  | ||||||
|     } |  | ||||||
|     callback(null); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Transform stream to merge Vinyl JSON files (buffer mode only). |  | ||||||
| class MergeJSON extends Transform { |  | ||||||
|   _objects = []; |  | ||||||
|  |  | ||||||
|   constructor(stem, startObj = {}, reviver = null) { |  | ||||||
|     super({ objectMode: true, allowHalfOpen: false }); |  | ||||||
|     this._stem = stem; |  | ||||||
|     this._startObj = structuredClone(startObj); |  | ||||||
|     this._reviver = reviver; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // eslint-disable-next-line @typescript-eslint/naming-convention |  | ||||||
|   async _transform(file, _, callback) { |  | ||||||
|     this._objects.push(JSON.parse(file.contents.toString(), this._reviver)); |  | ||||||
|     if (!this._outFile) this._outFile = file.clone({ contents: false }); |  | ||||||
|     callback(null); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // eslint-disable-next-line @typescript-eslint/naming-convention |  | ||||||
|   async _flush(callback) { |  | ||||||
|     const mergedObj = merge(this._startObj, ...this._objects); |  | ||||||
|     this._outFile.contents = Buffer.from(JSON.stringify(mergedObj)); |  | ||||||
|     this._outFile.stem = this._stem; |  | ||||||
|     callback(null, this._outFile); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Utility to flatten object keys to single level using separator |  | ||||||
| const flatten = (data, prefix = "", sep = ".") => { |  | ||||||
|   const output = {}; |  | ||||||
|   for (const [key, value] of Object.entries(data)) { |  | ||||||
|     if (typeof value === "object") { |  | ||||||
|       Object.assign(output, flatten(value, prefix + key + sep, sep)); |  | ||||||
|     } else { |     } else { | ||||||
|       output[prefix + key] = value; |       output[prefix + key] = data[key]; | ||||||
|     } |  | ||||||
|     } |     } | ||||||
|  |   }); | ||||||
|   return output; |   return output; | ||||||
| }; | } | ||||||
|  |  | ||||||
| // Filter functions that can be passed directly to JSON.parse() | function flatten(data) { | ||||||
| const emptyReviver = (_key, value) => value || undefined; |   return recursiveFlatten("", data); | ||||||
| const testReviver = (_key, value) => | } | ||||||
|   value && typeof value === "string" ? "TRANSLATED" : value; |  | ||||||
|  | function emptyFilter(data) { | ||||||
|  |   const newData = {}; | ||||||
|  |   Object.keys(data).forEach((key) => { | ||||||
|  |     if (data[key]) { | ||||||
|  |       if (typeof data[key] === "object") { | ||||||
|  |         newData[key] = emptyFilter(data[key]); | ||||||
|  |       } else { | ||||||
|  |         newData[key] = data[key]; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   return newData; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function recursiveEmpty(data) { | ||||||
|  |   const newData = {}; | ||||||
|  |   Object.keys(data).forEach((key) => { | ||||||
|  |     if (data[key]) { | ||||||
|  |       if (typeof data[key] === "object") { | ||||||
|  |         newData[key] = recursiveEmpty(data[key]); | ||||||
|  |       } else { | ||||||
|  |         newData[key] = "TRANSLATED"; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   return newData; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Replace Lokalise key placeholders with their actual values. |  * Replace Lokalise key placeholders with their actual values. | ||||||
| @@ -106,44 +95,60 @@ const testReviver = (_key, value) => | |||||||
|  * be included in src/translations/en.json, but still be usable while |  * be included in src/translations/en.json, but still be usable while | ||||||
|  * developing locally. |  * developing locally. | ||||||
|  * |  * | ||||||
|  * @link https://docs.lokalise.com/en/articles/1400528-key-referencing |  * @link https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing | ||||||
|  */ |  */ | ||||||
| const KEY_REFERENCE = /\[%key:([^%]+)%\]/; | const re_key_reference = /\[%key:([^%]+)%\]/; | ||||||
| const lokaliseTransform = (data, path, original = data) => { | function lokaliseTransform(data, original, file) { | ||||||
|   const output = {}; |   const output = {}; | ||||||
|   for (const [key, value] of Object.entries(data)) { |   Object.entries(data).forEach(([key, value]) => { | ||||||
|     if (typeof value === "object") { |     if (value instanceof Object) { | ||||||
|       output[key] = lokaliseTransform(value, path, original); |       output[key] = lokaliseTransform(value, original, file); | ||||||
|     } else { |     } else { | ||||||
|       output[key] = value.replace(KEY_REFERENCE, (_match, lokalise_key) => { |       output[key] = value.replace(re_key_reference, (_match, lokalise_key) => { | ||||||
|         const replace = lokalise_key.split("::").reduce((tr, k) => { |         const replace = lokalise_key.split("::").reduce((tr, k) => { | ||||||
|           if (!tr) { |           if (!tr) { | ||||||
|             throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`); |             throw Error( | ||||||
|  |               `Invalid key placeholder ${lokalise_key} in ${file.path}` | ||||||
|  |             ); | ||||||
|           } |           } | ||||||
|           return tr[k]; |           return tr[k]; | ||||||
|         }, original); |         }, original); | ||||||
|         if (typeof replace !== "string") { |         if (typeof replace !== "string") { | ||||||
|           throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`); |           throw Error( | ||||||
|  |             `Invalid key placeholder ${lokalise_key} in ${file.path}` | ||||||
|  |           ); | ||||||
|         } |         } | ||||||
|         return replace; |         return replace; | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   } |   }); | ||||||
|   return output; |   return output; | ||||||
| }; | } | ||||||
|  |  | ||||||
| gulp.task("clean-translations", () => deleteAsync([workDir])); | gulp.task("clean-translations", async () => deleteSync([workDir])); | ||||||
|  |  | ||||||
| const makeWorkDir = () => mkdir(workDir, { recursive: true }); | gulp.task("ensure-translations-build-dir", async () => { | ||||||
|  |   mkdirSync(workDir, { recursive: true }); | ||||||
|  | }); | ||||||
|  |  | ||||||
| const createTestTranslation = () => | gulp.task("create-test-metadata", () => | ||||||
|  |   env.isProdBuild() | ||||||
|  |     ? Promise.resolve() | ||||||
|  |     : writeFile( | ||||||
|  |         workDir + "/testMetadata.json", | ||||||
|  |         JSON.stringify({ test: { nativeName: "Test" } }) | ||||||
|  |       ) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | gulp.task("create-test-translation", () => | ||||||
|   env.isProdBuild() |   env.isProdBuild() | ||||||
|     ? Promise.resolve() |     ? Promise.resolve() | ||||||
|     : gulp |     : gulp | ||||||
|         .src(EN_SRC) |         .src(path.join(paths.translations_src, "en.json")) | ||||||
|         .pipe(new CustomJSON(null, testReviver)) |         .pipe(transform((data, _file) => recursiveEmpty(data))) | ||||||
|         .pipe(rename(`${TEST_LOCALE}.json`)) |         .pipe(rename("test.json")) | ||||||
|         .pipe(gulp.dest(workDir)); |         .pipe(gulp.dest(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 | ||||||
| @@ -154,174 +159,279 @@ const createTestTranslation = () => | |||||||
|  * project is buildable immediately after merging new translation keys, since |  * project is buildable immediately after merging new translation keys, since | ||||||
|  * the Lokalise update to translations/en.json will not happen immediately. |  * the Lokalise update to translations/en.json will not happen immediately. | ||||||
|  */ |  */ | ||||||
| const createMasterTranslation = () => | gulp.task("build-master-translation", () => { | ||||||
|   gulp |   const src = [path.join(paths.translations_src, "en.json")]; | ||||||
|     .src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])]) |  | ||||||
|     .pipe(new CustomJSON(lokaliseTransform)) |  | ||||||
|     .pipe(new MergeJSON("en")) |  | ||||||
|     .pipe(gulp.dest(workDir)); |  | ||||||
|  |  | ||||||
| const FRAGMENTS = ["base"]; |   if (mergeBackend) { | ||||||
|  |     src.push(path.join(inBackendDir, "en.json")); | ||||||
| const setFragment = (fragment) => async () => { |  | ||||||
|   FRAGMENTS[0] = fragment; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const panelFragment = (fragment) => |  | ||||||
|   fragment !== "base" && |  | ||||||
|   fragment !== "supervisor" && |  | ||||||
|   fragment !== "landing-page"; |  | ||||||
|  |  | ||||||
| const HASHES = new Map(); |  | ||||||
|  |  | ||||||
| const createTranslations = async () => { |  | ||||||
|   // Parse and store the master to avoid repeating this for each locale, then |  | ||||||
|   // add the panel fragments when processing the app. |  | ||||||
|   const enMaster = JSON.parse(await readFile(`${workDir}/en.json`, "utf-8")); |  | ||||||
|   if (FRAGMENTS[0] === "base") { |  | ||||||
|     FRAGMENTS.push(...Object.keys(enMaster.ui.panel)); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // The downstream pipeline is setup first.  It hashes the merged data for |   return gulp | ||||||
|   // each locale, then fragmentizes and flattens the data for final output. |     .src(src) | ||||||
|   const translationFiles = await glob([ |     .pipe(transform((data, file) => lokaliseTransform(data, data, file))) | ||||||
|     `${inFrontendDir}/!(en).json`, |  | ||||||
|     ...(env.isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]), |  | ||||||
|   ]); |  | ||||||
|   const hashStream = new Transform({ |  | ||||||
|     objectMode: true, |  | ||||||
|     transform: async (file, _, callback) => { |  | ||||||
|       const hash = env.isProdBuild() |  | ||||||
|         ? createHash("md5").update(file.contents).digest("hex") |  | ||||||
|         : "dev"; |  | ||||||
|       HASHES.set(file.stem, hash); |  | ||||||
|       file.stem += `-${hash}`; |  | ||||||
|       callback(null, file); |  | ||||||
|     }, |  | ||||||
|   }).setMaxListeners(translationFiles.length + 1); |  | ||||||
|   const fragmentsStream = hashStream |  | ||||||
|     .pipe( |     .pipe( | ||||||
|       new CustomJSON((data) => |       merge({ | ||||||
|         FRAGMENTS.map((fragment) => { |         fileName: "en.json", | ||||||
|           switch (fragment) { |  | ||||||
|             case "base": |  | ||||||
|               // Remove the panels and supervisor to create the base translations |  | ||||||
|               return [ |  | ||||||
|                 flatten({ |  | ||||||
|                   ...data, |  | ||||||
|                   ui: { ...data.ui, panel: undefined }, |  | ||||||
|                   supervisor: undefined, |  | ||||||
|                 }), |  | ||||||
|                 "", |  | ||||||
|               ]; |  | ||||||
|             case "supervisor": |  | ||||||
|               // Supervisor key is at the top level |  | ||||||
|               return [flatten(data.supervisor), ""]; |  | ||||||
|             case "landing-page": |  | ||||||
|               // landing-page key is at the top level |  | ||||||
|               return [flatten(data["landing-page"]), ""]; |  | ||||||
|             default: |  | ||||||
|               // Create a fragment with only the given panel |  | ||||||
|               return [ |  | ||||||
|                 flatten(data.ui.panel[fragment], `ui.panel.${fragment}.`), |  | ||||||
|                 fragment, |  | ||||||
|               ]; |  | ||||||
|           } |  | ||||||
|       }) |       }) | ||||||
|     ) |     ) | ||||||
|     ) |     .pipe(gulp.dest(fullDir)); | ||||||
|     .pipe(gulp.dest(outDir)); | }); | ||||||
|  |  | ||||||
|   // Send the English master downstream first, then for each other locale | gulp.task("build-merged-translations", () => | ||||||
|   // generate merged JSON data to continue piping. It begins with the master |   gulp | ||||||
|  |     .src([ | ||||||
|  |       inFrontendDir + "/*.json", | ||||||
|  |       "!" + inFrontendDir + "/en.json", | ||||||
|  |       ...(env.isProdBuild() ? [] : [workDir + "/test.json"]), | ||||||
|  |     ]) | ||||||
|  |     .pipe(transform((data, file) => lokaliseTransform(data, data, file))) | ||||||
|  |     .pipe( | ||||||
|  |       flatmap((stream, file) => { | ||||||
|  |         // For each language generate a merged json file. It begins with the master | ||||||
|         // translation as a failsafe for untranslated strings, and merges all parent |         // translation as a failsafe for untranslated strings, and merges all parent | ||||||
|         // tags into one file for each specific subtag |         // tags into one file for each specific subtag | ||||||
|         // |         // | ||||||
|         // 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 tr = path.basename(file.history[0], ".json"); | ||||||
|     .src(`${workDir}/en.json`) |         const subtags = tr.split("-"); | ||||||
|     .pipe(new PassThrough({ objectMode: true })); |         const src = [fullDir + "/en.json"]; | ||||||
|   masterStream.pipe(hashStream, { end: false }); |  | ||||||
|   const mergesFinished = [finished(masterStream)]; |  | ||||||
|   for (const translationFile of translationFiles) { |  | ||||||
|     const locale = basename(translationFile, ".json"); |  | ||||||
|     const subtags = locale.split("-"); |  | ||||||
|     const mergeFiles = []; |  | ||||||
|         for (let i = 1; i <= subtags.length; i++) { |         for (let i = 1; i <= subtags.length; i++) { | ||||||
|           const lang = subtags.slice(0, i).join("-"); |           const lang = subtags.slice(0, i).join("-"); | ||||||
|       if (lang === TEST_LOCALE) { |           if (lang === "test") { | ||||||
|         mergeFiles.push(`${workDir}/${TEST_LOCALE}.json`); |             src.push(workDir + "/test.json"); | ||||||
|           } else if (lang !== "en") { |           } else if (lang !== "en") { | ||||||
|         mergeFiles.push(`${inFrontendDir}/${lang}.json`); |             src.push(inFrontendDir + "/" + lang + ".json"); | ||||||
|             if (mergeBackend) { |             if (mergeBackend) { | ||||||
|           mergeFiles.push(`${inBackendDir}/${lang}.json`); |               src.push(inBackendDir + "/" + lang + ".json"); | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|     const mergeStream = gulp |         return gulp | ||||||
|       .src(mergeFiles, { allowEmpty: true }) |           .src(src, { allowEmpty: true }) | ||||||
|       .pipe(new MergeJSON(locale, enMaster, emptyReviver)); |           .pipe(transform((data) => emptyFilter(data))) | ||||||
|     mergesFinished.push(finished(mergeStream)); |  | ||||||
|     mergeStream.pipe(hashStream, { end: false }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Wait for all merges to finish, then it's safe to end writing to the |  | ||||||
|   // downstream pipeline and wait for all fragments to finish writing. |  | ||||||
|   await Promise.all(mergesFinished); |  | ||||||
|   hashStream.end(); |  | ||||||
|   await finished(fragmentsStream); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const writeTranslationMetaData = () => |  | ||||||
|   gulp |  | ||||||
|     .src([`${paths.translations_src}/translationMetadata.json`]) |  | ||||||
|           .pipe( |           .pipe( | ||||||
|       new CustomJSON((meta) => { |             merge({ | ||||||
|         // Add the test translation in development. |               fileName: tr + ".json", | ||||||
|         if (!env.isProdBuild()) { |  | ||||||
|           meta[TEST_LOCALE] = { nativeName: "Translation Test" }; |  | ||||||
|         } |  | ||||||
|         // Filter out locales without a native name, and add the hashes. |  | ||||||
|         for (const locale of Object.keys(meta)) { |  | ||||||
|           if (!meta[locale].nativeName) { |  | ||||||
|             meta[locale] = undefined; |  | ||||||
|             console.warn( |  | ||||||
|               `Skipping locale ${locale} because native name is not translated.` |  | ||||||
|             ); |  | ||||||
|           } else { |  | ||||||
|             meta[locale].hash = HASHES.get(locale); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|         return { |  | ||||||
|           fragments: FRAGMENTS.filter(panelFragment), |  | ||||||
|           translations: meta, |  | ||||||
|         }; |  | ||||||
|             }) |             }) | ||||||
|           ) |           ) | ||||||
|     .pipe(gulp.dest(workDir)); |           .pipe(gulp.dest(fullDir)); | ||||||
|  |       }) | ||||||
|  |     ) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | let taskName; | ||||||
|  |  | ||||||
|  | const splitTasks = []; | ||||||
|  | TRANSLATION_FRAGMENTS.forEach((fragment) => { | ||||||
|  |   taskName = "build-translation-fragment-" + fragment; | ||||||
|  |   gulp.task(taskName, () => | ||||||
|  |     // Return only the translations for this fragment. | ||||||
|  |     gulp | ||||||
|  |       .src(fullDir + "/*.json") | ||||||
|  |       .pipe( | ||||||
|  |         transform((data) => ({ | ||||||
|  |           ui: { | ||||||
|  |             panel: { | ||||||
|  |               [fragment]: data.ui.panel[fragment], | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         })) | ||||||
|  |       ) | ||||||
|  |       .pipe(gulp.dest(workDir + "/" + fragment)) | ||||||
|  |   ); | ||||||
|  |   splitTasks.push(taskName); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | taskName = "build-translation-core"; | ||||||
|  | gulp.task(taskName, () => | ||||||
|  |   // Remove the fragment translations from the core translation. | ||||||
|  |   gulp | ||||||
|  |     .src(fullDir + "/*.json") | ||||||
|  |     .pipe( | ||||||
|  |       transform((data, _file) => { | ||||||
|  |         TRANSLATION_FRAGMENTS.forEach((fragment) => { | ||||||
|  |           delete data.ui.panel[fragment]; | ||||||
|  |         }); | ||||||
|  |         delete data.supervisor; | ||||||
|  |         return data; | ||||||
|  |       }) | ||||||
|  |     ) | ||||||
|  |     .pipe(gulp.dest(coreDir)) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | splitTasks.push(taskName); | ||||||
|  |  | ||||||
|  | gulp.task("build-flattened-translations", () => | ||||||
|  |   // Flatten the split versions of our translations, and move them into outDir | ||||||
|  |   gulp | ||||||
|  |     .src( | ||||||
|  |       TRANSLATION_FRAGMENTS.map( | ||||||
|  |         (fragment) => workDir + "/" + fragment + "/*.json" | ||||||
|  |       ).concat(coreDir + "/*.json"), | ||||||
|  |       { base: workDir } | ||||||
|  |     ) | ||||||
|  |     .pipe( | ||||||
|  |       transform((data) => | ||||||
|  |         // Polymer.AppLocalizeBehavior requires flattened json | ||||||
|  |         flatten(data) | ||||||
|  |       ) | ||||||
|  |     ) | ||||||
|  |     .pipe( | ||||||
|  |       rename((filePath) => { | ||||||
|  |         if (filePath.dirname === "core") { | ||||||
|  |           filePath.dirname = ""; | ||||||
|  |         } | ||||||
|  |         // In dev we create the file with the fake hash in the filename | ||||||
|  |         if (!env.isProdBuild()) { | ||||||
|  |           filePath.basename += "-dev"; | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     ) | ||||||
|  |     .pipe(gulp.dest(outDir)) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | const fingerprints = {}; | ||||||
|  |  | ||||||
|  | gulp.task("build-translation-fingerprints", () => { | ||||||
|  |   // Fingerprint full file of each language | ||||||
|  |   const files = readdirSync(fullDir); | ||||||
|  |  | ||||||
|  |   for (let i = 0; i < files.length; i++) { | ||||||
|  |     fingerprints[files[i].split(".")[0]] = { | ||||||
|  |       // In dev we create fake hashes | ||||||
|  |       hash: env.isProdBuild() | ||||||
|  |         ? createHash("md5") | ||||||
|  |             .update(readFileSync(path.join(fullDir, files[i]), "utf-8")) | ||||||
|  |             .digest("hex") | ||||||
|  |         : "dev", | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // In dev we create the file with the fake hash in the filename | ||||||
|  |   if (env.isProdBuild()) { | ||||||
|  |     mapFiles(outDir, ".json", (filename) => { | ||||||
|  |       const parsed = path.parse(filename); | ||||||
|  |  | ||||||
|  |       // nl.json -> nl-<hash>.json | ||||||
|  |       if (!(parsed.name in fingerprints)) { | ||||||
|  |         throw new Error(`Unable to find hash for ${filename}`); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       renameSync( | ||||||
|  |         filename, | ||||||
|  |         `${parsed.dir}/${parsed.name}-${fingerprints[parsed.name].hash}${ | ||||||
|  |           parsed.ext | ||||||
|  |         }` | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const stream = source("translationFingerprints.json"); | ||||||
|  |   stream.write(JSON.stringify(fingerprints)); | ||||||
|  |   process.nextTick(() => stream.end()); | ||||||
|  |   return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir)); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | gulp.task("build-translation-fragment-supervisor", () => | ||||||
|  |   gulp | ||||||
|  |     .src(fullDir + "/*.json") | ||||||
|  |     .pipe(transform((data) => data.supervisor)) | ||||||
|  |     .pipe( | ||||||
|  |       rename((filePath) => { | ||||||
|  |         // In dev we create the file with the fake hash in the filename | ||||||
|  |         if (!env.isProdBuild()) { | ||||||
|  |           filePath.basename += "-dev"; | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     ) | ||||||
|  |     .pipe(gulp.dest(workDir + "/supervisor")) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | gulp.task("build-translation-flatten-supervisor", () => | ||||||
|  |   gulp | ||||||
|  |     .src(workDir + "/supervisor/*.json") | ||||||
|  |     .pipe( | ||||||
|  |       transform((data) => | ||||||
|  |         // Polymer.AppLocalizeBehavior requires flattened json | ||||||
|  |         flatten(data) | ||||||
|  |       ) | ||||||
|  |     ) | ||||||
|  |     .pipe(gulp.dest(outDir)) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | gulp.task("build-translation-write-metadata", () => | ||||||
|  |   gulp | ||||||
|  |     .src([ | ||||||
|  |       path.join(paths.translations_src, "translationMetadata.json"), | ||||||
|  |       ...(env.isProdBuild() ? [] : [workDir + "/testMetadata.json"]), | ||||||
|  |       workDir + "/translationFingerprints.json", | ||||||
|  |     ]) | ||||||
|  |     .pipe(merge({})) | ||||||
|  |     .pipe( | ||||||
|  |       transform((data) => { | ||||||
|  |         const newData = {}; | ||||||
|  |         Object.entries(data).forEach(([key, value]) => { | ||||||
|  |           // Filter out translations without native name. | ||||||
|  |           if (value.nativeName) { | ||||||
|  |             newData[key] = value; | ||||||
|  |           } else { | ||||||
|  |             console.warn( | ||||||
|  |               `Skipping language ${key}. Native name was not translated.` | ||||||
|  |             ); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |         return newData; | ||||||
|  |       }) | ||||||
|  |     ) | ||||||
|  |     .pipe( | ||||||
|  |       transform((data) => ({ | ||||||
|  |         fragments: TRANSLATION_FRAGMENTS, | ||||||
|  |         translations: data, | ||||||
|  |       })) | ||||||
|  |     ) | ||||||
|  |     .pipe(rename("translationMetadata.json")) | ||||||
|  |     .pipe(gulp.dest(workDir)) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | gulp.task( | ||||||
|  |   "create-translations", | ||||||
|  |   gulp.series( | ||||||
|  |     gulp.parallel("create-test-metadata", "create-test-translation"), | ||||||
|  |     "build-master-translation", | ||||||
|  |     "build-merged-translations", | ||||||
|  |     gulp.parallel(...splitTasks), | ||||||
|  |     "build-flattened-translations" | ||||||
|  |   ) | ||||||
|  | ); | ||||||
|  |  | ||||||
| gulp.task( | gulp.task( | ||||||
|   "build-translations", |   "build-translations", | ||||||
|   gulp.series( |   gulp.series( | ||||||
|     gulp.parallel( |     gulp.parallel( | ||||||
|       "fetch-nightly-translations", |       "fetch-nightly-translations", | ||||||
|       gulp.series("clean-translations", makeWorkDir) |       gulp.series("clean-translations", "ensure-translations-build-dir") | ||||||
|     ), |     ), | ||||||
|     createTestTranslation, |     "create-translations", | ||||||
|     createMasterTranslation, |     "build-translation-fingerprints", | ||||||
|     createTranslations, |     "build-translation-write-metadata" | ||||||
|     writeTranslationMetaData |  | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| gulp.task( | gulp.task( | ||||||
|   "build-supervisor-translations", |   "build-supervisor-translations", | ||||||
|   gulp.series(setFragment("supervisor"), "build-translations") |   gulp.series( | ||||||
| ); |     gulp.parallel( | ||||||
|  |       "fetch-nightly-translations", | ||||||
| gulp.task( |       gulp.series("clean-translations", "ensure-translations-build-dir") | ||||||
|   "build-landing-page-translations", |     ), | ||||||
|   gulp.series(setFragment("landing-page"), "build-translations") |     gulp.parallel("create-test-metadata", "create-test-translation"), | ||||||
|  |     "build-master-translation", | ||||||
|  |     "build-merged-translations", | ||||||
|  |     "build-translation-fragment-supervisor", | ||||||
|  |     "build-translation-flatten-supervisor", | ||||||
|  |     "build-translation-fingerprints", | ||||||
|  |     "build-translation-write-metadata" | ||||||
|  |   ) | ||||||
| ); | ); | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								build-scripts/gulp/wds.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								build-scripts/gulp/wds.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | import gulp from "gulp"; | ||||||
|  | import { startDevServer } from "@web/dev-server"; | ||||||
|  |  | ||||||
|  | gulp.task("wds-watch-app", async () => { | ||||||
|  |   startDevServer({ | ||||||
|  |     config: { | ||||||
|  |       watch: true, | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -1,11 +1,11 @@ | |||||||
| // Tasks to run rspack.
 | // Tasks to run webpack.
 | ||||||
| 
 | 
 | ||||||
| import fs from "fs"; | import fs from "fs"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import log from "fancy-log"; | import log from "fancy-log"; | ||||||
| import gulp from "gulp"; | import gulp from "gulp"; | ||||||
| import rspack from "@rspack/core"; | import webpack from "webpack"; | ||||||
| import { RspackDevServer } from "@rspack/dev-server"; | import WebpackDevServer from "webpack-dev-server"; | ||||||
| import env from "../env.cjs"; | import env from "../env.cjs"; | ||||||
| import paths from "../paths.cjs"; | import paths from "../paths.cjs"; | ||||||
| import { | import { | ||||||
| @@ -14,8 +14,7 @@ import { | |||||||
|   createDemoConfig, |   createDemoConfig, | ||||||
|   createGalleryConfig, |   createGalleryConfig, | ||||||
|   createHassioConfig, |   createHassioConfig, | ||||||
|   createLandingPageConfig, | } from "../webpack.cjs"; | ||||||
| } from "../rspack.cjs"; |  | ||||||
| 
 | 
 | ||||||
| const bothBuilds = (createConfigFunc, params) => [ | const bothBuilds = (createConfigFunc, params) => [ | ||||||
|   createConfigFunc({ ...params, latestBuild: true }), |   createConfigFunc({ ...params, latestBuild: true }), | ||||||
| @@ -31,7 +30,7 @@ const isWsl = | |||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {{ |  * @param {{ | ||||||
|  *   compiler: import("@rspack/core").Compiler, |  *   compiler: import("webpack").Compiler, | ||||||
|  *   contentBase: string, |  *   contentBase: string, | ||||||
|  *   port: number, |  *   port: number, | ||||||
|  *   listenHost?: string |  *   listenHost?: string | ||||||
| @@ -41,14 +40,9 @@ const runDevServer = async ({ | |||||||
|   compiler, |   compiler, | ||||||
|   contentBase, |   contentBase, | ||||||
|   port, |   port, | ||||||
|   listenHost = undefined, |   listenHost = "localhost", | ||||||
|   proxy = undefined, |  | ||||||
| }) => { | }) => { | ||||||
|   if (listenHost === undefined) { |   const server = new WebpackDevServer( | ||||||
|     // For dev container, we need to listen on all hosts
 |  | ||||||
|     listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost"; |  | ||||||
|   } |  | ||||||
|   const server = new RspackDevServer( |  | ||||||
|     { |     { | ||||||
|       hot: false, |       hot: false, | ||||||
|       open: true, |       open: true, | ||||||
| @@ -58,14 +52,13 @@ const runDevServer = async ({ | |||||||
|         directory: contentBase, |         directory: contentBase, | ||||||
|         watch: true, |         watch: true, | ||||||
|       }, |       }, | ||||||
|       proxy, |  | ||||||
|     }, |     }, | ||||||
|     compiler |     compiler | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   await server.start(); |   await server.start(); | ||||||
|   // Server listening
 |   // Server listening
 | ||||||
|   log("[rspack-dev-server]", `Project is running at http://localhost:${port}`); |   log("[webpack-dev-server]", `Project is running at http://localhost:${port}`); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const doneHandler = (done) => (err, stats) => { | const doneHandler = (done) => (err, stats) => { | ||||||
| @@ -90,27 +83,27 @@ const doneHandler = (done) => (err, stats) => { | |||||||
| 
 | 
 | ||||||
| const prodBuild = (conf) => | const prodBuild = (conf) => | ||||||
|   new Promise((resolve) => { |   new Promise((resolve) => { | ||||||
|     rspack( |     webpack( | ||||||
|       conf, |       conf, | ||||||
|       // Resolve promise when done. Because we pass a callback, rspack closes itself
 |       // Resolve promise when done. Because we pass a callback, webpack closes itself
 | ||||||
|       doneHandler(resolve) |       doneHandler(resolve) | ||||||
|     ); |     ); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
| gulp.task("rspack-watch-app", () => { | gulp.task("webpack-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( |   webpack( | ||||||
|     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( |   gulp.watch( | ||||||
|     path.join(paths.translations_src, "en.json"), |     path.join(paths.translations_src, "en.json"), | ||||||
|     gulp.series("build-translations", "copy-translations-app") |     gulp.series("create-translations", "copy-translations-app") | ||||||
|   ); |   ); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| gulp.task("rspack-prod-app", () => | gulp.task("webpack-prod-app", () => | ||||||
|   prodBuild( |   prodBuild( | ||||||
|     bothBuilds(createAppConfig, { |     bothBuilds(createAppConfig, { | ||||||
|       isProdBuild: true, |       isProdBuild: true, | ||||||
| @@ -120,9 +113,9 @@ gulp.task("rspack-prod-app", () => | |||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| gulp.task("rspack-dev-server-demo", () => | gulp.task("webpack-dev-server-demo", () => | ||||||
|   runDevServer({ |   runDevServer({ | ||||||
|     compiler: rspack( |     compiler: webpack( | ||||||
|       createDemoConfig({ isProdBuild: false, latestBuild: true }) |       createDemoConfig({ isProdBuild: false, latestBuild: true }) | ||||||
|     ), |     ), | ||||||
|     contentBase: paths.demo_output_root, |     contentBase: paths.demo_output_root, | ||||||
| @@ -130,18 +123,17 @@ gulp.task("rspack-dev-server-demo", () => | |||||||
|   }) |   }) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| gulp.task("rspack-prod-demo", () => | gulp.task("webpack-prod-demo", () => | ||||||
|   prodBuild( |   prodBuild( | ||||||
|     bothBuilds(createDemoConfig, { |     bothBuilds(createDemoConfig, { | ||||||
|       isProdBuild: true, |       isProdBuild: true, | ||||||
|       isStatsBuild: env.isStatsBuild(), |  | ||||||
|     }) |     }) | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| gulp.task("rspack-dev-server-cast", () => | gulp.task("webpack-dev-server-cast", () => | ||||||
|   runDevServer({ |   runDevServer({ | ||||||
|     compiler: rspack( |     compiler: webpack( | ||||||
|       createCastConfig({ isProdBuild: false, latestBuild: true }) |       createCastConfig({ isProdBuild: false, latestBuild: true }) | ||||||
|     ), |     ), | ||||||
|     contentBase: paths.cast_output_root, |     contentBase: paths.cast_output_root, | ||||||
| @@ -151,7 +143,7 @@ gulp.task("rspack-dev-server-cast", () => | |||||||
|   }) |   }) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| gulp.task("rspack-prod-cast", () => | gulp.task("webpack-prod-cast", () => | ||||||
|   prodBuild( |   prodBuild( | ||||||
|     bothBuilds(createCastConfig, { |     bothBuilds(createCastConfig, { | ||||||
|       isProdBuild: true, |       isProdBuild: true, | ||||||
| @@ -159,9 +151,9 @@ gulp.task("rspack-prod-cast", () => | |||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| gulp.task("rspack-watch-hassio", () => { | gulp.task("webpack-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( |   webpack( | ||||||
|     createHassioConfig({ |     createHassioConfig({ | ||||||
|       isProdBuild: false, |       isProdBuild: false, | ||||||
|       latestBuild: true, |       latestBuild: true, | ||||||
| @@ -174,7 +166,7 @@ gulp.task("rspack-watch-hassio", () => { | |||||||
|   ); |   ); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| gulp.task("rspack-prod-hassio", () => | gulp.task("webpack-prod-hassio", () => | ||||||
|   prodBuild( |   prodBuild( | ||||||
|     bothBuilds(createHassioConfig, { |     bothBuilds(createHassioConfig, { | ||||||
|       isProdBuild: true, |       isProdBuild: true, | ||||||
| @@ -184,9 +176,9 @@ gulp.task("rspack-prod-hassio", () => | |||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| gulp.task("rspack-dev-server-gallery", () => | gulp.task("webpack-dev-server-gallery", () => | ||||||
|   runDevServer({ |   runDevServer({ | ||||||
|     compiler: rspack( |     compiler: webpack( | ||||||
|       createGalleryConfig({ isProdBuild: false, latestBuild: true }) |       createGalleryConfig({ isProdBuild: false, latestBuild: true }) | ||||||
|     ), |     ), | ||||||
|     contentBase: paths.gallery_output_root, |     contentBase: paths.gallery_output_root, | ||||||
| @@ -195,7 +187,7 @@ gulp.task("rspack-dev-server-gallery", () => | |||||||
|   }) |   }) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| gulp.task("rspack-prod-gallery", () => | gulp.task("webpack-prod-gallery", () => | ||||||
|   prodBuild( |   prodBuild( | ||||||
|     createGalleryConfig({ |     createGalleryConfig({ | ||||||
|       isProdBuild: true, |       isProdBuild: true, | ||||||
| @@ -203,30 +195,3 @@ gulp.task("rspack-prod-gallery", () => | |||||||
|     }) |     }) | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
| 
 |  | ||||||
| gulp.task("rspack-watch-landing-page", () => { |  | ||||||
|   // This command will run forever because we don't close compiler
 |  | ||||||
|   rspack( |  | ||||||
|     process.env.ES5 |  | ||||||
|       ? bothBuilds(createLandingPageConfig, { isProdBuild: false }) |  | ||||||
|       : createLandingPageConfig({ isProdBuild: false, latestBuild: true }) |  | ||||||
|   ).watch({ poll: isWsl }, doneHandler()); |  | ||||||
| 
 |  | ||||||
|   gulp.watch( |  | ||||||
|     path.join(paths.translations_src, "en.json"), |  | ||||||
|     gulp.series( |  | ||||||
|       "build-landing-page-translations", |  | ||||||
|       "copy-translations-landing-page" |  | ||||||
|     ) |  | ||||||
|   ); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| gulp.task("rspack-prod-landing-page", () => |  | ||||||
|   prodBuild( |  | ||||||
|     bothBuilds(createLandingPageConfig, { |  | ||||||
|       isProdBuild: true, |  | ||||||
|       isStatsBuild: env.isStatsBuild(), |  | ||||||
|       isTestBuild: env.isTestBuild(), |  | ||||||
|     }) |  | ||||||
|   ) |  | ||||||
| ); |  | ||||||
| @@ -16,7 +16,6 @@ const detailsClose = "</details>\n"; | |||||||
|  |  | ||||||
| const dummyAPI = { | const dummyAPI = { | ||||||
|   version: babelVersion, |   version: babelVersion, | ||||||
|   // eslint-disable-next-line @typescript-eslint/no-empty-function |  | ||||||
|   assertVersion: () => {}, |   assertVersion: () => {}, | ||||||
|   caller: (callback) => |   caller: (callback) => | ||||||
|     callback({ |     callback({ | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| const path = require("path"); | const path = require("path"); | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   root_dir: path.resolve(__dirname, ".."), |   polymer_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"), | ||||||
| @@ -33,22 +33,6 @@ module.exports = { | |||||||
|   ), |   ), | ||||||
|   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_build: path.resolve(__dirname, "../landing-page/build"), |  | ||||||
|   landingPage_output_root: path.resolve(__dirname, "../landing-page/dist"), |  | ||||||
|   landingPage_output_latest: path.resolve( |  | ||||||
|     __dirname, |  | ||||||
|     "../landing-page/dist/frontend_latest" |  | ||||||
|   ), |  | ||||||
|   landingPage_output_es5: path.resolve( |  | ||||||
|     __dirname, |  | ||||||
|     "../landing-page/dist/frontend_es5" |  | ||||||
|   ), |  | ||||||
|   landingPage_output_static: path.resolve( |  | ||||||
|     __dirname, |  | ||||||
|     "../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"), | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								build-scripts/rollup-plugins/dont-hash-plugin.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								build-scripts/rollup-plugins/dont-hash-plugin.cjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | module.exports = function (opts = {}) { | ||||||
|  |   const dontHash = opts.dontHash || new Set(); | ||||||
|  |  | ||||||
|  |   return { | ||||||
|  |     name: "dont-hash", | ||||||
|  |     renderChunk(_code, chunk, _options) { | ||||||
|  |       if (!chunk.isEntry || !dontHash.has(chunk.name)) { | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  |       chunk.fileName = `${chunk.name}.js`; | ||||||
|  |       return null; | ||||||
|  |     }, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
							
								
								
									
										24
									
								
								build-scripts/rollup-plugins/ignore-plugin.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								build-scripts/rollup-plugins/ignore-plugin.cjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | module.exports = function (userOptions = {}) { | ||||||
|  |   // Files need to be absolute paths. | ||||||
|  |   // This only works if the file has no exports | ||||||
|  |   // and only is imported for its side effects | ||||||
|  |   const files = userOptions.files || []; | ||||||
|  |  | ||||||
|  |   if (files.length === 0) { | ||||||
|  |     return { | ||||||
|  |       name: "ignore", | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return { | ||||||
|  |     name: "ignore", | ||||||
|  |  | ||||||
|  |     load(id) { | ||||||
|  |       return files.some((toIgnorePath) => id.startsWith(toIgnorePath)) | ||||||
|  |         ? { | ||||||
|  |             code: "", | ||||||
|  |           } | ||||||
|  |         : null; | ||||||
|  |     }, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
							
								
								
									
										34
									
								
								build-scripts/rollup-plugins/manifest-plugin.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								build-scripts/rollup-plugins/manifest-plugin.cjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | const url = require("url"); | ||||||
|  |  | ||||||
|  | const defaultOptions = { | ||||||
|  |   publicPath: "", | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module.exports = function (userOptions = {}) { | ||||||
|  |   const options = { ...defaultOptions, ...userOptions }; | ||||||
|  |  | ||||||
|  |   return { | ||||||
|  |     name: "manifest", | ||||||
|  |     generateBundle(outputOptions, bundle) { | ||||||
|  |       const manifest = {}; | ||||||
|  |  | ||||||
|  |       for (const chunk of Object.values(bundle)) { | ||||||
|  |         if (!chunk.isEntry) { | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |         // Add js extension to mimic Webpack manifest. | ||||||
|  |         manifest[`${chunk.name}.js`] = url.resolve( | ||||||
|  |           options.publicPath, | ||||||
|  |           chunk.fileName | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       this.emitFile({ | ||||||
|  |         type: "asset", | ||||||
|  |         source: JSON.stringify(manifest, undefined, 2), | ||||||
|  |         name: "manifest.json", | ||||||
|  |         fileName: "manifest.json", | ||||||
|  |       }); | ||||||
|  |     }, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
							
								
								
									
										152
									
								
								build-scripts/rollup-plugins/worker-plugin.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								build-scripts/rollup-plugins/worker-plugin.cjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | // Worker plugin | ||||||
|  | // Each worker will include all of its dependencies | ||||||
|  | // instead of relying on an importer. | ||||||
|  |  | ||||||
|  | // Forked from v.1.4.1 | ||||||
|  | // https://github.com/surma/rollup-plugin-off-main-thread | ||||||
|  | /** | ||||||
|  |  * Copyright 2018 Google Inc. All Rights Reserved. | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | const rollup = require("rollup"); | ||||||
|  | const path = require("path"); | ||||||
|  | const MagicString = require("magic-string"); | ||||||
|  |  | ||||||
|  | const defaultOpts = { | ||||||
|  |   // A RegExp to find `new Workers()` calls. The second capture group _must_ | ||||||
|  |   // capture the provided file name without the quotes. | ||||||
|  |   workerRegexp: /new Worker\((["'])(.+?)\1(,[^)]+)?\)/g, | ||||||
|  |   plugins: ["node-resolve", "commonjs", "babel", "terser", "ignore"], | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | async function getBundledWorker(workerPath, rollupOptions) { | ||||||
|  |   const bundle = await rollup.rollup({ | ||||||
|  |     ...rollupOptions, | ||||||
|  |     input: { | ||||||
|  |       worker: workerPath, | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  |   const { output } = await bundle.generate({ | ||||||
|  |     // Generates cleanest output, we shouldn't have any imports/exports | ||||||
|  |     // that would be incompatible with ES5. | ||||||
|  |     format: "es", | ||||||
|  |     // We should not export anything. This will fail build if we are. | ||||||
|  |     exports: "none", | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   let code; | ||||||
|  |  | ||||||
|  |   for (const chunkOrAsset of output) { | ||||||
|  |     if (chunkOrAsset.name === "worker") { | ||||||
|  |       code = chunkOrAsset.code; | ||||||
|  |     } else if (chunkOrAsset.type !== "asset") { | ||||||
|  |       throw new Error("Unexpected extra output"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return code; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = function (opts = {}) { | ||||||
|  |   opts = { ...defaultOpts, ...opts }; | ||||||
|  |  | ||||||
|  |   let rollupOptions; | ||||||
|  |   let refIds; | ||||||
|  |  | ||||||
|  |   return { | ||||||
|  |     name: "hass-worker", | ||||||
|  |  | ||||||
|  |     async buildStart(options) { | ||||||
|  |       refIds = {}; | ||||||
|  |       rollupOptions = { | ||||||
|  |         plugins: options.plugins.filter((plugin) => | ||||||
|  |           opts.plugins.includes(plugin.name) | ||||||
|  |         ), | ||||||
|  |       }; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     async transform(code, id) { | ||||||
|  |       // Copy the regexp as they are stateful and this hook is async. | ||||||
|  |       const workerRegexp = new RegExp( | ||||||
|  |         opts.workerRegexp.source, | ||||||
|  |         opts.workerRegexp.flags | ||||||
|  |       ); | ||||||
|  |       if (!workerRegexp.test(code)) { | ||||||
|  |         return undefined; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       const ms = new MagicString(code); | ||||||
|  |       // Reset the regexp | ||||||
|  |       workerRegexp.lastIndex = 0; | ||||||
|  |       for (;;) { | ||||||
|  |         const match = workerRegexp.exec(code); | ||||||
|  |         if (!match) { | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const workerFile = match[2]; | ||||||
|  |         let optionsObject = {}; | ||||||
|  |         // Parse the optional options object | ||||||
|  |         if (match[3] && match[3].length > 0) { | ||||||
|  |           // FIXME: ooooof! | ||||||
|  |           // eslint-disable-next-line @typescript-eslint/no-implied-eval | ||||||
|  |           optionsObject = new Function(`return ${match[3].slice(1)};`)(); | ||||||
|  |         } | ||||||
|  |         delete optionsObject.type; | ||||||
|  |  | ||||||
|  |         if (!/^.*\//.test(workerFile)) { | ||||||
|  |           this.warn( | ||||||
|  |             `Paths passed to the Worker constructor must be relative or absolute, i.e. start with /, ./ or ../ (just like dynamic import!). Ignoring "${workerFile}".` | ||||||
|  |           ); | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Find worker file and store it as a chunk with ID prefixed for our loader | ||||||
|  |         // eslint-disable-next-line no-await-in-loop | ||||||
|  |         const resolvedWorkerFile = (await this.resolve(workerFile, id)).id; | ||||||
|  |         let chunkRefId; | ||||||
|  |         if (resolvedWorkerFile in refIds) { | ||||||
|  |           chunkRefId = refIds[resolvedWorkerFile]; | ||||||
|  |         } else { | ||||||
|  |           this.addWatchFile(resolvedWorkerFile); | ||||||
|  |           // eslint-disable-next-line no-await-in-loop | ||||||
|  |           const source = await getBundledWorker( | ||||||
|  |             resolvedWorkerFile, | ||||||
|  |             rollupOptions | ||||||
|  |           ); | ||||||
|  |           chunkRefId = refIds[resolvedWorkerFile] = this.emitFile({ | ||||||
|  |             name: path.basename(resolvedWorkerFile), | ||||||
|  |             source, | ||||||
|  |             type: "asset", | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const workerParametersStartIndex = match.index + "new Worker(".length; | ||||||
|  |         const workerParametersEndIndex = | ||||||
|  |           match.index + match[0].length - ")".length; | ||||||
|  |  | ||||||
|  |         ms.overwrite( | ||||||
|  |           workerParametersStartIndex, | ||||||
|  |           workerParametersEndIndex, | ||||||
|  |           `import.meta.ROLLUP_FILE_URL_${chunkRefId}, ${JSON.stringify( | ||||||
|  |             optionsObject | ||||||
|  |           )}` | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       return { | ||||||
|  |         code: ms.toString(), | ||||||
|  |         map: ms.generateMap({ hires: true }), | ||||||
|  |       }; | ||||||
|  |     }, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
							
								
								
									
										146
									
								
								build-scripts/rollup.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								build-scripts/rollup.cjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | |||||||
|  | const path = require("path"); | ||||||
|  |  | ||||||
|  | const commonjs = require("@rollup/plugin-commonjs"); | ||||||
|  | const resolve = require("@rollup/plugin-node-resolve"); | ||||||
|  | const json = require("@rollup/plugin-json"); | ||||||
|  | const { babel } = require("@rollup/plugin-babel"); | ||||||
|  | const replace = require("@rollup/plugin-replace"); | ||||||
|  | const visualizer = require("rollup-plugin-visualizer"); | ||||||
|  | const { string } = require("rollup-plugin-string"); | ||||||
|  | const { terser } = require("rollup-plugin-terser"); | ||||||
|  | const manifest = require("./rollup-plugins/manifest-plugin.cjs"); | ||||||
|  | const worker = require("./rollup-plugins/worker-plugin.cjs"); | ||||||
|  | const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin.cjs"); | ||||||
|  | const ignore = require("./rollup-plugins/ignore-plugin.cjs"); | ||||||
|  |  | ||||||
|  | const bundle = require("./bundle.cjs"); | ||||||
|  | const paths = require("./paths.cjs"); | ||||||
|  |  | ||||||
|  | const extensions = [".js", ".ts"]; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @param {Object} arg | ||||||
|  |  * @param { import("rollup").InputOption } arg.input | ||||||
|  |  */ | ||||||
|  | const createRollupConfig = ({ | ||||||
|  |   entry, | ||||||
|  |   outputPath, | ||||||
|  |   defineOverlay, | ||||||
|  |   isProdBuild, | ||||||
|  |   latestBuild, | ||||||
|  |   isStatsBuild, | ||||||
|  |   publicPath, | ||||||
|  |   dontHash, | ||||||
|  |   isWDS, | ||||||
|  | }) => ({ | ||||||
|  |   /** | ||||||
|  |    * @type { import("rollup").InputOptions } | ||||||
|  |    */ | ||||||
|  |   inputOptions: { | ||||||
|  |     input: entry, | ||||||
|  |     // Some entry points contain no JavaScript. This setting silences a warning about that. | ||||||
|  |     // https://rollupjs.org/configuration-options/#preserveentrysignatures | ||||||
|  |     preserveEntrySignatures: false, | ||||||
|  |     plugins: [ | ||||||
|  |       ignore({ | ||||||
|  |         files: bundle | ||||||
|  |           .emptyPackages({ latestBuild }) | ||||||
|  |           // TEMP HACK: Makes Rollup build work again | ||||||
|  |           .concat( | ||||||
|  |             require.resolve( | ||||||
|  |               "@webcomponents/scoped-custom-element-registry/scoped-custom-element-registry.min" | ||||||
|  |             ) | ||||||
|  |           ), | ||||||
|  |       }), | ||||||
|  |       resolve({ | ||||||
|  |         extensions, | ||||||
|  |         preferBuiltins: false, | ||||||
|  |         browser: true, | ||||||
|  |         rootDir: paths.polymer_dir, | ||||||
|  |       }), | ||||||
|  |       commonjs(), | ||||||
|  |       json(), | ||||||
|  |       babel({ | ||||||
|  |         ...bundle.babelOptions({ latestBuild, isProdBuild }), | ||||||
|  |         extensions, | ||||||
|  |         babelHelpers: isWDS ? "inline" : "bundled", | ||||||
|  |       }), | ||||||
|  |       string({ | ||||||
|  |         // Import certain extensions as strings | ||||||
|  |         include: [path.join(paths.polymer_dir, "node_modules/**/*.css")], | ||||||
|  |       }), | ||||||
|  |       replace(bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })), | ||||||
|  |       !isWDS && | ||||||
|  |         manifest({ | ||||||
|  |           publicPath, | ||||||
|  |         }), | ||||||
|  |       !isWDS && worker(), | ||||||
|  |       !isWDS && dontHashPlugin({ dontHash }), | ||||||
|  |       !isWDS && isProdBuild && terser(bundle.terserOptions({ latestBuild })), | ||||||
|  |       !isWDS && | ||||||
|  |         isStatsBuild && | ||||||
|  |         visualizer({ | ||||||
|  |           // https://github.com/btd/rollup-plugin-visualizer#options | ||||||
|  |           open: true, | ||||||
|  |           sourcemap: true, | ||||||
|  |         }), | ||||||
|  |     ].filter(Boolean), | ||||||
|  |   }, | ||||||
|  |   /** | ||||||
|  |    * @type { import("rollup").OutputOptions } | ||||||
|  |    */ | ||||||
|  |   outputOptions: { | ||||||
|  |     // https://rollupjs.org/configuration-options/#output-dir | ||||||
|  |     dir: outputPath, | ||||||
|  |     // https://rollupjs.org/configuration-options/#output-format | ||||||
|  |     format: latestBuild ? "es" : "systemjs", | ||||||
|  |     // https://rollupjs.org/configuration-options/#output-externallivebindings | ||||||
|  |     externalLiveBindings: false, | ||||||
|  |     // https://rollupjs.org/configuration-options/#output-entryfilenames | ||||||
|  |     // https://rollupjs.org/configuration-options/#output-chunkfilenames | ||||||
|  |     // https://rollupjs.org/configuration-options/#output-assetfilenames | ||||||
|  |     entryFileNames: | ||||||
|  |       isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js", | ||||||
|  |     chunkFileNames: isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js", | ||||||
|  |     assetFileNames: isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js", | ||||||
|  |     // https://rollupjs.org/configuration-options/#output-sourcemap | ||||||
|  |     sourcemap: isProdBuild ? true : "inline", | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) => | ||||||
|  |   createRollupConfig( | ||||||
|  |     bundle.config.app({ | ||||||
|  |       isProdBuild, | ||||||
|  |       latestBuild, | ||||||
|  |       isStatsBuild, | ||||||
|  |       isWDS, | ||||||
|  |     }) | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  | const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => | ||||||
|  |   createRollupConfig( | ||||||
|  |     bundle.config.demo({ | ||||||
|  |       isProdBuild, | ||||||
|  |       latestBuild, | ||||||
|  |       isStatsBuild, | ||||||
|  |     }) | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  | const createCastConfig = ({ isProdBuild, latestBuild }) => | ||||||
|  |   createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild })); | ||||||
|  |  | ||||||
|  | const createHassioConfig = ({ isProdBuild, latestBuild }) => | ||||||
|  |   createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild })); | ||||||
|  |  | ||||||
|  | const createGalleryConfig = ({ isProdBuild, latestBuild }) => | ||||||
|  |   createRollupConfig(bundle.config.gallery({ isProdBuild, latestBuild })); | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |   createAppConfig, | ||||||
|  |   createDemoConfig, | ||||||
|  |   createCastConfig, | ||||||
|  |   createHassioConfig, | ||||||
|  |   createGalleryConfig, | ||||||
|  |   createRollupConfig, | ||||||
|  | }; | ||||||
							
								
								
									
										16
									
								
								build-scripts/util.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								build-scripts/util.cjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | const path = require("path"); | ||||||
|  | const fs = require("fs"); | ||||||
|  |  | ||||||
|  | // Helper function to map recursively over files in a folder and it's subfolders | ||||||
|  | module.exports.mapFiles = function mapFiles(startPath, filter, mapFunc) { | ||||||
|  |   const files = fs.readdirSync(startPath); | ||||||
|  |   for (let i = 0; i < files.length; i++) { | ||||||
|  |     const filename = path.join(startPath, files[i]); | ||||||
|  |     const stat = fs.lstatSync(filename); | ||||||
|  |     if (stat.isDirectory()) { | ||||||
|  |       mapFiles(filename, filter, mapFunc); | ||||||
|  |     } else if (filename.indexOf(filter) >= 0) { | ||||||
|  |       mapFunc(filename); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
| @@ -1,18 +1,15 @@ | |||||||
| const { existsSync } = require("fs"); | const { existsSync } = require("fs"); | ||||||
| const path = require("path"); | const path = require("path"); | ||||||
| const rspack = require("@rspack/core"); | const webpack = require("webpack"); | ||||||
| // eslint-disable-next-line @typescript-eslint/naming-convention
 |  | ||||||
| const { RsdoctorRspackPlugin } = require("@rsdoctor/rspack-plugin"); |  | ||||||
| // eslint-disable-next-line @typescript-eslint/naming-convention
 |  | ||||||
| const { StatsWriterPlugin } = require("webpack-stats-plugin"); | const { StatsWriterPlugin } = require("webpack-stats-plugin"); | ||||||
| const filterStats = require("@bundle-stats/plugin-webpack-filter"); | const filterStats = require("@bundle-stats/plugin-webpack-filter").default; | ||||||
| // eslint-disable-next-line @typescript-eslint/naming-convention
 |  | ||||||
| const TerserPlugin = require("terser-webpack-plugin"); | const TerserPlugin = require("terser-webpack-plugin"); | ||||||
| // eslint-disable-next-line @typescript-eslint/naming-convention
 | const { WebpackManifestPlugin } = require("webpack-manifest-plugin"); | ||||||
| const { WebpackManifestPlugin } = require("rspack-manifest-plugin"); |  | ||||||
| const log = require("fancy-log"); | const log = require("fancy-log"); | ||||||
| // eslint-disable-next-line @typescript-eslint/naming-convention
 | const WebpackBar = require("webpackbar"); | ||||||
| const WebpackBar = require("webpackbar/rspack"); | const { | ||||||
|  |   TransformAsyncModulesPlugin, | ||||||
|  | } = require("transform-async-modules-webpack-plugin"); | ||||||
| const paths = require("./paths.cjs"); | const paths = require("./paths.cjs"); | ||||||
| const bundle = require("./bundle.cjs"); | const bundle = require("./bundle.cjs"); | ||||||
| 
 | 
 | ||||||
| @@ -30,7 +27,7 @@ class LogStartCompilePlugin { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const createRspackConfig = ({ | const createWebpackConfig = ({ | ||||||
|   name, |   name, | ||||||
|   entry, |   entry, | ||||||
|   outputPath, |   outputPath, | ||||||
| @@ -65,32 +62,17 @@ const createRspackConfig = ({ | |||||||
|       rules: [ |       rules: [ | ||||||
|         { |         { | ||||||
|           test: /\.m?js$|\.ts$/, |           test: /\.m?js$|\.ts$/, | ||||||
|           exclude: /node_modules[\\/]core-js/, |           use: { | ||||||
|           use: (info) => [ |  | ||||||
|             { |  | ||||||
|             loader: "babel-loader", |             loader: "babel-loader", | ||||||
|             options: { |             options: { | ||||||
|                 ...bundle.babelOptions({ |               ...bundle.babelOptions({ latestBuild, isProdBuild, isTestBuild }), | ||||||
|                   latestBuild, |  | ||||||
|                   isProdBuild, |  | ||||||
|                   isTestBuild, |  | ||||||
|                   sw: info.issuerLayer === "sw", |  | ||||||
|                 }), |  | ||||||
|               cacheDirectory: !isProdBuild, |               cacheDirectory: !isProdBuild, | ||||||
|               cacheCompression: false, |               cacheCompression: false, | ||||||
|             }, |             }, | ||||||
|           }, |           }, | ||||||
|             { |  | ||||||
|               loader: "builtin:swc-loader", |  | ||||||
|               options: bundle.swcOptions(), |  | ||||||
|             }, |  | ||||||
|           ], |  | ||||||
|           resolve: { |           resolve: { | ||||||
|             fullySpecified: false, |             fullySpecified: false, | ||||||
|           }, |           }, | ||||||
|           parser: { |  | ||||||
|             worker: ["*context.audioWorklet.addModule()", "..."], |  | ||||||
|           }, |  | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           test: /\.css$/, |           test: /\.css$/, | ||||||
| @@ -109,20 +91,11 @@ const createRspackConfig = ({ | |||||||
|       moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", |       moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", | ||||||
|       chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", |       chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", | ||||||
|       splitChunks: { |       splitChunks: { | ||||||
|         // Disable splitting for web workers and worklets because imports of
 |         // Disable splitting for web workers with ESM output
 | ||||||
|         // external chunks are broken for:
 |         // Imports of external chunks are broken
 | ||||||
|         chunks: !isProdBuild |         chunks: latestBuild | ||||||
|           ? // improve incremental build speed, but blows up bundle size
 |           ? (chunk) => !chunk.canBeInitial() && !/^.+-worker$/.test(chunk.name) | ||||||
|             new RegExp( |           : undefined, | ||||||
|               `^(?!(${Object.keys(entry).join("|")}|.*work(?:er|let))$)` |  | ||||||
|             ) |  | ||||||
|           : // - ESM output: https://github.com/webpack/webpack/issues/17014
 |  | ||||||
|             // - Worklets use `importScripts`: https://github.com/webpack/webpack/issues/11543
 |  | ||||||
|             (chunk) => |  | ||||||
|               !chunk.canBeInitial() && |  | ||||||
|               !new RegExp( |  | ||||||
|                 `^.+-work${latestBuild ? "(?:let|er)" : "let"}$` |  | ||||||
|               ).test(chunk.name), |  | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|     plugins: [ |     plugins: [ | ||||||
| @@ -131,10 +104,10 @@ 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 webpack.DefinePlugin( | ||||||
|         bundle.definedVars({ isProdBuild, latestBuild, defineOverlay }) |         bundle.definedVars({ isProdBuild, latestBuild, defineOverlay }) | ||||||
|       ), |       ), | ||||||
|       new rspack.IgnorePlugin({ |       new webpack.IgnorePlugin({ | ||||||
|         checkResource(resource, context) { |         checkResource(resource, context) { | ||||||
|           // Only use ignore to intercept imports that we don't control
 |           // Only use ignore to intercept imports that we don't control
 | ||||||
|           // inside node_module dependencies.
 |           // inside node_module dependencies.
 | ||||||
| @@ -143,8 +116,7 @@ const createRspackConfig = ({ | |||||||
|             // calling define.amd will call require("!!webpack amd options")
 |             // calling define.amd will call require("!!webpack amd options")
 | ||||||
|             resource.startsWith("!!webpack") || |             resource.startsWith("!!webpack") || | ||||||
|             // loaded by webpack dev server but doesn't exist.
 |             // loaded by webpack dev server but doesn't exist.
 | ||||||
|             resource === "webpack/hot" || |             resource === "webpack/hot" | ||||||
|             resource.startsWith("@swc/helpers") |  | ||||||
|           ) { |           ) { | ||||||
|             return false; |             return false; | ||||||
|           } |           } | ||||||
| @@ -167,9 +139,11 @@ const createRspackConfig = ({ | |||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|       }), |       }), | ||||||
|       new rspack.NormalModuleReplacementPlugin( |       new webpack.NormalModuleReplacementPlugin( | ||||||
|         new RegExp(bundle.emptyPackages({ isHassioBuild }).join("|")), |         new RegExp( | ||||||
|         path.resolve(paths.root_dir, "src/util/empty.js") |           bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|") | ||||||
|  |         ), | ||||||
|  |         path.resolve(paths.polymer_dir, "src/util/empty.js") | ||||||
|       ), |       ), | ||||||
|       !isProdBuild && new LogStartCompilePlugin(), |       !isProdBuild && new LogStartCompilePlugin(), | ||||||
|       isProdBuild && |       isProdBuild && | ||||||
| @@ -181,20 +155,12 @@ const createRspackConfig = ({ | |||||||
|           stats: { assets: true, chunks: true, modules: true }, |           stats: { assets: true, chunks: true, modules: true }, | ||||||
|           transform: (stats) => JSON.stringify(filterStats(stats)), |           transform: (stats) => JSON.stringify(filterStats(stats)), | ||||||
|         }), |         }), | ||||||
|       isProdBuild && |       !latestBuild && | ||||||
|         isStatsBuild && |         new TransformAsyncModulesPlugin({ browserslistEnv: "legacy" }), | ||||||
|         new RsdoctorRspackPlugin({ |  | ||||||
|           reportDir: path.join(paths.build_dir, "rsdoctor"), |  | ||||||
|           features: ["plugins", "bundle"], |  | ||||||
|           supports: { |  | ||||||
|             generateTileGraph: true, |  | ||||||
|           }, |  | ||||||
|         }), |  | ||||||
|     ].filter(Boolean), |     ].filter(Boolean), | ||||||
|     resolve: { |     resolve: { | ||||||
|       extensions: [".ts", ".js", ".json"], |       extensions: [".ts", ".js", ".json"], | ||||||
|       alias: { |       alias: { | ||||||
|         "lit/static-html$": "lit/static-html.js", |  | ||||||
|         "lit/decorators$": "lit/decorators.js", |         "lit/decorators$": "lit/decorators.js", | ||||||
|         "lit/directive$": "lit/directive.js", |         "lit/directive$": "lit/directive.js", | ||||||
|         "lit/directives/until$": "lit/directives/until.js", |         "lit/directives/until$": "lit/directives/until.js", | ||||||
| @@ -203,10 +169,8 @@ const createRspackConfig = ({ | |||||||
|         "lit/directives/if-defined$": "lit/directives/if-defined.js", |         "lit/directives/if-defined$": "lit/directives/if-defined.js", | ||||||
|         "lit/directives/guard$": "lit/directives/guard.js", |         "lit/directives/guard$": "lit/directives/guard.js", | ||||||
|         "lit/directives/cache$": "lit/directives/cache.js", |         "lit/directives/cache$": "lit/directives/cache.js", | ||||||
|         "lit/directives/join$": "lit/directives/join.js", |  | ||||||
|         "lit/directives/repeat$": "lit/directives/repeat.js", |         "lit/directives/repeat$": "lit/directives/repeat.js", | ||||||
|         "lit/directives/live$": "lit/directives/live.js", |         "lit/directives/live$": "lit/directives/live.js", | ||||||
|         "lit/directives/keyed$": "lit/directives/keyed.js", |  | ||||||
|         "lit/polyfill-support$": "lit/polyfill-support.js", |         "lit/polyfill-support$": "lit/polyfill-support.js", | ||||||
|         "@lit-labs/virtualizer/layouts/grid": |         "@lit-labs/virtualizer/layouts/grid": | ||||||
|           "@lit-labs/virtualizer/layouts/grid.js", |           "@lit-labs/virtualizer/layouts/grid.js", | ||||||
| @@ -228,6 +192,8 @@ const createRspackConfig = ({ | |||||||
|         isProdBuild && !isStatsBuild ? "[id].[contenthash][ext]" : "[id][ext]", |         isProdBuild && !isStatsBuild ? "[id].[contenthash][ext]" : "[id][ext]", | ||||||
|       crossOriginLoading: "use-credentials", |       crossOriginLoading: "use-credentials", | ||||||
|       hashFunction: "xxhash64", |       hashFunction: "xxhash64", | ||||||
|  |       hashDigest: "base64url", | ||||||
|  |       hashDigestLength: 11, // full length of 64 bit base64url
 | ||||||
|       path: outputPath, |       path: outputPath, | ||||||
|       publicPath, |       publicPath, | ||||||
|       // To silence warning in worker plugin
 |       // To silence warning in worker plugin
 | ||||||
| @@ -257,7 +223,6 @@ const createRspackConfig = ({ | |||||||
|       ), |       ), | ||||||
|     }, |     }, | ||||||
|     experiments: { |     experiments: { | ||||||
|       layers: true, |  | ||||||
|       outputModule: true, |       outputModule: true, | ||||||
|     }, |     }, | ||||||
|   }; |   }; | ||||||
| @@ -269,17 +234,17 @@ const createAppConfig = ({ | |||||||
|   isStatsBuild, |   isStatsBuild, | ||||||
|   isTestBuild, |   isTestBuild, | ||||||
| }) => | }) => | ||||||
|   createRspackConfig( |   createWebpackConfig( | ||||||
|     bundle.config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild }) |     bundle.config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild }) | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
| const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => | const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => | ||||||
|   createRspackConfig( |   createWebpackConfig( | ||||||
|     bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild }) |     bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild }) | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
| const createCastConfig = ({ isProdBuild, latestBuild }) => | const createCastConfig = ({ isProdBuild, latestBuild }) => | ||||||
|   createRspackConfig(bundle.config.cast({ isProdBuild, latestBuild })); |   createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild })); | ||||||
| 
 | 
 | ||||||
| const createHassioConfig = ({ | const createHassioConfig = ({ | ||||||
|   isProdBuild, |   isProdBuild, | ||||||
| @@ -287,7 +252,7 @@ const createHassioConfig = ({ | |||||||
|   isStatsBuild, |   isStatsBuild, | ||||||
|   isTestBuild, |   isTestBuild, | ||||||
| }) => | }) => | ||||||
|   createRspackConfig( |   createWebpackConfig( | ||||||
|     bundle.config.hassio({ |     bundle.config.hassio({ | ||||||
|       isProdBuild, |       isProdBuild, | ||||||
|       latestBuild, |       latestBuild, | ||||||
| @@ -297,10 +262,7 @@ const createHassioConfig = ({ | |||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
| const createGalleryConfig = ({ isProdBuild, latestBuild }) => | const createGalleryConfig = ({ isProdBuild, latestBuild }) => | ||||||
|   createRspackConfig(bundle.config.gallery({ isProdBuild, latestBuild })); |   createWebpackConfig(bundle.config.gallery({ isProdBuild, latestBuild })); | ||||||
| 
 |  | ||||||
| const createLandingPageConfig = ({ isProdBuild, latestBuild }) => |  | ||||||
|   createRspackConfig(bundle.config.landingPage({ isProdBuild, latestBuild })); |  | ||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|   createAppConfig, |   createAppConfig, | ||||||
| @@ -308,6 +270,5 @@ module.exports = { | |||||||
|   createCastConfig, |   createCastConfig, | ||||||
|   createHassioConfig, |   createHassioConfig, | ||||||
|   createGalleryConfig, |   createGalleryConfig, | ||||||
|   createRspackConfig, |   createWebpackConfig, | ||||||
|   createLandingPageConfig, |  | ||||||
| }; | }; | ||||||
| @@ -25,7 +25,7 @@ Home Assistant Cast is made up of two separate applications: | |||||||
|  |  | ||||||
| ### Setting dev variables | ### Setting dev variables | ||||||
|  |  | ||||||
| Open `src/cast/dev_const.ts` and change `CAST_DEV_APP_ID` to the ID of the app you just created. And set the `CAST_DEV_HASS_URL` to the url of your development machine. | Open `src/cast/dev_const.ts` and change `CAST_DEV_APP_ID` to the ID of the app you just created. And set the `CAST_DEV_HASS_URL` to the url of you development machine. | ||||||
|  |  | ||||||
| ### Changing configuration | ### Changing configuration | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
|  |  | ||||||
| self.addEventListener("fetch", (event) => { |  | ||||||
|   event.respondWith(fetch(event.request)); |  | ||||||
| }); |  | ||||||
							
								
								
									
										10
									
								
								cast/rollup.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								cast/rollup.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | import rollup from "../build-scripts/rollup.cjs"; | ||||||
|  | import env from "../build-scripts/env.cjs"; | ||||||
|  |  | ||||||
|  | const config = rollup.createCastConfig({ | ||||||
|  |   isProdBuild: env.isProdBuild(), | ||||||
|  |   latestBuild: true, | ||||||
|  |   isStatsBuild: env.isStatsBuild(), | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export default { ...config.inputOptions, output: config.outputOptions }; | ||||||
| @@ -36,7 +36,13 @@ | |||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <%= renderTemplate("../../../src/html/_js_base.html.template") %> |     <%= renderTemplate("../../../src/html/_js_base.html.template") %> | ||||||
|     <%= renderTemplate("../../../src/html/_script_loader.html.template") %> |     <script> | ||||||
|  |       <% for (const entry of latestEntryJS) { %> | ||||||
|  |         import("<%= entry %>"); | ||||||
|  |       <% } %> | ||||||
|  |       window.latestJS = true; | ||||||
|  |     </script> | ||||||
|  |     <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> | ||||||
|     <hc-layout subtitle="FAQ"> |     <hc-layout subtitle="FAQ"> | ||||||
|       <style> |       <style> | ||||||
|         a { |         a { | ||||||
| @@ -139,7 +145,7 @@ | |||||||
|         </p> |         </p> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <div class="section-header">What does Home Assistant Cast do?</div> |       <div class="section-header">Wat does Home Assistant Cast do?</div> | ||||||
|       <div class="card-content"> |       <div class="card-content"> | ||||||
|         <p> |         <p> | ||||||
|           Home Assistant Cast is a receiver application for the Chromecast. When |           Home Assistant Cast is a receiver application for the Chromecast. When | ||||||
| @@ -226,5 +232,17 @@ http: | |||||||
|         </p> |         </p> | ||||||
|       </div> |       </div> | ||||||
|     </hc-layout> |     </hc-layout> | ||||||
|  |  | ||||||
|  |     <script> | ||||||
|  |       var _gaq = [["_setAccount", "UA-57927901-9"], ["_trackPageview"]]; | ||||||
|  |       (function (d, t) { | ||||||
|  |         var g = d.createElement(t), | ||||||
|  |           s = d.getElementsByTagName(t)[0]; | ||||||
|  |         g.src = | ||||||
|  |           ("https:" == location.protocol ? "//ssl" : "//www") + | ||||||
|  |           ".google-analytics.com/ga.js"; | ||||||
|  |         s.parentNode.insertBefore(g, s); | ||||||
|  |       })(document, "script"); | ||||||
|  |     </script> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -13,9 +13,15 @@ | |||||||
|     <%= renderTemplate("_social_meta.html.template") %> |     <%= renderTemplate("_social_meta.html.template") %> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <hc-connect></hc-connect> |  | ||||||
|     <%= renderTemplate("../../../src/html/_js_base.html.template") %> |     <%= renderTemplate("../../../src/html/_js_base.html.template") %> | ||||||
|     <%= renderTemplate("../../../src/html/_script_loader.html.template") %> |     <hc-connect></hc-connect> | ||||||
|  |     <script> | ||||||
|  |       <% for (const entry of latestEntryJS) { %> | ||||||
|  |         import("<%= entry %>"); | ||||||
|  |       <% } %> | ||||||
|  |       window.latestJS = true; | ||||||
|  |     </script> | ||||||
|  |     <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> | ||||||
|     <script> |     <script> | ||||||
|     (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |     (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | ||||||
|     (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |     (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | ||||||
|   | |||||||
| @@ -14,10 +14,22 @@ | |||||||
|         --background-color: #41bdf5; |         --background-color: #41bdf5; | ||||||
|       } |       } | ||||||
|     </style> |     </style> | ||||||
|  |     <script> | ||||||
|  |       var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']]; | ||||||
|  |       (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0]; | ||||||
|  |       g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js'; | ||||||
|  |       s.parentNode.insertBefore(g,s)}(document,'script')); | ||||||
|  |     </script> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <cast-media-player></cast-media-player> |  | ||||||
|     <%= renderTemplate("../../../src/html/_js_base.html.template") %> |     <%= renderTemplate("../../../src/html/_js_base.html.template") %> | ||||||
|     <%= renderTemplate("../../../src/html/_script_loader.html.template") %> |     <cast-media-player></cast-media-player> | ||||||
|  |     <script> | ||||||
|  |       <% for (const entry of latestEntryJS) { %> | ||||||
|  |         import("<%= entry %>"); | ||||||
|  |       <% } %> | ||||||
|  |       window.latestJS = true; | ||||||
|  |     </script> | ||||||
|  |     <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -7,7 +7,14 @@ | |||||||
|   <%= renderTemplate("../../../src/html/_style_base.html.template") %> |   <%= renderTemplate("../../../src/html/_style_base.html.template") %> | ||||||
|   <style> |   <style> | ||||||
|     body { |     body { | ||||||
|  |       background-color: white; | ||||||
|       font-size: initial; |       font-size: initial; | ||||||
|     } |     } | ||||||
|   </style> |   </style> | ||||||
|  |   <script> | ||||||
|  |   var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']]; | ||||||
|  |   (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0]; | ||||||
|  |   g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js'; | ||||||
|  |   s.parentNode.insertBefore(g,s)}(document,'script')); | ||||||
|  |   </script> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import "../../../src/resources/safari-14-attachshadow-patch"; | ||||||
| import "./layout/hc-connect"; | import "./layout/hc-connect"; | ||||||
|  |  | ||||||
| import("../../../src/resources/append-ha-style"); | import("../../../src/resources/ha-style"); | ||||||
|   | |||||||
| @@ -1,12 +1,11 @@ | |||||||
| import "@material/mwc-button/mwc-button"; | import "@material/mwc-button/mwc-button"; | ||||||
|  | import { mdiCast, mdiCastConnected } from "@mdi/js"; | ||||||
| import type { ActionDetail } from "@material/mwc-list/mwc-list"; | import "@polymer/paper-item/paper-icon-item"; | ||||||
| import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js"; | import "@polymer/paper-listbox/paper-listbox"; | ||||||
| import type { Auth, Connection } from "home-assistant-js-websocket"; | import { Auth, Connection } from "home-assistant-js-websocket"; | ||||||
| import type { TemplateResult } from "lit"; | import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; | ||||||
| import { LitElement, css, html } from "lit"; |  | ||||||
| import { customElement, property, state } from "lit/decorators"; | import { customElement, property, state } from "lit/decorators"; | ||||||
| import type { CastManager } from "../../../../src/cast/cast_manager"; | import { CastManager } from "../../../../src/cast/cast_manager"; | ||||||
| import { | import { | ||||||
|   castSendShowLovelaceView, |   castSendShowLovelaceView, | ||||||
|   ensureConnectedCastSession, |   ensureConnectedCastSession, | ||||||
| @@ -19,15 +18,13 @@ import { | |||||||
| import { atLeastVersion } from "../../../../src/common/config/version"; | import { atLeastVersion } from "../../../../src/common/config/version"; | ||||||
| import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute"; | import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute"; | ||||||
| import "../../../../src/components/ha-icon"; | import "../../../../src/components/ha-icon"; | ||||||
| import "../../../../src/components/ha-list"; |  | ||||||
| import "../../../../src/components/ha-list-item"; |  | ||||||
| import "../../../../src/components/ha-svg-icon"; | import "../../../../src/components/ha-svg-icon"; | ||||||
| import { | import { | ||||||
|   getLegacyLovelaceCollection, |   getLegacyLovelaceCollection, | ||||||
|   getLovelaceCollection, |   getLovelaceCollection, | ||||||
| } from "../../../../src/data/lovelace"; | } from "../../../../src/data/lovelace"; | ||||||
| import { isStrategyDashboard } from "../../../../src/data/lovelace/config/types"; | import { isStrategyDashboard } from "../../../../src/data/lovelace/config/types"; | ||||||
| import type { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view"; | import { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view"; | ||||||
| import "../../../../src/layouts/hass-loading-screen"; | import "../../../../src/layouts/hass-loading-screen"; | ||||||
| import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config"; | import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config"; | ||||||
| import "./hc-layout"; | import "./hc-layout"; | ||||||
| @@ -86,38 +83,34 @@ class HcCast extends LitElement { | |||||||
|               ` |               ` | ||||||
|             : html` |             : html` | ||||||
|                 <div class="section-header">PICK A VIEW</div> |                 <div class="section-header">PICK A VIEW</div> | ||||||
|                 <ha-list @action=${this._handlePickView} activatable> |                 <paper-listbox | ||||||
|  |                   attr-for-selected="data-path" | ||||||
|  |                   .selected=${this.castManager.status.lovelacePath || ""} | ||||||
|  |                 > | ||||||
|                   ${( |                   ${( | ||||||
|                     this.lovelaceViews ?? [ |                     this.lovelaceViews ?? [ | ||||||
|                       generateDefaultViewConfig({}, {}, {}, {}, () => ""), |                       generateDefaultViewConfig({}, {}, {}, {}, () => ""), | ||||||
|                     ] |                     ] | ||||||
|                   ).map( |                   ).map( | ||||||
|                     (view, idx) => html` |                     (view, idx) => html` | ||||||
|                       <ha-list-item |                       <paper-icon-item | ||||||
|                         graphic="avatar" |                         @click=${this._handlePickView} | ||||||
|                         .activated=${this.castManager.status?.lovelacePath === |                         data-path=${view.path || idx} | ||||||
|                         (view.path ?? idx)} |  | ||||||
|                         .selected=${this.castManager.status?.lovelacePath === |  | ||||||
|                         (view.path ?? idx)} |  | ||||||
|                       > |                       > | ||||||
|                         ${view.title || view.path || "Unnamed view"} |  | ||||||
|                         ${view.icon |                         ${view.icon | ||||||
|                           ? html` |                           ? html` | ||||||
|                               <ha-icon |                               <ha-icon | ||||||
|                                 .icon=${view.icon} |                                 .icon=${view.icon} | ||||||
|                                 slot="graphic" |                                 slot="item-icon" | ||||||
|                               ></ha-icon> |                               ></ha-icon> | ||||||
|                             ` |                             ` | ||||||
|                           : html`<ha-svg-icon |                           : ""} | ||||||
|                               slot="item-icon" |                         ${view.title || view.path} | ||||||
|                               .path=${mdiViewDashboard} |                       </paper-icon-item> | ||||||
|                             ></ha-svg-icon>`} |  | ||||||
|                       </ha-list-item> |  | ||||||
|                     ` |                     ` | ||||||
|                   )}</ha-list |                   )} | ||||||
|                 > |                 </paper-listbox> | ||||||
|               `} |               `} | ||||||
|  |  | ||||||
|         <div class="card-actions"> |         <div class="card-actions"> | ||||||
|           ${this.castManager.status |           ${this.castManager.status | ||||||
|             ? html` |             ? html` | ||||||
| @@ -189,8 +182,8 @@ class HcCast extends LitElement { | |||||||
|     this.castManager.requestSession(); |     this.castManager.requestSession(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private async _handlePickView(ev: CustomEvent<ActionDetail>) { |   private async _handlePickView(ev: Event) { | ||||||
|     const path = this.lovelaceViews![ev.detail.index].path ?? ev.detail.index; |     const path = (ev.currentTarget as any).getAttribute("data-path"); | ||||||
|     await ensureConnectedCastSession(this.castManager!, this.auth!); |     await ensureConnectedCastSession(this.castManager!, this.auth!); | ||||||
|     castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path); |     castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path); | ||||||
|   } |   } | ||||||
| @@ -204,12 +197,13 @@ class HcCast extends LitElement { | |||||||
|       } |       } | ||||||
|       this.connection.close(); |       this.connection.close(); | ||||||
|       location.reload(); |       location.reload(); | ||||||
|     } catch (_err: any) { |     } catch (err: any) { | ||||||
|       alert("Unable to log out!"); |       alert("Unable to log out!"); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static styles = css` |   static get styles(): CSSResultGroup { | ||||||
|  |     return css` | ||||||
|       .center-item { |       .center-item { | ||||||
|         display: flex; |         display: flex; | ||||||
|         justify-content: space-around; |         justify-content: space-around; | ||||||
| @@ -252,14 +246,25 @@ class HcCast extends LitElement { | |||||||
|         height: 18px; |         height: 18px; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|     ha-list-item ha-icon, |       paper-listbox { | ||||||
|     ha-list-item ha-svg-icon { |         padding-top: 0; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       paper-listbox ha-icon { | ||||||
|         padding: 12px; |         padding: 12px; | ||||||
|         color: var(--secondary-text-color); |         color: var(--secondary-text-color); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|     :host([hide-icons]) ha-icon { |       paper-icon-item { | ||||||
|       display: none; |         cursor: pointer; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       paper-icon-item[disabled] { | ||||||
|  |         cursor: initial; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       :host([hide-icons]) paper-icon-item { | ||||||
|  |         --paper-item-icon-width: 0px; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       .spacer { |       .spacer { | ||||||
| @@ -270,6 +275,7 @@ class HcCast extends LitElement { | |||||||
|         color: var(--primary-color); |         color: var(--primary-color); | ||||||
|       } |       } | ||||||
|     `; |     `; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| declare global { | declare global { | ||||||
|   | |||||||
| @@ -1,23 +1,19 @@ | |||||||
| import "@material/mwc-button"; | import "@material/mwc-button"; | ||||||
| import { mdiCastConnected, mdiCast } from "@mdi/js"; | import { mdiCastConnected, mdiCast } from "@mdi/js"; | ||||||
| import type { | import { | ||||||
|   Auth, |   Auth, | ||||||
|   Connection, |   Connection, | ||||||
|   getAuthOptions, |  | ||||||
| } from "home-assistant-js-websocket"; |  | ||||||
| import { |  | ||||||
|   createConnection, |   createConnection, | ||||||
|   ERR_CANNOT_CONNECT, |   ERR_CANNOT_CONNECT, | ||||||
|   ERR_HASS_HOST_REQUIRED, |   ERR_HASS_HOST_REQUIRED, | ||||||
|   ERR_INVALID_AUTH, |   ERR_INVALID_AUTH, | ||||||
|   ERR_INVALID_HTTPS_TO_HTTP, |   ERR_INVALID_HTTPS_TO_HTTP, | ||||||
|   getAuth, |   getAuth, | ||||||
|  |   getAuthOptions, | ||||||
| } from "home-assistant-js-websocket"; | } from "home-assistant-js-websocket"; | ||||||
| import type { TemplateResult } from "lit"; | import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; | ||||||
| import { css, html, LitElement } from "lit"; |  | ||||||
| import { customElement, state } from "lit/decorators"; | import { customElement, state } from "lit/decorators"; | ||||||
| import type { CastManager } from "../../../../src/cast/cast_manager"; | import { CastManager, getCastManager } from "../../../../src/cast/cast_manager"; | ||||||
| import { getCastManager } from "../../../../src/cast/cast_manager"; |  | ||||||
| import { castSendShowDemo } from "../../../../src/cast/receiver_messages"; | import { castSendShowDemo } from "../../../../src/cast/receiver_messages"; | ||||||
| import { | import { | ||||||
|   loadTokens, |   loadTokens, | ||||||
| @@ -215,7 +211,7 @@ export class HcConnect extends LitElement { | |||||||
|     let url: URL; |     let url: URL; | ||||||
|     try { |     try { | ||||||
|       url = new URL(value); |       url = new URL(value); | ||||||
|     } catch (_err: any) { |     } catch (err: any) { | ||||||
|       this.error = "Invalid URL"; |       this.error = "Invalid URL"; | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| @@ -252,7 +248,7 @@ export class HcConnect extends LitElement { | |||||||
|       this.loading = false; |       this.loading = false; | ||||||
|       return; |       return; | ||||||
|     } finally { |     } finally { | ||||||
|       // Clear url if we have an auth callback in url. |       // Clear url if we have a auth callback in url. | ||||||
|       if (location.search.includes("auth_callback=1")) { |       if (location.search.includes("auth_callback=1")) { | ||||||
|         history.replaceState(null, "", location.pathname); |         history.replaceState(null, "", location.pathname); | ||||||
|       } |       } | ||||||
| @@ -288,12 +284,13 @@ export class HcConnect extends LitElement { | |||||||
|     try { |     try { | ||||||
|       saveTokens(null); |       saveTokens(null); | ||||||
|       location.reload(); |       location.reload(); | ||||||
|     } catch (_err: any) { |     } catch (err: any) { | ||||||
|       alert("Unable to log out!"); |       alert("Unable to log out!"); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static styles = css` |   static get styles(): CSSResultGroup { | ||||||
|  |     return css` | ||||||
|       .card-content a { |       .card-content a { | ||||||
|         color: var(--primary-color); |         color: var(--primary-color); | ||||||
|       } |       } | ||||||
| @@ -302,7 +299,7 @@ export class HcConnect extends LitElement { | |||||||
|       } |       } | ||||||
|       .error { |       .error { | ||||||
|         color: red; |         color: red; | ||||||
|       font-weight: var(--ha-font-weight-bold); |         font-weight: bold; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       .error a { |       .error a { | ||||||
| @@ -321,6 +318,7 @@ export class HcConnect extends LitElement { | |||||||
|         width: 100%; |         width: 100%; | ||||||
|       } |       } | ||||||
|     `; |     `; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| declare global { | declare global { | ||||||
|   | |||||||
| @@ -1,7 +1,10 @@ | |||||||
| import type { Auth, Connection, HassUser } from "home-assistant-js-websocket"; | import { | ||||||
| import { getUser } from "home-assistant-js-websocket"; |   Auth, | ||||||
| import type { TemplateResult } from "lit"; |   Connection, | ||||||
| import { css, html, LitElement } from "lit"; |   getUser, | ||||||
|  |   HassUser, | ||||||
|  | } from "home-assistant-js-websocket"; | ||||||
|  | import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; | ||||||
| import { customElement, property } from "lit/decorators"; | import { customElement, property } from "lit/decorators"; | ||||||
| import "../../../../src/components/ha-card"; | import "../../../../src/components/ha-card"; | ||||||
|  |  | ||||||
| @@ -63,7 +66,8 @@ class HcLayout extends LitElement { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static styles = css` |   static get styles(): CSSResultGroup { | ||||||
|  |     return css` | ||||||
|       :host { |       :host { | ||||||
|         display: flex; |         display: flex; | ||||||
|         min-height: 100%; |         min-height: 100%; | ||||||
| @@ -84,11 +88,11 @@ class HcLayout extends LitElement { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       .card-header { |       .card-header { | ||||||
|       color: var(--ha-card-header-color, var(--primary-text-color)); |         color: var(--ha-card-header-color, --primary-text-color); | ||||||
|         font-family: var(--ha-card-header-font-family, inherit); |         font-family: var(--ha-card-header-font-family, inherit); | ||||||
|       font-size: var(--ha-card-header-font-size, var(--ha-font-size-2xl)); |         font-size: var(--ha-card-header-font-size, 24px); | ||||||
|         letter-spacing: -0.012em; |         letter-spacing: -0.012em; | ||||||
|       line-height: var(--ha-line-height-normal); |         line-height: 32px; | ||||||
|         padding: 24px 16px 16px; |         padding: 24px 16px 16px; | ||||||
|         display: block; |         display: block; | ||||||
|         margin: 0; |         margin: 0; | ||||||
| @@ -98,7 +102,7 @@ class HcLayout extends LitElement { | |||||||
|         border-radius: 4px 4px 0 0; |         border-radius: 4px 4px 0 0; | ||||||
|       } |       } | ||||||
|       .subtitle { |       .subtitle { | ||||||
|       font-size: var(--ha-font-size-m); |         font-size: 14px; | ||||||
|         color: var(--secondary-text-color); |         color: var(--secondary-text-color); | ||||||
|         line-height: initial; |         line-height: initial; | ||||||
|       } |       } | ||||||
| @@ -113,7 +117,7 @@ class HcLayout extends LitElement { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       :host ::slotted(.section-header) { |       :host ::slotted(.section-header) { | ||||||
|       font-weight: var(--ha-font-weight-semibold); |         font-weight: 500; | ||||||
|         padding: 4px 16px; |         padding: 4px 16px; | ||||||
|         text-transform: uppercase; |         text-transform: uppercase; | ||||||
|       } |       } | ||||||
| @@ -135,7 +139,7 @@ class HcLayout extends LitElement { | |||||||
|  |  | ||||||
|       .footer { |       .footer { | ||||||
|         text-align: center; |         text-align: center; | ||||||
|       font-size: var(--ha-font-size-s); |         font-size: 12px; | ||||||
|         padding: 8px 0 24px; |         padding: 8px 0 24px; | ||||||
|         color: var(--secondary-text-color); |         color: var(--secondary-text-color); | ||||||
|       } |       } | ||||||
| @@ -151,6 +155,7 @@ class HcLayout extends LitElement { | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     `; |     `; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| declare global { | declare global { | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import type { Entity } from "../../../../src/fake_data/entity"; | import { convertEntities, Entity } from "../../../../src/fake_data/entity"; | ||||||
| import { convertEntities } from "../../../../src/fake_data/entity"; |  | ||||||
|  |  | ||||||
| export const castDemoEntities: () => Entity[] = () => | export const castDemoEntities: () => Entity[] = () => | ||||||
|   convertEntities({ |   convertEntities({ | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import type { LovelaceCardConfig } from "../../../../src/data/lovelace/config/card"; | import { LovelaceCardConfig } from "../../../../src/data/lovelace/config/card"; | ||||||
| import type { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; | import { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; | ||||||
| import { castContext } from "../cast_context"; | import { castContext } from "../cast_context"; | ||||||
|  |  | ||||||
| export const castDemoLovelace: () => LovelaceConfig = () => { | export const castDemoLovelace: () => LovelaceConfig = () => { | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| import { framework } from "./cast_framework"; | import { framework } from "./cast_framework"; | ||||||
| import { CAST_NS } from "../../../src/cast/const"; | import { CAST_NS } from "../../../src/cast/const"; | ||||||
| import type { HassMessage } from "../../../src/cast/receiver_messages"; | import { HassMessage } from "../../../src/cast/receiver_messages"; | ||||||
| import "../../../src/resources/custom-card-support"; | import "../../../src/resources/custom-card-support"; | ||||||
| import { castContext } from "./cast_context"; | import { castContext } from "./cast_context"; | ||||||
| import { HcMain } from "./layout/hc-main"; | import { HcMain } from "./layout/hc-main"; | ||||||
| import type { ReceivedMessage } from "./types"; | import { ReceivedMessage } from "./types"; | ||||||
|  |  | ||||||
| const lovelaceController = new HcMain(); | const lovelaceController = new HcMain(); | ||||||
| document.body.append(lovelaceController); | document.body.append(lovelaceController); | ||||||
|   | |||||||
| @@ -1,11 +1,13 @@ | |||||||
| import { html, nothing } from "lit"; | import { html, nothing } from "lit"; | ||||||
| import { customElement, property, state } from "lit/decorators"; | import { customElement, property, state } from "lit/decorators"; | ||||||
| import { mockHistory } from "../../../../demo/src/stubs/history"; | import { mockHistory } from "../../../../demo/src/stubs/history"; | ||||||
| import type { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; | import { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; | ||||||
| import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass"; | import { | ||||||
| import { provideHass } from "../../../../src/fake_data/provide_hass"; |   MockHomeAssistant, | ||||||
|  |   provideHass, | ||||||
|  | } from "../../../../src/fake_data/provide_hass"; | ||||||
| import { HassElement } from "../../../../src/state/hass-element"; | import { HassElement } from "../../../../src/state/hass-element"; | ||||||
| import type { HomeAssistant } from "../../../../src/types"; | import { HomeAssistant } from "../../../../src/types"; | ||||||
| import { castDemoEntities } from "../demo/cast-demo-entities"; | import { castDemoEntities } from "../demo/cast-demo-entities"; | ||||||
| import { castDemoLovelace } from "../demo/cast-demo-lovelace"; | import { castDemoLovelace } from "../demo/cast-demo-lovelace"; | ||||||
| import "./hc-lovelace"; | import "./hc-lovelace"; | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| import type { TemplateResult } from "lit"; | import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; | ||||||
| import { css, html, LitElement } from "lit"; |  | ||||||
| import { customElement, property } from "lit/decorators"; | import { customElement, property } from "lit/decorators"; | ||||||
| import type { HomeAssistant } from "../../../../src/types"; | import { HomeAssistant } from "../../../../src/types"; | ||||||
|  |  | ||||||
| @customElement("hc-launch-screen") | @customElement("hc-launch-screen") | ||||||
| class HcLaunchScreen extends LitElement { | class HcLaunchScreen extends LitElement { | ||||||
| @@ -24,12 +23,13 @@ class HcLaunchScreen extends LitElement { | |||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static styles = css` |   static get styles(): CSSResultGroup { | ||||||
|  |     return css` | ||||||
|       :host { |       :host { | ||||||
|         display: block; |         display: block; | ||||||
|         height: 100vh; |         height: 100vh; | ||||||
|         background-color: #f2f4f9; |         background-color: #f2f4f9; | ||||||
|       font-size: var(--ha-font-size-2xl); |         font-size: 24px; | ||||||
|       } |       } | ||||||
|       .container { |       .container { | ||||||
|         display: flex; |         display: flex; | ||||||
| @@ -47,6 +47,7 @@ class HcLaunchScreen extends LitElement { | |||||||
|         color: #1d2126; |         color: #1d2126; | ||||||
|       } |       } | ||||||
|     `; |     `; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| declare global { | declare global { | ||||||
|   | |||||||
| @@ -1,14 +1,11 @@ | |||||||
| import { css, html, LitElement, type TemplateResult } from "lit"; | import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; | ||||||
| import { customElement, property } from "lit/decorators"; | import { customElement, property, query } from "lit/decorators"; | ||||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||||
| import type { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; | import { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; | ||||||
| import { getPanelTitleFromUrlPath } from "../../../../src/data/panel"; | import { Lovelace } from "../../../../src/panels/lovelace/types"; | ||||||
| import type { Lovelace } from "../../../../src/panels/lovelace/types"; |  | ||||||
| import "../../../../src/panels/lovelace/views/hui-view"; | import "../../../../src/panels/lovelace/views/hui-view"; | ||||||
| import "../../../../src/panels/lovelace/views/hui-view-container"; | import { HomeAssistant } from "../../../../src/types"; | ||||||
| import type { HomeAssistant } from "../../../../src/types"; |  | ||||||
| import "./hc-launch-screen"; | import "./hc-launch-screen"; | ||||||
| import "../../../../src/panels/lovelace/views/hui-view-background"; |  | ||||||
|  |  | ||||||
| (window as any).loadCardHelpers = () => | (window as any).loadCardHelpers = () => | ||||||
|   import("../../../../src/panels/lovelace/custom-card-helpers"); |   import("../../../../src/panels/lovelace/custom-card-helpers"); | ||||||
| @@ -20,9 +17,11 @@ class HcLovelace extends LitElement { | |||||||
|   @property({ attribute: false }) |   @property({ attribute: false }) | ||||||
|   public lovelaceConfig!: LovelaceConfig; |   public lovelaceConfig!: LovelaceConfig; | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public viewPath?: string | number | null; |   @property() public viewPath?: string | number | null; | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public urlPath: string | null = null; |   @property() public urlPath: string | null = null; | ||||||
|  |  | ||||||
|  |   @query("hui-view") private _huiView?: HTMLElement; | ||||||
|  |  | ||||||
|   protected render(): TemplateResult { |   protected render(): TemplateResult { | ||||||
|     const index = this._viewIndex; |     const index = this._viewIndex; | ||||||
| @@ -45,22 +44,13 @@ class HcLovelace extends LitElement { | |||||||
|       saveConfig: async () => undefined, |       saveConfig: async () => undefined, | ||||||
|       deleteConfig: async () => undefined, |       deleteConfig: async () => undefined, | ||||||
|       setEditMode: () => undefined, |       setEditMode: () => undefined, | ||||||
|       showToast: () => undefined, |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     const viewConfig = this.lovelaceConfig.views[index]; |  | ||||||
|     const background = viewConfig.background || this.lovelaceConfig.background; |  | ||||||
|  |  | ||||||
|     return html` |     return html` | ||||||
|       <hui-view-container .hass=${this.hass} .theme=${viewConfig.theme}> |  | ||||||
|         <hui-view-background .hass=${this.hass} .background=${background}> |  | ||||||
|         </hui-view-background> |  | ||||||
|       <hui-view |       <hui-view | ||||||
|         .hass=${this.hass} |         .hass=${this.hass} | ||||||
|         .lovelace=${lovelace} |         .lovelace=${lovelace} | ||||||
|         .index=${index} |         .index=${index} | ||||||
|       ></hui-view> |       ></hui-view> | ||||||
|       </hui-view-container> |  | ||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -71,12 +61,7 @@ class HcLovelace extends LitElement { | |||||||
|       const index = this._viewIndex; |       const index = this._viewIndex; | ||||||
|  |  | ||||||
|       if (index !== undefined) { |       if (index !== undefined) { | ||||||
|         const title = getPanelTitleFromUrlPath( |         const dashboardTitle = this.lovelaceConfig.title || this.urlPath; | ||||||
|           this.hass, |  | ||||||
|           this.urlPath || "lovelace" |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         const dashboardTitle = title || this.urlPath; |  | ||||||
|  |  | ||||||
|         const viewTitle = |         const viewTitle = | ||||||
|           this.lovelaceConfig.views[index].title || |           this.lovelaceConfig.views[index].title || | ||||||
| @@ -90,6 +75,19 @@ class HcLovelace extends LitElement { | |||||||
|                 }${viewTitle || ""}` |                 }${viewTitle || ""}` | ||||||
|               : undefined, |               : undefined, | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         const configBackground = | ||||||
|  |           this.lovelaceConfig.views[index].background || | ||||||
|  |           this.lovelaceConfig.background; | ||||||
|  |  | ||||||
|  |         if (configBackground) { | ||||||
|  |           this._huiView!.style.setProperty( | ||||||
|  |             "--lovelace-background", | ||||||
|  |             configBackground | ||||||
|  |           ); | ||||||
|  |         } else { | ||||||
|  |           this._huiView!.style.removeProperty("--lovelace-background"); | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -111,18 +109,24 @@ class HcLovelace extends LitElement { | |||||||
|     return undefined; |     return undefined; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static styles = css` |   static get styles(): CSSResultGroup { | ||||||
|     hui-view-container { |     return css` | ||||||
|       display: flex; |       :host { | ||||||
|       position: relative; |  | ||||||
|         min-height: 100vh; |         min-height: 100vh; | ||||||
|  |         height: 0; | ||||||
|  |         display: flex; | ||||||
|  |         flex-direction: column; | ||||||
|         box-sizing: border-box; |         box-sizing: border-box; | ||||||
|  |         background: var(--primary-background-color); | ||||||
|       } |       } | ||||||
|     hui-view-container > * { |       :host > * { | ||||||
|       flex: 1 1 100%; |         flex: 1; | ||||||
|       max-width: 100%; |       } | ||||||
|  |       hui-view { | ||||||
|  |         background: var(--lovelace-background, var(--primary-background-color)); | ||||||
|       } |       } | ||||||
|     `; |     `; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface CastViewChanged { | export interface CastViewChanged { | ||||||
|   | |||||||
| @@ -1,40 +1,40 @@ | |||||||
| import type { UnsubscribeFunc } from "home-assistant-js-websocket"; | import { | ||||||
| import { createConnection, getAuth } from "home-assistant-js-websocket"; |   createConnection, | ||||||
| import type { TemplateResult } from "lit"; |   getAuth, | ||||||
| import { html } from "lit"; |   UnsubscribeFunc, | ||||||
|  | } from "home-assistant-js-websocket"; | ||||||
|  | import { html, TemplateResult } from "lit"; | ||||||
| import { customElement, state } from "lit/decorators"; | import { customElement, state } from "lit/decorators"; | ||||||
| import { CAST_NS } from "../../../../src/cast/const"; | import { CAST_NS } from "../../../../src/cast/const"; | ||||||
| import type { | import { | ||||||
|   ConnectMessage, |   ConnectMessage, | ||||||
|   GetStatusMessage, |   GetStatusMessage, | ||||||
|   HassMessage, |   HassMessage, | ||||||
|   ShowDemoMessage, |   ShowDemoMessage, | ||||||
|   ShowLovelaceViewMessage, |   ShowLovelaceViewMessage, | ||||||
| } from "../../../../src/cast/receiver_messages"; | } from "../../../../src/cast/receiver_messages"; | ||||||
| import type { | import { | ||||||
|  |   ReceiverErrorCode, | ||||||
|   ReceiverErrorMessage, |   ReceiverErrorMessage, | ||||||
|   ReceiverStatusMessage, |   ReceiverStatusMessage, | ||||||
| } from "../../../../src/cast/sender_messages"; | } from "../../../../src/cast/sender_messages"; | ||||||
| import { ReceiverErrorCode } from "../../../../src/cast/sender_messages"; |  | ||||||
| import { atLeastVersion } from "../../../../src/common/config/version"; | import { atLeastVersion } from "../../../../src/common/config/version"; | ||||||
| import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click"; | import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click"; | ||||||
| import { | import { | ||||||
|   getLegacyLovelaceCollection, |   getLegacyLovelaceCollection, | ||||||
|   getLovelaceCollection, |   getLovelaceCollection, | ||||||
| } from "../../../../src/data/lovelace"; | } from "../../../../src/data/lovelace"; | ||||||
| import type { | import { | ||||||
|  |   isStrategyDashboard, | ||||||
|   LegacyLovelaceConfig, |   LegacyLovelaceConfig, | ||||||
|   LovelaceConfig, |   LovelaceConfig, | ||||||
|   LovelaceDashboardStrategyConfig, |   LovelaceDashboardStrategyConfig, | ||||||
| } from "../../../../src/data/lovelace/config/types"; | } from "../../../../src/data/lovelace/config/types"; | ||||||
| import { isStrategyDashboard } from "../../../../src/data/lovelace/config/types"; |  | ||||||
| import { fetchResources } from "../../../../src/data/lovelace/resource"; | import { fetchResources } from "../../../../src/data/lovelace/resource"; | ||||||
| import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources"; | import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources"; | ||||||
| import { HassElement } from "../../../../src/state/hass-element"; | import { HassElement } from "../../../../src/state/hass-element"; | ||||||
| import { castContext } from "../cast_context"; | import { castContext } from "../cast_context"; | ||||||
| import "./hc-launch-screen"; | import "./hc-launch-screen"; | ||||||
| import { getPanelTitleFromUrlPath } from "../../../../src/data/panel"; |  | ||||||
| import { checkLovelaceConfig } from "../../../../src/panels/lovelace/common/check-lovelace-config"; |  | ||||||
|  |  | ||||||
| const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = { | const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = { | ||||||
|   strategy: { |   strategy: { | ||||||
| @@ -109,7 +109,7 @@ export class HcMain extends HassElement { | |||||||
|   protected firstUpdated(changedProps) { |   protected firstUpdated(changedProps) { | ||||||
|     super.firstUpdated(changedProps); |     super.firstUpdated(changedProps); | ||||||
|     import("./hc-lovelace"); |     import("./hc-lovelace"); | ||||||
|     import("../../../../src/resources/append-ha-style"); |     import("../../../../src/resources/ha-style"); | ||||||
|  |  | ||||||
|     window.addEventListener("location-changed", () => { |     window.addEventListener("location-changed", () => { | ||||||
|       const panelPath = `/${this._urlPath || "lovelace"}/`; |       const panelPath = `/${this._urlPath || "lovelace"}/`; | ||||||
| @@ -144,10 +144,10 @@ export class HcMain extends HassElement { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (senderId) { |     if (senderId) { | ||||||
|       this._sendMessage(senderId, status); |       this.sendMessage(senderId, status); | ||||||
|     } else { |     } else { | ||||||
|       for (const sender of castContext.getSenders()) { |       for (const sender of castContext.getSenders()) { | ||||||
|         this._sendMessage(sender.id, status); |         this.sendMessage(sender.id, status); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -164,10 +164,10 @@ export class HcMain extends HassElement { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     if (senderId) { |     if (senderId) { | ||||||
|       this._sendMessage(senderId, error); |       this.sendMessage(senderId, error); | ||||||
|     } else { |     } else { | ||||||
|       for (const sender of castContext.getSenders()) { |       for (const sender of castContext.getSenders()) { | ||||||
|         this._sendMessage(sender.id, error); |         this.sendMessage(sender.id, error); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -309,7 +309,7 @@ export class HcMain extends HassElement { | |||||||
|               "../../../../src/panels/lovelace/strategies/get-strategy" |               "../../../../src/panels/lovelace/strategies/get-strategy" | ||||||
|             ); |             ); | ||||||
|             const config = await generateLovelaceDashboardStrategy( |             const config = await generateLovelaceDashboardStrategy( | ||||||
|               rawConfig, |               rawConfig.strategy, | ||||||
|               this.hass! |               this.hass! | ||||||
|             ); |             ); | ||||||
|             this._handleNewLovelaceConfig(config); |             this._handleNewLovelaceConfig(config); | ||||||
| @@ -351,19 +351,16 @@ export class HcMain extends HassElement { | |||||||
|       "../../../../src/panels/lovelace/strategies/get-strategy" |       "../../../../src/panels/lovelace/strategies/get-strategy" | ||||||
|     ); |     ); | ||||||
|     this._handleNewLovelaceConfig( |     this._handleNewLovelaceConfig( | ||||||
|       await generateLovelaceDashboardStrategy(DEFAULT_CONFIG, this.hass!) |       await generateLovelaceDashboardStrategy( | ||||||
|  |         DEFAULT_CONFIG.strategy, | ||||||
|  |         this.hass! | ||||||
|  |       ) | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) { |   private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) { | ||||||
|     const title = getPanelTitleFromUrlPath( |     castContext.setApplicationState(lovelaceConfig.title || ""); | ||||||
|       this.hass!, |     this._lovelaceConfig = lovelaceConfig; | ||||||
|       this._urlPath || "lovelace" |  | ||||||
|     ); |  | ||||||
|     castContext.setApplicationState(title || ""); |  | ||||||
|     this._lovelaceConfig = checkLovelaceConfig( |  | ||||||
|       lovelaceConfig |  | ||||||
|     ) as LovelaceConfig; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _handleShowDemo(_msg: ShowDemoMessage) { |   private _handleShowDemo(_msg: ShowDemoMessage) { | ||||||
| @@ -391,7 +388,7 @@ export class HcMain extends HassElement { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _sendMessage(senderId: string, response: any) { |   private sendMessage(senderId: string, response: any) { | ||||||
|     castContext.sendCustomMessage(CAST_NS, senderId, response); |     castContext.sendCustomMessage(CAST_NS, senderId, response); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								cast/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								cast/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | import webpack from "../build-scripts/webpack.cjs"; | ||||||
|  | import env from "../build-scripts/env.cjs"; | ||||||
|  |  | ||||||
|  | export default webpack.createCastConfig({ | ||||||
|  |   isProdBuild: env.isProdBuild(), | ||||||
|  |   isStatsBuild: env.isStatsBuild(), | ||||||
|  |   latestBuild: true, | ||||||
|  | }); | ||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 30 KiB | 
| @@ -1,5 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
|  |  | ||||||
| self.addEventListener("fetch", (event) => { |  | ||||||
|   event.respondWith(fetch(event.request)); |  | ||||||
| }); |  | ||||||
							
								
								
									
										10
									
								
								demo/rollup.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								demo/rollup.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | import rollup from "../build-scripts/rollup.cjs"; | ||||||
|  | import env from "../build-scripts/env.cjs"; | ||||||
|  |  | ||||||
|  | const config = rollup.createDemoConfig({ | ||||||
|  |   isProdBuild: env.isProdBuild(), | ||||||
|  |   latestBuild: true, | ||||||
|  |   isStatsBuild: env.isStatsBuild(), | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export default { ...config.inputOptions, output: config.outputOptions }; | ||||||
| @@ -4,6 +4,11 @@ | |||||||
| # Stop on errors | # Stop on errors | ||||||
| set -e | set -e | ||||||
|  |  | ||||||
| cd "$(dirname "$0")/../.." | cd "$(dirname "$0")/.." | ||||||
|  |  | ||||||
| ./node_modules/.bin/gulp analyze-demo | export STATS=1 | ||||||
|  | statsfile="compilation-stats-demo.json" | ||||||
|  |  | ||||||
|  | ./node_modules/.bin/webpack-cli --profile --node-env=production --json=$statsfile | ||||||
|  | npx webpack-bundle-analyzer $statsfile dist/frontend_latest | ||||||
|  | rm -f $statsfile | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { convertEntities } from "../../../../src/fake_data/entity"; | import { convertEntities } from "../../../../src/fake_data/entity"; | ||||||
| import type { DemoConfig } from "../types"; | import { DemoConfig } from "../types"; | ||||||
|  |  | ||||||
| export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => | export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => | ||||||
|   convertEntities({ |   convertEntities({ | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import type { DemoConfig } from "../types"; | import { DemoConfig } from "../types"; | ||||||
| import { demoEntitiesArsaboo } from "./entities"; | import { demoEntitiesArsaboo } from "./entities"; | ||||||
| import { demoLovelaceArsaboo } from "./lovelace"; | import { demoLovelaceArsaboo } from "./lovelace"; | ||||||
| import { demoThemeArsaboo } from "./theme"; | import { demoThemeArsaboo } from "./theme"; | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import type { DemoConfig } from "../types"; | import { DemoConfig } from "../types"; | ||||||
|  |  | ||||||
| export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({ | export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({ | ||||||
|   title: "Home Assistant", |   title: "Home Assistant", | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; | import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; | ||||||
| import type { Lovelace } from "../../../src/panels/lovelace/types"; | import { Lovelace } from "../../../src/panels/lovelace/types"; | ||||||
| import { energyEntities } from "../stubs/entities"; | import { energyEntities } from "../stubs/entities"; | ||||||
| import type { DemoConfig } from "./types"; | import { DemoConfig } from "./types"; | ||||||
|  |  | ||||||
| export const demoConfigs: (() => Promise<DemoConfig>)[] = [ | export const demoConfigs: Array<() => Promise<DemoConfig>> = [ | ||||||
|   () => import("./sections").then((mod) => mod.demoSections), |   () => import("./sections").then((mod) => mod.demoSections), | ||||||
|   () => import("./arsaboo").then((mod) => mod.demoArsaboo), |   () => import("./arsaboo").then((mod) => mod.demoArsaboo), | ||||||
|   () => import("./teachingbirds").then((mod) => mod.demoTeachingbirds), |   () => import("./teachingbirds").then((mod) => mod.demoTeachingbirds), | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { convertEntities } from "../../../../src/fake_data/entity"; | import { convertEntities } from "../../../../src/fake_data/entity"; | ||||||
| import type { DemoConfig } from "../types"; | import { DemoConfig } from "../types"; | ||||||
|  |  | ||||||
| export const demoEntitiesJimpower: DemoConfig["entities"] = () => | export const demoEntitiesJimpower: DemoConfig["entities"] = () => | ||||||
|   convertEntities({ |   convertEntities({ | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import type { DemoConfig } from "../types"; | import { DemoConfig } from "../types"; | ||||||
| import { demoEntitiesJimpower } from "./entities"; | import { demoEntitiesJimpower } from "./entities"; | ||||||
| import { demoLovelaceJimpower } from "./lovelace"; | import { demoLovelaceJimpower } from "./lovelace"; | ||||||
| import { demoThemeJimpower } from "./theme"; | import { demoThemeJimpower } from "./theme"; | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import "../../custom-cards/card-modder"; | import "../../custom-cards/card-modder"; | ||||||
| import type { DemoConfig } from "../types"; | import { DemoConfig } from "../types"; | ||||||
|  |  | ||||||
| export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ | export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ | ||||||
|   name: "Kingia Castle", |   name: "Kingia Castle", | ||||||
|   | |||||||
| @@ -1,28 +1,37 @@ | |||||||
| export const demoThemeJimpower = () => ({ | export const demoThemeJimpower = () => ({ | ||||||
|   "text-primary-color": "var(--primary-text-color)", |   "text-primary-color": "var(--primary-text-color)", | ||||||
|  |   "paper-item-icon-color": "var(--primary-text-color)", | ||||||
|   "primary-color": "#5294E2", |   "primary-color": "#5294E2", | ||||||
|   "label-badge-red": "var(--accent-color)", |   "label-badge-red": "var(--accent-color)", | ||||||
|  |   "paper-tabs-selection-bar-color": "green", | ||||||
|   "light-primary-color": "var(--accent-color)", |   "light-primary-color": "var(--accent-color)", | ||||||
|   "primary-background-color": "#383C45", |   "primary-background-color": "#383C45", | ||||||
|   "primary-text-color": "#FFFFFF", |   "primary-text-color": "#FFFFFF", | ||||||
|  |   "paper-item-selected_-_background-color": "#434954", | ||||||
|   "secondary-background-color": "#383C45", |   "secondary-background-color": "#383C45", | ||||||
|   "disabled-text-color": "#7F848E", |   "disabled-text-color": "#7F848E", | ||||||
|  |   "paper-item-icon_-_color": "green", | ||||||
|   "paper-grey-200": "#414A59", |   "paper-grey-200": "#414A59", | ||||||
|   "label-badge-background-color": "#2E333A", |   "label-badge-background-color": "#2E333A", | ||||||
|   "sidebar-icon-color": "var(--state-icon-color)", |   "paper-card-header-color": "var(--accent-color)", | ||||||
|  |   "sidebar-icon-color": "var(--paper-item-icon-color)", | ||||||
|  |   "paper-listbox-background-color": "#2E333A", | ||||||
|   "table-row-background-color": "#353840", |   "table-row-background-color": "#353840", | ||||||
|   "paper-grey-50": "var(--primary-text-color)", |   "paper-grey-50": "var(--primary-text-color)", | ||||||
|   "switch-checked-color": "var(--accent-color)", |   "switch-checked-color": "var(--accent-color)", | ||||||
|  |   "paper-dialog-background-color": "#434954", | ||||||
|   "secondary-text-color": "#5294E2", |   "secondary-text-color": "#5294E2", | ||||||
|   "error-color": "#E45E65", |   "error-color": "#E45E65", | ||||||
|   "divider-color": "rgba(0, 0, 0, .12)", |   "divider-color": "rgba(0, 0, 0, .12)", | ||||||
|   "success-color": "#39E949", |   "success-color": "#39E949", | ||||||
|   "switch-unchecked-button-color": "var(--disabled-text-color)", |   "switch-unchecked-button-color": "var(--disabled-text-color)", | ||||||
|   "label-badge-border-color": "green", |   "label-badge-border-color": "green", | ||||||
|  |   "paper-listbox-color": "var(--primary-color)", | ||||||
|   "card-background-color": "#434954", |   "card-background-color": "#434954", | ||||||
|   "label-badge-text-color": "var(--primary-text-color)", |   "label-badge-text-color": "var(--primary-text-color)", | ||||||
|   "switch-unchecked-track-color": "var(--disabled-text-color)", |   "switch-unchecked-track-color": "var(--disabled-text-color)", | ||||||
|   "dark-primary-color": "var(--accent-color)", |   "dark-primary-color": "var(--accent-color)", | ||||||
|  |   "paper-item-icon-active-color": "#F9C536", | ||||||
|   "accent-color": "#E45E65", |   "accent-color": "#E45E65", | ||||||
|   "table-row-alternative-background-color": "#3E424B", |   "table-row-alternative-background-color": "#3E424B", | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { convertEntities } from "../../../../src/fake_data/entity"; | import { convertEntities } from "../../../../src/fake_data/entity"; | ||||||
| import type { DemoConfig } from "../types"; | import { DemoConfig } from "../types"; | ||||||
|  |  | ||||||
| export const demoEntitiesKernehed: DemoConfig["entities"] = () => | export const demoEntitiesKernehed: DemoConfig["entities"] = () => | ||||||
|   convertEntities({ |   convertEntities({ | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user