Skip to content

feat: add Farming Labs favicon #445

feat: add Farming Labs favicon

feat: add Farming Labs favicon #445

Workflow file for this run

name: CI
on:
push:
branches: [main, master]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint, format & typecheck
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm format:check
- run: pnpm typecheck
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 35
env:
FARM_ORM_LOCAL_PG_ADMIN_URL: postgres://postgres:postgres@127.0.0.1:5432/postgres
FARM_ORM_LOCAL_MYSQL_ADMIN_URL: mysql://root:root@127.0.0.1:3306
FARM_ORM_LOCAL_MONGODB_URL: mongodb://127.0.0.1:27017
FARM_ORM_SKIP_REAL_FIRESTORE_TESTS: "1"
FARM_ORM_DEMO_REQUIRE_LOCAL: "1"
services:
postgres:
image: postgres:17
env:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 10
mysql:
image: mysql:8.4
env:
MYSQL_DATABASE: mysql
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: >-
--health-cmd "mysqladmin ping -h 127.0.0.1 -uroot -proot"
--health-interval 10s
--health-timeout 5s
--health-retries 20
mongodb:
image: mongo:7.0
ports:
- 27017:27017
options: >-
--health-cmd "mongosh --quiet --eval 'db.adminCommand({ ping: 1 })'"
--health-interval 10s
--health-timeout 5s
--health-retries 20
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test
- run: pnpm test:local:sql
- run: pnpm test:local:mongodb
- run: pnpm --filter demo test:local
security-audit:
name: Security audit
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm store cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install workspace dependencies
shell: bash
run: |
set -euo pipefail
pnpm install --frozen-lockfile
- name: Run security audit
shell: bash
run: |
set -euo pipefail
AUDIT_TREE_FILE="$(mktemp)"
trap 'rm -f "$AUDIT_TREE_FILE"' EXIT
pnpm list -r --json --depth Infinity > "$AUDIT_TREE_FILE"
AUDIT_TREE_FILE="$AUDIT_TREE_FILE" node --input-type=module <<'EOF'
import fs from "node:fs/promises";
const ADVISORY_URL =
"https://registry.npmjs.org/-/npm/v1/security/advisories/bulk";
const SEVERITY_ORDER = {
info: 0,
low: 1,
moderate: 2,
high: 3,
critical: 4,
};
const threshold = SEVERITY_ORDER.high;
const tree = JSON.parse(
await fs.readFile(process.env.AUDIT_TREE_FILE, "utf8"),
);
const versionsByName = new Map();
const seen = new Set();
function add(name, version) {
if (!name || !version) return;
const text = String(version);
if (
text.startsWith("link:") ||
text.startsWith("file:") ||
text.startsWith("workspace:")
) {
return;
}
let versions = versionsByName.get(name);
if (!versions) {
versions = new Set();
versionsByName.set(name, versions);
}
versions.add(text);
}
function visit(record) {
if (!record || typeof record !== "object") return;
for (const [name, dep] of Object.entries(record)) {
if (!dep || typeof dep !== "object") continue;
add(name, dep.version);
const key = dep.path ?? `${name}@${dep.version}`;
if (seen.has(key)) continue;
seen.add(key);
visit(dep.dependencies);
visit(dep.devDependencies);
visit(dep.optionalDependencies);
visit(dep.peerDependencies);
}
}
for (const workspace of tree) {
add(workspace.name, workspace.version);
visit(workspace.dependencies);
visit(workspace.devDependencies);
visit(workspace.optionalDependencies);
visit(workspace.peerDependencies);
}
const entries = [...versionsByName.entries()].sort(([left], [right]) =>
left.localeCompare(right),
);
const findings = [];
for (let index = 0; index < entries.length; index += 250) {
const payload = Object.fromEntries(
entries
.slice(index, index + 250)
.map(([name, versions]) => [name, [...versions].sort()]),
);
const response = await fetch(ADVISORY_URL, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(
`npm bulk advisory request failed with ${response.status}: ${await response.text()}`,
);
}
const advisoriesByName = await response.json();
for (const [name, advisories] of Object.entries(advisoriesByName)) {
if (!Array.isArray(advisories)) continue;
for (const advisory of advisories) {
const severity = advisory.severity ?? "info";
if ((SEVERITY_ORDER[severity] ?? -1) < threshold) continue;
findings.push({
name,
severity,
title: advisory.title,
vulnerableVersions: advisory.vulnerable_versions,
url: advisory.url,
});
}
}
}
console.log(
`Scanned ${entries.length} package names using npm bulk advisories.`,
);
if (findings.length === 0) {
console.log("No high or critical vulnerabilities found");
process.exit(0);
}
findings.sort((left, right) => {
const severityDelta =
(SEVERITY_ORDER[right.severity] ?? -1) -
(SEVERITY_ORDER[left.severity] ?? -1);
if (severityDelta !== 0) return severityDelta;
return left.name.localeCompare(right.name);
});
console.error(
`Found ${findings.length} high/critical vulnerabilit${findings.length === 1 ? "y" : "ies"}`,
);
for (const finding of findings) {
console.error(
`- [${finding.severity}] ${finding.name}: ${finding.title} (${finding.vulnerableVersions})`,
);
console.error(` ${finding.url}`);
}
process.exit(1);
EOF