From dd139162e64566784b5cf9f3a67f9e2975e5b5de Mon Sep 17 00:00:00 2001 From: Dmitri Khokhlov Date: Fri, 28 Nov 2025 16:44:40 -0800 Subject: [PATCH 1/6] fix graphql --- source/plugins/achievements/list/organizations.mjs | 4 ++-- source/plugins/achievements/list/users.mjs | 4 ++-- source/plugins/achievements/queries/achievements.graphql | 9 +++++---- .../plugins/achievements/queries/organizations.graphql | 6 +++++- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/source/plugins/achievements/list/organizations.mjs b/source/plugins/achievements/list/organizations.mjs index 70b22826460..6d70bb89c24 100644 --- a/source/plugins/achievements/list/organizations.mjs +++ b/source/plugins/achievements/list/organizations.mjs @@ -39,8 +39,8 @@ export default async function({list, login, data, computed, imports, graphql, qu //Managers { - const value = organization.projects.totalCount - const unlock = organization.projects.nodes?.shift() + const value = organization.projectsV2.totalCount + const unlock = organization.projectsV2.nodes?.shift() list.push({ title: "Managers", diff --git a/source/plugins/achievements/list/users.mjs b/source/plugins/achievements/list/users.mjs index 8b4ef03814e..de4877d3c87 100644 --- a/source/plugins/achievements/list/users.mjs +++ b/source/plugins/achievements/list/users.mjs @@ -55,8 +55,8 @@ export default async function({list, login, data, computed, imports, graphql, qu //Manager { - const value = user.projects.totalCount - const unlock = user.projects.nodes?.shift() + const value = user.projectsV2.totalCount + const unlock = user.projectsV2.nodes?.shift() list.push({ title: "Manager", diff --git a/source/plugins/achievements/queries/achievements.graphql b/source/plugins/achievements/queries/achievements.graphql index c7bcb13b334..bd3a33129ad 100644 --- a/source/plugins/achievements/queries/achievements.graphql +++ b/source/plugins/achievements/queries/achievements.graphql @@ -47,11 +47,12 @@ query AchievementsDefault { totalCount } } - projects(first: 1, orderBy: {field: CREATED_AT, direction: ASC}) { + projectsV2(first: 1, orderBy: {field: CREATED_AT, direction: ASC}) { totalCount - #nodes { This requires additional scopes :/ - # name - #} + nodes { + createdAt + title + } } packages(first: 1, orderBy: {direction: ASC, field: CREATED_AT}) { totalCount diff --git a/source/plugins/achievements/queries/organizations.graphql b/source/plugins/achievements/queries/organizations.graphql index f50cc56f4f6..06ae54205a1 100644 --- a/source/plugins/achievements/queries/organizations.graphql +++ b/source/plugins/achievements/queries/organizations.graphql @@ -21,8 +21,12 @@ query AchievementsOrganizations { } } } - projects(first: 1, orderBy: {field: CREATED_AT, direction: ASC}) { + projectsV2(first: 1, orderBy: {field: CREATED_AT, direction: ASC}) { totalCount + nodes { + createdAt + title + } } packages(first: 1, orderBy: {direction: ASC, field: CREATED_AT}) { totalCount From d14374c2741eb3f8a3ff167fdf4f9c19d1820960 Mon Sep 17 00:00:00 2001 From: Dmitri Khokhlov Date: Fri, 28 Nov 2025 16:44:42 -0800 Subject: [PATCH 2/6] Optimize Docker build: use pre-built base image for forks - Use original lowlighter/metrics Docker image as base - Only copy changed GraphQL/JS files (4 files) - Avoids rebuilding Chrome, Node modules, dependencies - Much faster builds for code-only changes --- Dockerfile | 40 ++++++++++++---------------------------- action.yml | 20 +++++++++++++++++++- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3ff5209a48a..bdbc59f587b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,17 @@ -# Base image -FROM node:20-bookworm-slim +# Use original pre-built image as base (contains all dependencies) +# This avoids rebuilding Chrome, Node modules, etc. when only code changes +ARG BASE_IMAGE=ghcr.io/lowlighter/metrics:v3.35-beta +FROM ${BASE_IMAGE} as base -# Copy repository -COPY . /metrics -WORKDIR /metrics +# Copy only our changed files (GraphQL queries and JS code) +# This is much faster than rebuilding everything +COPY source/plugins/achievements/queries/achievements.graphql /metrics/source/plugins/achievements/queries/achievements.graphql +COPY source/plugins/achievements/queries/organizations.graphql /metrics/source/plugins/achievements/queries/organizations.graphql +COPY source/plugins/achievements/list/users.mjs /metrics/source/plugins/achievements/list/users.mjs +COPY source/plugins/achievements/list/organizations.mjs /metrics/source/plugins/achievements/list/organizations.mjs -# Setup -RUN chmod +x /metrics/source/app/action/index.mjs \ - # Install latest chrome dev package, fonts to support major charsets and skip chromium download on puppeteer install - # Based on https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#running-puppeteer-in-docker - && apt-get update \ - && apt-get install -y wget gnupg ca-certificates libgconf-2-4 \ - && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ - && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ - && apt-get update \ - && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 libx11-xcb1 libxtst6 lsb-release --no-install-recommends \ - # Install deno for miscellaneous scripts - && apt-get install -y curl unzip \ - && curl -fsSL https://deno.land/x/install/install.sh | DENO_INSTALL=/usr/local sh \ - # Install ruby to support github licensed gem - && apt-get install -y ruby-full git g++ cmake pkg-config libssl-dev \ - && gem install licensed \ - # Install python for node-gyp - && apt-get install -y python3 \ - # Clean apt/lists - && rm -rf /var/lib/apt/lists/* \ - # Install node modules and rebuild indexes - && npm ci \ - && npm run build +# No need to rebuild - the base image already has everything installed +# Our code changes are just file replacements # Environment variables ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true diff --git a/action.yml b/action.yml index a1c037d003f..e47bdb16648 100644 --- a/action.yml +++ b/action.yml @@ -1578,7 +1578,20 @@ runs: # Forked action else echo "Using a forked version" + # For forks, we use the original pre-built image as base (via Dockerfile) + # This avoids rebuilding Chrome, Node modules, etc. - much faster! METRICS_IMAGE=metrics:forked-$METRICS_VERSION + # Pull base image first so Dockerfile can use it + # Check if version is beta + set +e + METRICS_IS_RELEASED=$(expr $(expr match $METRICS_VERSION .*-beta) == 0) + set -e + BASE_IMAGE_TAG=$METRICS_TAG + if [[ "$METRICS_IS_RELEASED" -eq "0" ]]; then + BASE_IMAGE_TAG="$BASE_IMAGE_TAG-beta" + fi + echo "Pulling base image ghcr.io/lowlighter/metrics:$BASE_IMAGE_TAG for faster builds" + docker image pull ghcr.io/lowlighter/metrics:$BASE_IMAGE_TAG || echo "Base image pull failed, will build from scratch" fi echo "Image name: $METRICS_IMAGE" @@ -1589,7 +1602,12 @@ runs: set -e if [[ "$METRICS_IMAGE_NEEDS_BUILD" -gt "0" ]]; then echo "Image $METRICS_IMAGE is not present locally, rebuilding it from Dockerfile" - docker build -t $METRICS_IMAGE . + # For forks, pass base image arg to use pre-built image + if [[ $METRICS_SOURCE != "lowlighter" ]]; then + docker build --build-arg BASE_IMAGE=ghcr.io/lowlighter/metrics:$BASE_IMAGE_TAG -t $METRICS_IMAGE . + else + docker build -t $METRICS_IMAGE . + fi else echo "Image $METRICS_IMAGE is present locally" fi From d95f8c5267d5c3143d5e65379c5800adb4d87393 Mon Sep 17 00:00:00 2001 From: Dmitri Khokhlov Date: Fri, 28 Nov 2025 16:44:43 -0800 Subject: [PATCH 3/6] Add better error handling to habits plugin for debugging --- source/plugins/habits/index.mjs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/source/plugins/habits/index.mjs b/source/plugins/habits/index.mjs index 3a1a437d602..9650ce5508d 100644 --- a/source/plugins/habits/index.mjs +++ b/source/plugins/habits/index.mjs @@ -139,7 +139,21 @@ export default async function({login, data, rest, imports, q, account}, {enabled } //Handle errors catch (error) { - throw imports.format.error(error) + // Log the actual error for debugging + console.debug(`metrics/compute/${login}/plugins > habits > error:`, error) + console.debug(`metrics/compute/${login}/plugins > habits > error message:`, error.message) + console.debug(`metrics/compute/${login}/plugins > habits > error stack:`, error.stack) + throw imports.format.error(error, { + descriptions: { + custom(error) { + // Provide more specific error messages + if (error.message) return error.message + if (error.response?.data?.message) return error.response.data.message + if (error.response?.data?.errors?.[0]?.message) return error.response.data.errors[0].message + return null + } + } + }) } } From 7a536ea3aef14877c19eff71d125aea16e5b5c90 Mon Sep 17 00:00:00 2001 From: Dmitri Khokhlov Date: Fri, 28 Nov 2025 16:44:44 -0800 Subject: [PATCH 4/6] Include habits plugin fix in Dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index bdbc59f587b..6f5fc9ec021 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,7 @@ COPY source/plugins/achievements/queries/achievements.graphql /metrics/source/pl COPY source/plugins/achievements/queries/organizations.graphql /metrics/source/plugins/achievements/queries/organizations.graphql COPY source/plugins/achievements/list/users.mjs /metrics/source/plugins/achievements/list/users.mjs COPY source/plugins/achievements/list/organizations.mjs /metrics/source/plugins/achievements/list/organizations.mjs +COPY source/plugins/habits/index.mjs /metrics/source/plugins/habits/index.mjs # No need to rebuild - the base image already has everything installed # Our code changes are just file replacements From 99a4240f08b33186a18df74d89960cf21b75363b Mon Sep 17 00:00:00 2001 From: Dmitri Khokhlov Date: Fri, 28 Nov 2025 16:44:46 -0800 Subject: [PATCH 5/6] Fix habits plugin: handle undefined commits and missing author property - Filter out null/undefined commits from payload.commits - Add safety check for missing author property - Prevents 'Cannot destructure property author of undefined' error --- source/plugins/habits/index.mjs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/source/plugins/habits/index.mjs b/source/plugins/habits/index.mjs index 9650ce5508d..2df7593ecc6 100644 --- a/source/plugins/habits/index.mjs +++ b/source/plugins/habits/index.mjs @@ -47,8 +47,13 @@ export default async function({login, data, rest, imports, q, account}, {enabled const patches = [ ...await Promise.allSettled( commits - .flatMap(({payload}) => payload.commits) - .filter(({author}) => data.shared["commits.authoring"].filter(authoring => author?.login?.toLocaleLowerCase().includes(authoring) || author?.email?.toLocaleLowerCase().includes(authoring) || author?.name?.toLocaleLowerCase().includes(authoring)).length) + .flatMap(({payload}) => payload?.commits ?? []) + .filter(commit => commit != null) // Filter out null/undefined commits + .filter(({author}) => { + // Handle missing author property + if (!author) return false + return data.shared["commits.authoring"].filter(authoring => author?.login?.toLocaleLowerCase().includes(authoring) || author?.email?.toLocaleLowerCase().includes(authoring) || author?.name?.toLocaleLowerCase().includes(authoring)).length + }) .map(async commit => (await rest.request(commit)).data.files), ), ] From ca58c6bb513e4a04afca0ad2e46b943b543f74cc Mon Sep 17 00:00:00 2001 From: Dmitri Khokhlov Date: Fri, 28 Nov 2025 16:44:47 -0800 Subject: [PATCH 6/6] Fix habits plugin: safer commit object handling to prevent destructuring errors --- source/plugins/habits/index.mjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/plugins/habits/index.mjs b/source/plugins/habits/index.mjs index 2df7593ecc6..c717ba0d3d1 100644 --- a/source/plugins/habits/index.mjs +++ b/source/plugins/habits/index.mjs @@ -48,9 +48,10 @@ export default async function({login, data, rest, imports, q, account}, {enabled ...await Promise.allSettled( commits .flatMap(({payload}) => payload?.commits ?? []) - .filter(commit => commit != null) // Filter out null/undefined commits - .filter(({author}) => { - // Handle missing author property + .filter(commit => commit != null && typeof commit === 'object') // Filter out null/undefined commits and ensure it's an object + .filter(commit => { + // Safely check author property + const author = commit?.author if (!author) return false return data.shared["commits.authoring"].filter(authoring => author?.login?.toLocaleLowerCase().includes(authoring) || author?.email?.toLocaleLowerCase().includes(authoring) || author?.name?.toLocaleLowerCase().includes(authoring)).length })