From 89014b47ba6660f75e416bfa8e53136ec38e6383 Mon Sep 17 00:00:00 2001 From: ggfevans Date: Thu, 19 Feb 2026 03:13:29 -0800 Subject: [PATCH 1/2] chore(ci): wire required PR/push validation workflow (#1278) --- .github/workflows/test.yml | 11 ++++++++++- docs/guides/TESTING.md | 11 +++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 027cf6054..518fb8c6b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,11 +1,20 @@ name: Test on: + pull_request: + branches: [main] + push: + branches: [main] workflow_dispatch: workflow_call: +concurrency: + group: test-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }} + cancel-in-progress: true + jobs: - test: + validate: + name: validate runs-on: ubuntu-latest permissions: contents: read diff --git a/docs/guides/TESTING.md b/docs/guides/TESTING.md index 29de960f5..46a9bf80d 100644 --- a/docs/guides/TESTING.md +++ b/docs/guides/TESTING.md @@ -104,6 +104,17 @@ Use it to: **Note:** Dev deployment only succeeds if lint and tests pass. +### Required CI Checks + +Branch protection should require the core CI validation check from `.github/workflows/test.yml`: + +- **Check name:** `Test / validate` + +This check runs on: + +- Pull requests targeting `main` (pre-merge gate) +- Pushes to `main` (post-merge validation) + ## Philosophy We follow the **Testing Trophy** approach: From 6f7b4eb921143e52a81ebc7d2096a0219a917458 Mon Sep 17 00:00:00 2001 From: ggfevans Date: Wed, 25 Feb 2026 00:49:10 -0800 Subject: [PATCH 2/2] fix(lint): resolve 7 pre-existing lint errors exposed by CI validation Fix 4 preserve-caught-error violations by adding { cause } to rethrown errors, and 3 no-useless-assignment violations by removing unnecessary initial values. These pre-existing issues on main were exposed when PR #1284 wired up lint validation on PRs. Co-Authored-By: Claude Opus 4.6 --- api/src/security.ts | 2 +- api/src/storage/filesystem.ts | 4 ++-- scripts/generate-netbox-homelab-candidates.ts | 3 ++- scripts/import-netbox-devices.ts | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/api/src/security.ts b/api/src/security.ts index ce1bb4191..0122ca434 100644 --- a/api/src/security.ts +++ b/api/src/security.ts @@ -281,7 +281,7 @@ function normalizeOrigin(input: string): string { return url.origin; } catch (error) { const reason = error instanceof Error ? error.message : String(error); - throw new Error(`Invalid CORS origin "${input}": ${reason}`); + throw new Error(`Invalid CORS origin "${input}": ${reason}`, { cause: error }); } } diff --git a/api/src/storage/filesystem.ts b/api/src/storage/filesystem.ts index 1485f7dab..9053fa918 100644 --- a/api/src/storage/filesystem.ts +++ b/api/src/storage/filesystem.ts @@ -332,7 +332,7 @@ async function migrateLegacyLayout( try { parsed = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA }); } catch (e) { - throw new Error(`Invalid YAML: ${e instanceof Error ? e.message : e}`); + throw new Error(`Invalid YAML: ${e instanceof Error ? e.message : e}`, { cause: e }); } const layout = LayoutFileSchema.safeParse(parsed); @@ -459,7 +459,7 @@ export async function saveLayout( parsed = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA }); } catch (e) { const message = e instanceof Error ? e.message : String(e); - throw new Error(`Invalid YAML: ${message}`); + throw new Error(`Invalid YAML: ${message}`, { cause: e }); } // Validate layout schema diff --git a/scripts/generate-netbox-homelab-candidates.ts b/scripts/generate-netbox-homelab-candidates.ts index 4ab9eec8f..3da8929c0 100644 --- a/scripts/generate-netbox-homelab-candidates.ts +++ b/scripts/generate-netbox-homelab-candidates.ts @@ -451,6 +451,7 @@ function getNetBoxGitInfo(netboxRoot: string): { const message = error instanceof Error ? error.message : String(error); throw new Error( `Failed to read git metadata from NetBox root "${netboxRoot}": ${message}`, + { cause: error }, ); } } @@ -472,7 +473,7 @@ function collectCandidates( if (!file.endsWith(".yaml")) continue; const yamlPath = join(vendorDir, file); - let parsed: NetBoxDoc | null = null; + let parsed: NetBoxDoc | null; try { parsed = yaml.load(readFileSync(yamlPath, "utf8")) as NetBoxDoc; } catch (error) { diff --git a/scripts/import-netbox-devices.ts b/scripts/import-netbox-devices.ts index 2a52a9dd9..13e4b14f4 100644 --- a/scripts/import-netbox-devices.ts +++ b/scripts/import-netbox-devices.ts @@ -317,8 +317,8 @@ async function importDevice( // e.g., device.slug = 'hpe-proliant-dl360-gen10' const imageSlug = device.slug; - let frontImage = false; - let rearImage = false; + let frontImage: boolean; + let rearImage: boolean; // Try to download front image (try .png first, then .jpg) const frontDest = join(destDir, `${imageSlug}.front.png`);