Skip to content

Commit 439a439

Browse files
test: Add CI, linting, testing, and SVG utilities (#49)
1 parent 5e6b478 commit 439a439

File tree

10 files changed

+824
-45
lines changed

10 files changed

+824
-45
lines changed

.github/workflows/_ci-node.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
# This workflow is centrally managed in https://github.com/LizardByte/.github/
3+
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
4+
# the above-mentioned repo.
5+
6+
# To use, add the `npm-pkg` repository label to identify repositories that should trigger this workflow.
7+
8+
# This will run standard CI for Node.js/npm/TypeScript projects.
9+
10+
name: CI-Node
11+
permissions:
12+
contents: write # required for release_setup action
13+
14+
on:
15+
push:
16+
branches:
17+
- master
18+
pull_request:
19+
branches:
20+
- master
21+
22+
concurrency:
23+
group: "${{ github.workflow }}-${{ github.ref }}"
24+
cancel-in-progress: true
25+
26+
jobs:
27+
call-ci-node:
28+
name: CI-Node
29+
uses: LizardByte/.github/.github/workflows/__call-ci-node.yml@master
30+
if: ${{ github.repository != 'LizardByte/.github' }}
31+
secrets:
32+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
33+
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ node_modules/
66
package-lock.json
77

88
dist/
9+
10+
# coverage
11+
coverage/
12+
junit.xml

build.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ github_accounts=(
1818

1919
output_dir="$(pwd)/dist"
2020

21+
# Debug output for CI environment
22+
echo "GITHUB_JOB: ${GITHUB_JOB:-not set}"
23+
24+
# Check if we're running in a GitHub Actions job named "build"
25+
# If so, skip all token-dependent sections since secrets won't be available
26+
if [[ "${GITHUB_JOB}" = "build" ]]; then
27+
echo "Running in GitHub Actions 'build' job - skipping all token-dependent sections"
28+
echo "Done!"
29+
exit 0
30+
fi
31+
2132
echo "Building sponsors..."
2233
pushd configs/sponsors || exit 1
2334
npx contribkit --outputDir="${output_dir}" -w=800 --name=sponsors --force

configs/sponsors/contribkit.config.ts

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
defineConfig,
33
tierPresets,
44
} from '@lizardbyte/contribkit'
5+
import { extractSvgDimensions, createWrappedSponsorSvg } from './svg-utils.js'
56

67
const createThemeAwareSvgStyle = (pathClass: string, lightColor: string = '#000000', darkColor: string = '#ffffff') => `
78
<style>
@@ -127,49 +128,6 @@ const specialSupporters = [
127128
},
128129
];
129130

130-
// Function to create wrapped SVG for a special sponsor
131-
function createWrappedSponsorSvg(
132-
sponsor: { name: string, url: string },
133-
svgContent: string,
134-
svgWidth: number,
135-
svgHeight: number,
136-
height: number,
137-
x: number,
138-
y: number
139-
): string {
140-
const scale = height ? height / svgHeight : 1;
141-
const scaledWidth = svgWidth * scale;
142-
const scaledHeight = svgHeight * scale;
143-
144-
return `
145-
<a xlink:href="${sponsor.url}" class="contribkit-link" target="_blank" id="${sponsor.name.replace(/\s+/g, '')}">
146-
<svg x="${x}" y="${y}" width="${scaledWidth}" height="${scaledHeight}" viewBox="0 0 ${svgWidth} ${svgHeight}">
147-
<rect width="${svgWidth}" height="${svgHeight}" fill="transparent" />
148-
${svgContent}
149-
</svg>
150-
</a>`;
151-
}
152-
153-
// Function to extract dimensions from SVG content
154-
function extractSvgDimensions(svgContent: string): { width: number, height: number } {
155-
// Try to get dimensions from viewBox first
156-
const viewBoxMatch = svgContent.match(/viewBox=['"]([^'"]*)['"]/);
157-
if (viewBoxMatch) {
158-
const [, minX, minY, width, height] = viewBoxMatch[1].split(/\s+/).map(Number);
159-
if (!isNaN(width) && !isNaN(height)) {
160-
return { width, height };
161-
}
162-
}
163-
164-
// Try to get from width/height attributes
165-
const widthMatch = svgContent.match(/width=['"]([^'"]*)['"]/);
166-
const heightMatch = svgContent.match(/height=['"]([^'"]*)['"]/);
167-
168-
const width = widthMatch ? parseInt(widthMatch[1]) : 200;
169-
const height = heightMatch ? parseInt(heightMatch[1]) : 100;
170-
171-
return { width, height };
172-
}
173131

174132
export default defineConfig({
175133
tiers: [

configs/sponsors/svg-utils.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Utility functions for SVG dimension extraction
3+
*/
4+
5+
/**
6+
* Extracts dimensions from SVG content by parsing viewBox or width/height attributes
7+
* @param svgContent - The SVG content as a string
8+
* @returns Object containing width and height
9+
*/
10+
export function extractSvgDimensions(svgContent: string): { width: number; height: number } {
11+
// Try to get dimensions from viewBox first
12+
const viewBoxMatch = svgContent.match(/viewBox=['"]([^'"]*)['"]/);
13+
if (viewBoxMatch) {
14+
const [minX, minY, width, height] = viewBoxMatch[1].split(/\s+/).map(Number);
15+
// Check that both width and height are valid numbers (not NaN and not undefined)
16+
if (!Number.isNaN(width) && !Number.isNaN(height) && width !== undefined && height !== undefined) {
17+
return { width, height };
18+
}
19+
}
20+
21+
// Try to get from width/height attributes
22+
const widthMatch = svgContent.match(/width=['"]([^'"]*)['"]/);
23+
const heightMatch = svgContent.match(/height=['"]([^'"]*)['"]/);
24+
25+
const width = widthMatch ? Number.parseInt(widthMatch[1], 10) : 200;
26+
const height = heightMatch ? Number.parseInt(heightMatch[1], 10) : 100;
27+
28+
return { width, height };
29+
}
30+
31+
/**
32+
* Creates a wrapped SVG element for a sponsor with positioning and scaling
33+
* @param sponsor - Sponsor object with name and url
34+
* @param svgContent - The SVG content to wrap
35+
* @param svgWidth - Original width of the SVG
36+
* @param svgHeight - Original height of the SVG
37+
* @param height - Target height for the scaled SVG
38+
* @param x - X position for the SVG
39+
* @param y - Y position for the SVG
40+
* @returns Wrapped SVG string
41+
*/
42+
export function createWrappedSponsorSvg(
43+
sponsor: { name: string; url: string },
44+
svgContent: string,
45+
svgWidth: number,
46+
svgHeight: number,
47+
height: number,
48+
x: number,
49+
y: number
50+
): string {
51+
const scale = height ? height / svgHeight : 1;
52+
const scaledWidth = svgWidth * scale;
53+
const scaledHeight = svgHeight * scale;
54+
55+
return `
56+
<a xlink:href="${sponsor.url}" class="contribkit-link" target="_blank" id="${sponsor.name.replaceAll(/\s+/g, '')}">
57+
<svg x="${x}" y="${y}" width="${scaledWidth}" height="${scaledHeight}" viewBox="0 0 ${svgWidth} ${svgHeight}">
58+
<rect width="${svgWidth}" height="${svgHeight}" fill="transparent" />
59+
${svgContent}
60+
</svg>
61+
</a>`;
62+
}

eslint.config.mjs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import globals from "globals";
2+
import pluginJs from "@eslint/js";
3+
4+
export default [
5+
pluginJs.configs.recommended,
6+
{
7+
ignores: [
8+
"coverage/**",
9+
"dist/**",
10+
],
11+
},
12+
{
13+
languageOptions: {
14+
globals: {
15+
...globals.browser,
16+
...globals.node,
17+
"require": "readonly",
18+
},
19+
},
20+
plugins: {
21+
jest: {
22+
extends: ["eslint:recommended"],
23+
rules: {
24+
"jest/no-disabled-tests": "error",
25+
"jest/no-focused-tests": "error",
26+
"jest/no-identical-title": "error",
27+
"jest/prefer-to-have-length": "error",
28+
"jest/valid-expect": "error",
29+
},
30+
},
31+
},
32+
},
33+
];

package.json

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,69 @@
11
{
2+
"name": "@lizardbyte/contributors",
23
"type": "module",
4+
"repository": {
5+
"type": "git",
6+
"url": "git+https://github.com/LizardByte/contributors.git"
7+
},
8+
"version": "0.0.0",
9+
"description": "contribkit for LizardByte",
10+
"license": "MIT",
11+
"funding": "https://app.lizardbyte.dev",
12+
"homepage": "https://github.com/LizardByte/contributors#readme",
13+
"bugs": {
14+
"url": "https://github.com/LizardByte/contributors/issues"
15+
},
16+
"keywords": [
17+
"contributors",
18+
"sponsors",
19+
"github-sponsors"
20+
],
321
"scripts": {
4-
"build": "bash build.sh"
22+
"build": "bash build.sh",
23+
"test": "npm-run-all test:unit test:report test:lint test:typecheck",
24+
"test:unit": "jest --coverage",
25+
"test:report": "jest --reporters=jest-junit",
26+
"test:lint": "eslint .",
27+
"test:typecheck": "tsc --noEmit"
528
},
629
"dependencies": {
730
"@lizardbyte/contribkit": "2025.1130.1103"
831
},
932
"devDependencies": {
10-
"@types/node": "^24.0.0"
33+
"@types/jest": "30.0.0",
34+
"@types/node": "24.10.1",
35+
"eslint": "9.39.1",
36+
"eslint-plugin-jest": "29.2.1",
37+
"jest": "30.2.0",
38+
"jest-junit": "16.0.0",
39+
"npm-run-all": "4.1.5",
40+
"ts-jest": "29.4.5",
41+
"typescript": "5.9.3"
42+
},
43+
"jest": {
44+
"preset": "ts-jest/presets/default-esm",
45+
"testEnvironment": "node",
46+
"extensionsToTreatAsEsm": [
47+
".ts"
48+
],
49+
"moduleNameMapper": {
50+
"^(\\.{1,2}/.*)\\.js$": "$1"
51+
},
52+
"transform": {
53+
"^.+\\.tsx?$": [
54+
"ts-jest",
55+
{
56+
"useESM": true
57+
}
58+
]
59+
},
60+
"testMatch": [
61+
"**/tests/**/*.test.ts",
62+
"**/tests/**/*.spec.ts"
63+
],
64+
"collectCoverageFrom": [
65+
"configs/**/*.ts",
66+
"!configs/**/*.d.ts"
67+
]
1168
}
1269
}

0 commit comments

Comments
 (0)