Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ permissions:
env:
HYPERFRAMES_NO_TELEMETRY: "1"

# Graphite can update a branch and its PR base separately for the same head
# SHA. Keep only the newest expensive regression run for each PR/ref.
concurrency:
group: regression-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

on:
pull_request:
push:
Expand Down Expand Up @@ -111,7 +117,10 @@ jobs:
load: true
tags: hyperframes-producer:test
cache-from: type=gha,scope=regression-test-image
cache-to: type=gha,mode=max,scope=regression-test-image
# PR matrices can fan out across many stacked branches at once. Let
# them consume the shared cache, but keep a single writer on main so
# concurrent exports cannot exhaust the Actions cache service.
cache-to: ${{ github.event_name == 'push' && 'type=gha,mode=max,scope=regression-test-image' || '' }}

- name: "Run regression shard: ${{ matrix.shard }}"
run: |
Expand Down
5 changes: 5 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ pre-commit:
# split paths containing spaces. It skips files under the limit, anything
# routed through LFS, and registry/ assets. Tune via HF_MAX_NONLFS_KB.
run: ./scripts/check-large-files.sh
tracked-artifacts:
# Keep ignored dependency trees and platform metadata out of commits.
# `git ls-files` observes the staged index, so staged removals pass and
# an accidental force-add fails before the commit is created.
run: bun run check:tracked-artifacts
filesize:
# Scoped to packages/studio — the 600 LOC limit is a studio architecture
# standard enforced as part of the App.tsx decomposition work. Player and
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@
"changelog:weekly": "tsx scripts/changelog-weekly.ts",
"sync-schemas": "tsx scripts/sync-schemas.ts",
"sync-schemas:check": "tsx scripts/sync-schemas.ts --check",
"lint": "oxlint . && tsx scripts/lint-skills.ts",
"lint": "bun run check:tracked-artifacts && oxlint . && tsx scripts/lint-skills.ts",
"lint:skills": "tsx scripts/lint-skills.ts",
"lint:fix": "oxlint --fix .",
"check:tracked-artifacts": "node scripts/check-tracked-artifacts.mjs",
"format": "oxfmt .",
"test": "bun run --filter '*' test",
"player:perf": "bun run --filter @hyperframes/player perf",
"format:check": "oxfmt --check .",
"knip": "knip",
"test:scripts": "node --import tsx --test scripts/validate-release-channel.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/verify-packed-manifests.test.mjs",
"test:scripts": "node --import tsx --test scripts/check-tracked-artifacts.test.mjs scripts/validate-release-channel.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/verify-packed-manifests.test.mjs",
"test:skills": "node --test 'skills/**/*.test.mjs'",
"generate:previews": "tsx scripts/generate-template-previews.ts",
"generate:catalog-previews": "tsx scripts/generate-catalog-previews.ts",
Expand Down
Binary file removed packages/producer/.DS_Store
Binary file not shown.
Binary file removed packages/producer/tests/.DS_Store
Binary file not shown.
Binary file removed packages/producer/tests/style-1-prod/.DS_Store
Binary file not shown.
48 changes: 48 additions & 0 deletions scripts/check-tracked-artifacts.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { spawnSync } from "node:child_process";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";

const FORBIDDEN_BASENAMES = new Set([".DS_Store"]);

export function isForbiddenTrackedPath(filePath) {
const normalized = filePath.replaceAll("\\", "/");
const segments = normalized.split("/");
return segments.includes("node_modules") || FORBIDDEN_BASENAMES.has(segments.at(-1));
}

export function findForbiddenTrackedPaths(filePaths) {
return filePaths.filter(isForbiddenTrackedPath).sort();
}

export function readTrackedPaths(cwd = process.cwd()) {
const result = spawnSync("git", ["ls-files", "-z"], {
cwd,
encoding: "utf8",
maxBuffer: 16 * 1024 * 1024,
});
if (result.error) throw result.error;
if (result.status !== 0) {
throw new Error(result.stderr.trim() || `git ls-files exited with status ${result.status}`);
}
return result.stdout.split("\0").filter(Boolean);
}

export function checkTrackedArtifacts(cwd = process.cwd()) {
return findForbiddenTrackedPaths(readTrackedPaths(cwd));
}

function main() {
const forbidden = checkTrackedArtifacts();
if (forbidden.length === 0) {
console.log("Tracked artifact check passed.");
return;
}

console.error("Forbidden generated artifacts are tracked by Git:");
for (const filePath of forbidden) console.error(`- ${filePath}`);
console.error("Remove these paths from the index; .gitignore already excludes them.");
process.exitCode = 1;
}

const entryPath = process.argv[1] ? resolve(process.argv[1]) : null;
if (entryPath === fileURLToPath(import.meta.url)) main();
32 changes: 32 additions & 0 deletions scripts/check-tracked-artifacts.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { findForbiddenTrackedPaths, isForbiddenTrackedPath } from "./check-tracked-artifacts.mjs";

describe("tracked artifact check", () => {
it("rejects node_modules at any directory depth", () => {
assert.equal(isForbiddenTrackedPath("node_modules/pkg/index.js"), true);
assert.equal(isForbiddenTrackedPath("packages/producer/node_modules/pkg"), true);
assert.equal(isForbiddenTrackedPath("packages\\producer\\node_modules\\pkg"), true);
});

it("rejects platform metadata by basename", () => {
assert.equal(isForbiddenTrackedPath(".DS_Store"), true);
assert.equal(isForbiddenTrackedPath("packages/producer/tests/.DS_Store"), true);
});

it("does not reject similarly named source paths", () => {
assert.equal(isForbiddenTrackedPath("docs/node_modules-policy.md"), false);
assert.equal(isForbiddenTrackedPath("packages/producer/src/DS_Store.ts"), false);
});

it("returns a deterministic sorted list", () => {
assert.deepEqual(
findForbiddenTrackedPaths([
"packages/z/.DS_Store",
"packages/producer/src/index.ts",
"packages/a/node_modules/pkg",
]),
["packages/a/node_modules/pkg", "packages/z/.DS_Store"],
);
});
});
Loading