From a31a8fd4dd30128a2e356fe8c8d60c9d2ed1c4f3 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Sat, 18 Jul 2026 17:14:10 +0200 Subject: [PATCH] fix(ci): restore custom widget checks --- .github/workflows/ci.yml | 12 ++ .../advanced/environment-variables/index.mdx | 6 +- apps/workshop/Dockerfile | 3 +- .../src/core/component-props.generated.json | 108 ++++-------------- packages/widgets/src/test/translation.spec.ts | 47 +++++--- 5 files changed, 65 insertions(+), 111 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 296ee4a72..79452a31e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -312,6 +312,18 @@ jobs: "${PREVIEW_IMAGE}@${AMD64_DIGEST}" \ "${PREVIEW_IMAGE}@${ARM64_DIGEST}" + - name: Verify preview manifest platforms + if: "!env.ACT" + run: | + image="${PREVIEW_IMAGE}:${PREVIEW_TAG}" + manifest="$(docker buildx imagetools inspect "$image" --raw)" + for architecture in amd64 arm64; do + jq --exit-status --arg architecture "$architecture" \ + 'any(.manifests[]; .platform.os == "linux" and .platform.architecture == $architecture)' \ + <<<"$manifest" >/dev/null + done + docker buildx imagetools inspect "$image" + cleanup: name: Clean up preview image if: github.event_name == 'pull_request_target' || (github.event_name == 'workflow_dispatch' && inputs.operation == 'cleanup') diff --git a/apps/docs/docs/advanced/environment-variables/index.mdx b/apps/docs/docs/advanced/environment-variables/index.mdx index 7bb1fa61a..904aea1ed 100644 --- a/apps/docs/docs/advanced/environment-variables/index.mdx +++ b/apps/docs/docs/advanced/environment-variables/index.mdx @@ -105,7 +105,7 @@ External redis is currently not supported with Proxmox Community Scripts The advanced deployments environment variables should only be used if you know what you are doing. ::: -| Environment Variable | Description | -| ------------------------ | -------------------------------------------------- | -| `DB_MIGRATIONS_DISABLED` | Disable db migrations. For example for helm charts | +| Environment Variable | Description | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------- | +| `DB_MIGRATIONS_DISABLED` | Disable db migrations. For example for helm charts | | `NEXT_PUBLIC_WORKSHOP_API_URL` | Build-time public origin for a separately hosted Workshop PocketBase service. Defaults to `https://homarr.dev`. | diff --git a/apps/workshop/Dockerfile b/apps/workshop/Dockerfile index c47ad353b..36d06f243 100644 --- a/apps/workshop/Dockerfile +++ b/apps/workshop/Dockerfile @@ -6,13 +6,12 @@ ENV COREPACK_HOME=/corepack RUN mkdir -p /corepack && corepack enable pnpm && corepack install --global pnpm@11.11.0 COPY pnpm-lock.yaml pnpm-workspace.yaml package.json turbo.json ./ COPY patches ./patches -RUN --mount=type=cache,id=workshop-pnpm-store,target=/pnpm/store pnpm config set store-dir /pnpm/store && pnpm fetch --ignore-scripts COPY apps/docs ./apps/docs COPY packages/custom-widgets ./packages/custom-widgets COPY packages/definitions ./packages/definitions COPY packages/workshop ./packages/workshop COPY tooling/typescript ./tooling/typescript -RUN --mount=type=cache,id=workshop-pnpm-store,target=/pnpm/store pnpm install --recursive --offline --frozen-lockfile +RUN --mount=type=cache,id=workshop-pnpm-store,target=/pnpm/store pnpm config set store-dir /pnpm/store && pnpm install --filter @homarr/docs... --frozen-lockfile FROM dependencies AS website RUN CI=true SKIP_ENV_VALIDATION=true pnpm turbo build --filter=@homarr/docs diff --git a/packages/custom-widgets/src/core/component-props.generated.json b/packages/custom-widgets/src/core/component-props.generated.json index b7611466f..352b0efcd 100644 --- a/packages/custom-widgets/src/core/component-props.generated.json +++ b/packages/custom-widgets/src/core/component-props.generated.json @@ -6862,16 +6862,8 @@ "width", "wrapperProps" ], - "Chip.Group": [ - "defaultValue", - "multiple", - "value" - ], - "ChipGroup": [ - "defaultValue", - "multiple", - "value" - ], + "Chip.Group": ["defaultValue", "multiple", "value"], + "ChipGroup": ["defaultValue", "multiple", "value"], "CloseButton": [ "attributes", "bd", @@ -8171,10 +8163,7 @@ "vocab", "w" ], - "CopyButton": [ - "timeout", - "value" - ], + "CopyButton": ["timeout", "value"], "DataList": [ "about", "accessKey", @@ -12187,10 +12176,7 @@ "withOverlay", "withRoles" ], - "HoverCard.Target": [ - "eventPropsWrapperName", - "popupType" - ], + "HoverCard.Target": ["eventPropsWrapperName", "popupType"], "HoverCard.Dropdown": [ "about", "accessKey", @@ -12346,10 +12332,7 @@ "vocab", "w" ], - "HoverCard.Group": [ - "closeDelay", - "openDelay" - ], + "HoverCard.Group": ["closeDelay", "openDelay"], "HoverCardDropdown": [ "about", "accessKey", @@ -12505,14 +12488,8 @@ "vocab", "w" ], - "HoverCardGroup": [ - "closeDelay", - "openDelay" - ], - "HoverCardTarget": [ - "eventPropsWrapperName", - "popupType" - ], + "HoverCardGroup": ["closeDelay", "openDelay"], + "HoverCardTarget": ["eventPropsWrapperName", "popupType"], "HueSlider": [ "about", "accessKey", @@ -15762,10 +15739,7 @@ "vocab", "w" ], - "Menu.CheckboxGroup": [ - "defaultValue", - "value" - ], + "Menu.CheckboxGroup": ["defaultValue", "value"], "Menu.RadioItem": [ "about", "accessKey", @@ -15930,18 +15904,9 @@ "vocab", "w" ], - "Menu.RadioGroup": [ - "defaultValue", - "value" - ], - "Menu.ContextMenu": [ - "disabled", - "longPressDelay" - ], - "MenuCheckboxGroup": [ - "defaultValue", - "value" - ], + "Menu.RadioGroup": ["defaultValue", "value"], + "Menu.ContextMenu": ["disabled", "longPressDelay"], + "MenuCheckboxGroup": ["defaultValue", "value"], "MenuCheckboxItem": [ "about", "accessKey", @@ -16106,10 +16071,7 @@ "vocab", "w" ], - "MenuContextMenu": [ - "disabled", - "longPressDelay" - ], + "MenuContextMenu": ["disabled", "longPressDelay"], "MenuDivider": [ "about", "accessKey", @@ -16638,10 +16600,7 @@ "vocab", "w" ], - "MenuRadioGroup": [ - "defaultValue", - "value" - ], + "MenuRadioGroup": ["defaultValue", "value"], "MenuRadioItem": [ "about", "accessKey", @@ -21530,10 +21489,7 @@ "withOverlay", "withRoles" ], - "Popover.Target": [ - "key", - "popupType" - ], + "Popover.Target": ["key", "popupType"], "Popover.Dropdown": [ "about", "accessKey", @@ -21689,14 +21645,8 @@ "vocab", "w" ], - "Popover.ContextMenu": [ - "disabled", - "longPressDelay" - ], - "PopoverContextMenu": [ - "disabled", - "longPressDelay" - ], + "Popover.ContextMenu": ["disabled", "longPressDelay"], + "PopoverContextMenu": ["disabled", "longPressDelay"], "PopoverDropdown": [ "about", "accessKey", @@ -21852,10 +21802,7 @@ "vocab", "w" ], - "PopoverTarget": [ - "key", - "popupType" - ], + "PopoverTarget": ["key", "popupType"], "Progress": [ "about", "accessKey", @@ -30320,9 +30267,7 @@ "vocab", "w" ], - "Table.DataRenderer": [ - "data" - ], + "Table.DataRenderer": ["data"], "TableCaption": [ "about", "accessKey", @@ -34705,10 +34650,7 @@ "vocab", "w" ], - "Tooltip.Group": [ - "closeDelay", - "openDelay" - ], + "Tooltip.Group": ["closeDelay", "openDelay"], "TooltipFloating": [ "about", "accessKey", @@ -34872,10 +34814,7 @@ "vocab", "w" ], - "TooltipGroup": [ - "closeDelay", - "openDelay" - ], + "TooltipGroup": ["closeDelay", "openDelay"], "Tree": [ "about", "accessKey", @@ -39832,12 +39771,7 @@ "withSuccessStyles", "wrapperProps" ], - "TimeValue": [ - "amPmLabels", - "format", - "value", - "withSeconds" - ], + "TimeValue": ["amPmLabels", "format", "value", "withSeconds"], "WeekdaysRow": [ "about", "accessKey", diff --git a/packages/widgets/src/test/translation.spec.ts b/packages/widgets/src/test/translation.spec.ts index 8af82dc25..80de27dc4 100644 --- a/packages/widgets/src/test/translation.spec.ts +++ b/packages/widgets/src/test/translation.spec.ts @@ -6,37 +6,46 @@ import { createLanguageMapping } from "@homarr/translation"; import { widgetImports } from ".."; +const doesOptionRenderTranslatedCopy = (option: unknown) => { + if (typeof option !== "object" || option === null || !("type" in option)) return false; + return option.type !== "internal" && option.type !== "customWidgetConfiguration"; +}; + describe("Widget properties with description should have matching translations", async () => { const enTranslation = await createLanguageMapping().en(); objectEntries(widgetImports).forEach(([key, value]) => { - Object.entries(value.definition.createOptions({} as SettingsContextProps)).forEach(([optionKey, optionValue_]) => { - const optionValue = optionValue_ as { withDescription: boolean }; - it(`should have matching translations for ${optionKey} option description of ${key} widget`, () => { - const option = enTranslation.default.widget[key].option; - if (!(optionKey in option)) { - throw new Error(`Option ${optionKey} not found in translation`); - } - const value = option[optionKey as keyof typeof option]; + Object.entries(value.definition.createOptions({} as SettingsContextProps)) + .filter(([, option]) => doesOptionRenderTranslatedCopy(option)) + .forEach(([optionKey, optionValue_]) => { + const optionValue = optionValue_ as { withDescription: boolean }; + it(`should have matching translations for ${optionKey} option description of ${key} widget`, () => { + const option = enTranslation.default.widget[key].option; + if (!(optionKey in option)) { + throw new Error(`Option ${optionKey} not found in translation`); + } + const value = option[optionKey as keyof typeof option]; - expect("description" in value).toBe(optionValue.withDescription); + expect("description" in value).toBe(optionValue.withDescription); + }); }); - }); }); }); describe("Widget properties should have matching name translations", async () => { const enTranslation = await createLanguageMapping().en(); objectEntries(widgetImports).forEach(([key, value]) => { - Object.keys(value.definition.createOptions({} as SettingsContextProps)).forEach((optionKey) => { - it(`should have matching translations for ${optionKey} option name of ${key} widget`, () => { - const option = enTranslation.default.widget[key].option; - if (!(optionKey in option)) { - throw new Error(`Option ${optionKey} not found in translation`); - } - const value = option[optionKey as keyof typeof option]; + Object.entries(value.definition.createOptions({} as SettingsContextProps)) + .filter(([, option]) => doesOptionRenderTranslatedCopy(option)) + .forEach(([optionKey]) => { + it(`should have matching translations for ${optionKey} option name of ${key} widget`, () => { + const option = enTranslation.default.widget[key].option; + if (!(optionKey in option)) { + throw new Error(`Option ${optionKey} not found in translation`); + } + const value = option[optionKey as keyof typeof option]; - expect("label" in value).toBe(true); + expect("label" in value).toBe(true); + }); }); - }); }); });