mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
ci: relax file-size ceilings by surface (#6485)
## Summary - raise Desktop Rust's differential file-size ceiling from 1,000 to 1,500 lines - raise Desktop frontend and Mobile ceilings from 1,000 to 1,200 lines - keep Web at 1,000 lines and preserve the existing no-growth ratchet above each ceiling ## Why The flat 1,000-line limit is forcing mechanical trimming in Desktop and Mobile even for small cohesive changes. Surface-specific ceilings relieve that pressure without granting Web or every authored component a blanket 2,000-line budget. ## Testing - `just file-size-check` - `cd desktop && pnpm exec biome check scripts/check-file-sizes.mjs` - `node --check mobile/scripts/check-file-sizes.mjs` - `git diff HEAD^ --check` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
This commit is contained in:
10
AGENTS.md
10
AGENTS.md
@@ -674,11 +674,13 @@ The mobile app lives in `mobile/` — a Flutter app using Riverpod + Hooks.
|
|||||||
over raw `Theme.of(context)` calls.
|
over raw `Theme.of(context)` calls.
|
||||||
- **Keep widgets small and composable.** One public widget per file; push
|
- **Keep widgets small and composable.** One public widget per file; push
|
||||||
private sub-widgets (`_Foo`) into sibling `part` files under a
|
private sub-widgets (`_Foo`) into sibling `part` files under a
|
||||||
`<page>/` folder rather than growing the page file. Hard ceiling:
|
`<page>/` folder rather than growing the page file. Mobile's hard ceiling is
|
||||||
**1000 lines/file**, enforced across Desktop, Web, and Mobile by the
|
**1200 lines/file**, enforced with the other surface-specific limits by the
|
||||||
repository-level `just file-size-check` gate (`just check`, CI, and every
|
repository-level `just file-size-check` gate (`just check`, CI, and every
|
||||||
pre-push). If the guard trips, **split the file — never bump the limit or add
|
pre-push). If an individual file trips the guard, **split the file — never
|
||||||
an override to slip under it.**
|
bump a surface limit or add an override merely to admit that file.**
|
||||||
|
Deliberate repository-wide policy revisions must update the enforced rules,
|
||||||
|
tests, and guidance together.
|
||||||
- Feature modules must not import from other feature modules — only from
|
- Feature modules must not import from other feature modules — only from
|
||||||
`shared/`.
|
`shared/`.
|
||||||
- Use `Grid` tokens for spacing, `Radii` for border radius.
|
- Use `Grid` tokens for spacing, `Radii` for border radius.
|
||||||
|
|||||||
@@ -1,61 +1,21 @@
|
|||||||
|
import { realpathSync } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { runFileSizeCheck } from "../../scripts/check-file-sizes-core.mjs";
|
import { runFileSizeCheck } from "../../scripts/check-file-sizes-core.mjs";
|
||||||
|
import { rules } from "./file-size-policy.mjs";
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const scriptPath = realpathSync(fileURLToPath(import.meta.url));
|
||||||
const projectRoot = path.resolve(__dirname, "..");
|
const projectRoot = path.resolve(path.dirname(scriptPath), "..");
|
||||||
|
|
||||||
const MAX_LINES = 1000;
|
export const policy = {
|
||||||
|
|
||||||
const rules = [
|
|
||||||
{ root: "src-tauri/src", extensions: new Set([".rs"]), maxLines: MAX_LINES },
|
|
||||||
// Workspace member crates. Without this the ratchet's only Rust root is
|
|
||||||
// `src-tauri/src`, and a crate under `src-tauri/crates/` is born outside the
|
|
||||||
// repo's one size discipline -- silently, since the check still exits 0.
|
|
||||||
{
|
|
||||||
root: "src-tauri/crates",
|
|
||||||
extensions: new Set([".rs"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "src/app",
|
|
||||||
extensions: new Set([".ts", ".tsx"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "src/features",
|
|
||||||
extensions: new Set([".ts", ".tsx"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "src/shared/api",
|
|
||||||
extensions: new Set([".ts", ".tsx"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "src/shared/context",
|
|
||||||
extensions: new Set([".ts", ".tsx"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "src/shared/lib",
|
|
||||||
extensions: new Set([".ts", ".tsx"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "src/shared/ui",
|
|
||||||
extensions: new Set([".ts", ".tsx"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "src/shared/styles",
|
|
||||||
extensions: new Set([".css"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await runFileSizeCheck({
|
|
||||||
projectRoot,
|
projectRoot,
|
||||||
rules,
|
rules,
|
||||||
label: "Desktop",
|
label: "Desktop",
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.argv[1] &&
|
||||||
|
realpathSync(path.resolve(process.argv[1])) === scriptPath
|
||||||
|
) {
|
||||||
|
await runFileSizeCheck(policy);
|
||||||
|
}
|
||||||
|
|||||||
53
desktop/scripts/file-size-policy.mjs
Normal file
53
desktop/scripts/file-size-policy.mjs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
const DESKTOP_FRONTEND_MAX_LINES = 1200;
|
||||||
|
const DESKTOP_RUST_MAX_LINES = 1500;
|
||||||
|
|
||||||
|
export const rules = [
|
||||||
|
{
|
||||||
|
root: "src-tauri/src",
|
||||||
|
extensions: new Set([".rs"]),
|
||||||
|
maxLines: DESKTOP_RUST_MAX_LINES,
|
||||||
|
},
|
||||||
|
// Workspace member crates. Without this the ratchet's only Rust root is
|
||||||
|
// `src-tauri/src`, and a crate under `src-tauri/crates/` is born outside the
|
||||||
|
// repo's one size discipline -- silently, since the check still exits 0.
|
||||||
|
{
|
||||||
|
root: "src-tauri/crates",
|
||||||
|
extensions: new Set([".rs"]),
|
||||||
|
maxLines: DESKTOP_RUST_MAX_LINES,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "src/app",
|
||||||
|
extensions: new Set([".ts", ".tsx"]),
|
||||||
|
maxLines: DESKTOP_FRONTEND_MAX_LINES,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "src/features",
|
||||||
|
extensions: new Set([".ts", ".tsx"]),
|
||||||
|
maxLines: DESKTOP_FRONTEND_MAX_LINES,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "src/shared/api",
|
||||||
|
extensions: new Set([".ts", ".tsx"]),
|
||||||
|
maxLines: DESKTOP_FRONTEND_MAX_LINES,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "src/shared/context",
|
||||||
|
extensions: new Set([".ts", ".tsx"]),
|
||||||
|
maxLines: DESKTOP_FRONTEND_MAX_LINES,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "src/shared/lib",
|
||||||
|
extensions: new Set([".ts", ".tsx"]),
|
||||||
|
maxLines: DESKTOP_FRONTEND_MAX_LINES,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "src/shared/ui",
|
||||||
|
extensions: new Set([".ts", ".tsx"]),
|
||||||
|
maxLines: DESKTOP_FRONTEND_MAX_LINES,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "src/shared/styles",
|
||||||
|
extensions: new Set([".css"]),
|
||||||
|
maxLines: DESKTOP_FRONTEND_MAX_LINES,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
//! via [`AgentMetricIndexRow::from_payload`].
|
//! via [`AgentMetricIndexRow::from_payload`].
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file (not `store.rs`) to keep that file under the
|
//! Kept in a sibling file (not `store.rs`) to keep that file under the
|
||||||
//! 1000-line gate, per the existing `pipeline.rs` precedent.
|
//! 1500-line gate, per the existing `pipeline.rs` precedent.
|
||||||
|
|
||||||
use rusqlite::{params, Connection, OptionalExtension};
|
use rusqlite::{params, Connection, OptionalExtension};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Kind-44200 (NIP-AM agent turn metric) archive and `get_agent_usage_series`
|
//! Kind-44200 (NIP-AM agent turn metric) archive and `get_agent_usage_series`
|
||||||
//! integration tests for `archive/mod.rs`.
|
//! integration tests for `archive/mod.rs`.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file so `mod_tests.rs` stays under the 1000-line gate;
|
//! Kept in a sibling file so `mod_tests.rs` stays under the 1500-line gate;
|
||||||
//! `#[path]`-included from there so the shared fixtures (`in_memory`,
|
//! `#[path]`-included from there so the shared fixtures (`in_memory`,
|
||||||
//! `add_sub`, `candidate`, `make_observer_frame`, `run_batch_sync_with_keys`)
|
//! `add_sub`, `candidate`, `make_observer_frame`, `run_batch_sync_with_keys`)
|
||||||
//! stay private to `mod_tests`.
|
//! stay private to `mod_tests`.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Unit and integration tests for `archive/mod.rs`.
|
//! Unit and integration tests for `archive/mod.rs`.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file so `mod.rs` stays under the 1000-line gate;
|
//! Kept in a sibling file so `mod.rs` stays under the 1500-line gate;
|
||||||
//! `#[path]`-included from there.
|
//! `#[path]`-included from there.
|
||||||
|
|
||||||
use super::pipeline::BucketWithResult;
|
use super::pipeline::BucketWithResult;
|
||||||
@@ -622,7 +622,7 @@ fn test_commit_archive_rolls_back_when_scope_write_would_fail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Kind-44200 agent-turn-metric coverage lives in a sibling file to keep this
|
// Kind-44200 agent-turn-metric coverage lives in a sibling file to keep this
|
||||||
// one under the 1000-line gate; nested here (not in `mod.rs`) so it inherits
|
// one under the 1500-line gate; nested here (not in `mod.rs`) so it inherits
|
||||||
// the shared fixtures above through `use super::*`.
|
// the shared fixtures above through `use super::*`.
|
||||||
#[path = "mod_agent_metric_tests.rs"]
|
#[path = "mod_agent_metric_tests.rs"]
|
||||||
mod agent_metric;
|
mod agent_metric;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Archive pipeline — three-phase plan/query/commit split.
|
//! Archive pipeline — three-phase plan/query/commit split.
|
||||||
//!
|
//!
|
||||||
//! Separated from `mod.rs` to keep that file under the 1000-line gate.
|
//! Separated from `mod.rs` to keep that file under the 1500-line gate.
|
||||||
//!
|
//!
|
||||||
//! # Send-safety
|
//! # Send-safety
|
||||||
//!
|
//!
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
//! Phase-2 prune scan, the get/set accessors for the observer window, and the
|
//! Phase-2 prune scan, the get/set accessors for the observer window, and the
|
||||||
//! PRAGMA-based size readout. The prune worker itself lands in Phase 2.
|
//! PRAGMA-based size readout. The prune worker itself lands in Phase 2.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file (not `store.rs`) to respect the 1000-line gate, per
|
//! Kept in a sibling file (not `store.rs`) to respect the 1500-line gate, per
|
||||||
//! the existing `metric_store.rs` / `pipeline.rs` / `store_migrations.rs`
|
//! the existing `metric_store.rs` / `pipeline.rs` / `store_migrations.rs`
|
||||||
//! precedent.
|
//! precedent.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Behavior tests for the observer-retention setting, the size readout, and the
|
//! Behavior tests for the observer-retention setting, the size readout, and the
|
||||||
//! M4 migration.
|
//! M4 migration.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file so `retention.rs` stays under the 1000-line gate;
|
//! Kept in a sibling file so `retention.rs` stays under the 1500-line gate;
|
||||||
//! `#[path]`-included from there. `super::*` brings the retention API (and its
|
//! `#[path]`-included from there. `super::*` brings the retention API (and its
|
||||||
//! `rusqlite::{params, Connection}` imports) into scope; `super::super::store`
|
//! `rusqlite::{params, Connection}` imports) into scope; `super::super::store`
|
||||||
//! reaches the neighbouring subscription mutators and the base `SCHEMA`.
|
//! reaches the neighbouring subscription mutators and the base `SCHEMA`.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Migration tests for `archive/store.rs` — M1: harness column.
|
//! Migration tests for `archive/store.rs` — M1: harness column.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file so `store_tests.rs` stays under the 1000-line gate;
|
//! Kept in a sibling file so `store_tests.rs` stays under the 1500-line gate;
|
||||||
//! `#[path]`-included from `store.rs`.
|
//! `#[path]`-included from `store.rs`.
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
//! `archive_migrations`, so a migration that already ran is a no-op.
|
//! `archive_migrations`, so a migration that already ran is a no-op.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file (not `store.rs`) to keep that file under the
|
//! Kept in a sibling file (not `store.rs`) to keep that file under the
|
||||||
//! 1000-line gate, per the existing `metric_store.rs` / `pipeline.rs`
|
//! 1500-line gate, per the existing `metric_store.rs` / `pipeline.rs`
|
||||||
//! precedent.
|
//! precedent.
|
||||||
|
|
||||||
use rusqlite::{params, Connection};
|
use rusqlite::{params, Connection};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Unit tests for `archive/store.rs`.
|
//! Unit tests for `archive/store.rs`.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file so `store.rs` stays under the 1000-line gate;
|
//! Kept in a sibling file so `store.rs` stays under the 1500-line gate;
|
||||||
//! `#[path]`-included from there.
|
//! `#[path]`-included from there.
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//! Unit tests for `commands/agent_config.rs` (split to keep `agent_config.rs`
|
//! Unit tests for `commands/agent_config.rs` (split to keep `agent_config.rs`
|
||||||
//! under the 1000-line file-size ratchet).
|
//! under the 1500-line file-size ratchet).
|
||||||
//!
|
//!
|
||||||
//! Included via `#[path = "agent_config_tests.rs"] mod tests;` at the bottom of
|
//! Included via `#[path = "agent_config_tests.rs"] mod tests;` at the bottom of
|
||||||
//! `agent_config.rs`, so `use super::*` gives access to all items in that module.
|
//! `agent_config.rs`, so `use super::*` gives access to all items in that module.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//! Unit tests for `card.rs` — split into a child module file so the parent
|
//! Unit tests for `card.rs` — split into a child module file so the parent
|
||||||
//! stays under the 1000-line gate (same layout as `snapshot/tests.rs`).
|
//! stays under the 1500-line gate (same layout as `snapshot/tests.rs`).
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
//! and their supporting helpers.
|
//! and their supporting helpers.
|
||||||
//!
|
//!
|
||||||
//! Import-side commands and helpers live in `snapshot::import` to keep this
|
//! Import-side commands and helpers live in `snapshot::import` to keep this
|
||||||
//! file under the 1000-line gate.
|
//! file under the 1500-line gate.
|
||||||
//!
|
//!
|
||||||
//! Split from `personas/mod.rs` to keep that file under the line-count gate.
|
//! Split from `personas/mod.rs` to keep that file under the line-count gate.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Import-side helpers for `buzz-agent-snapshot v1`.
|
//! Import-side helpers for `buzz-agent-snapshot v1`.
|
||||||
//!
|
//!
|
||||||
//! Extracted from `snapshot.rs` to keep that file under the 1000-line gate.
|
//! Extracted from `snapshot.rs` to keep that file under the 1500-line gate.
|
||||||
//! The Tauri commands here (`preview_agent_snapshot_import`,
|
//! The Tauri commands here (`preview_agent_snapshot_import`,
|
||||||
//! `confirm_agent_snapshot_import`) are re-exported from `snapshot.rs` and
|
//! `confirm_agent_snapshot_import`) are re-exported from `snapshot.rs` and
|
||||||
//! registered in `lib.rs` through the same `personas::` path as the export
|
//! registered in `lib.rs` through the same `personas::` path as the export
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Export-size guard tests for `validate_snapshot_encode_size`.
|
//! Export-size guard tests for `validate_snapshot_encode_size`.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file so `snapshot/tests.rs` stays under the
|
//! Kept in a sibling file so `snapshot/tests.rs` stays under the
|
||||||
//! 1000-line gate; `#[path]`-included from there as a child module,
|
//! 1500-line gate; `#[path]`-included from there as a child module,
|
||||||
//! so `super::*` still resolves to the shared test imports.
|
//! so `super::*` still resolves to the shared test imports.
|
||||||
//!
|
//!
|
||||||
//! Tests call `validate_snapshot_encode_size` directly so they prove the
|
//! Tests call `validate_snapshot_encode_size` directly so they prove the
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Locked-card import tests for `decode_snapshot_for_import`.
|
//! Locked-card import tests for `decode_snapshot_for_import`.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file so `snapshot/tests.rs` stays under the
|
//! Kept in a sibling file so `snapshot/tests.rs` stays under the
|
||||||
//! 1000-line gate; `#[path]`-included from there as a child module,
|
//! 1500-line gate; `#[path]`-included from there as a child module,
|
||||||
//! so `super::*` still resolves to the shared test helpers.
|
//! so `super::*` still resolves to the shared test helpers.
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Tests for `memory_entries_from_listing` — the shared level → entries
|
//! Tests for `memory_entries_from_listing` — the shared level → entries
|
||||||
//! selection used by both snapshot export and card minting. Split from
|
//! selection used by both snapshot export and card minting. Split from
|
||||||
//! `tests.rs` to keep that file under the 1000-line gate; `#[path]`-included
|
//! `tests.rs` to keep that file under the 1500-line gate; `#[path]`-included
|
||||||
//! from there as a child module, so `super::*` resolves to `tests`'s parent
|
//! from there as a child module, so `super::*` resolves to `tests`'s parent
|
||||||
//! scope re-exports.
|
//! scope re-exports.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Unit tests for `managed_agents/agent_snapshot.rs`.
|
//! Unit tests for `managed_agents/agent_snapshot.rs`.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file so `agent_snapshot.rs` stays under the
|
//! Kept in a sibling file so `agent_snapshot.rs` stays under the
|
||||||
//! 1000-line gate; `#[path]`-included from there.
|
//! 1500-line gate; `#[path]`-included from there.
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::managed_agents::types::{BackendKind, ManagedAgentRecord, RespondTo};
|
use crate::managed_agents::types::{BackendKind, ManagedAgentRecord, RespondTo};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//! Unit tests for `config_bridge/reader.rs` (kept in a sibling file so
|
//! Unit tests for `config_bridge/reader.rs` (kept in a sibling file so
|
||||||
//! `reader.rs` stays under the 1000-line budget; `#[path]`-included from
|
//! `reader.rs` stays under the 1500-line budget; `#[path]`-included from
|
||||||
//! there).
|
//! there).
|
||||||
|
|
||||||
use std::{collections::BTreeMap, path::Path, sync::Mutex};
|
use std::{collections::BTreeMap, path::Path, sync::Mutex};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//! Additional tests for `config_bridge/reader.rs` — split out to keep
|
//! Additional tests for `config_bridge/reader.rs` — split out to keep
|
||||||
//! `reader_tests.rs` under the 1000-line file-size ratchet.
|
//! `reader_tests.rs` under the 1500-line file-size ratchet.
|
||||||
//!
|
//!
|
||||||
//! Included as `mod ext` inside `reader_tests.rs`, so `use super::*` gives
|
//! Included as `mod ext` inside `reader_tests.rs`, so `use super::*` gives
|
||||||
//! access to all helpers and types from that module.
|
//! access to all helpers and types from that module.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//! B5 effort lifecycle tests split out of `spawn_snapshot/tests.rs` to hold
|
//! B5 effort lifecycle tests split out of `spawn_snapshot/tests.rs` to hold
|
||||||
//! that file under the 1000-line file-size ratchet.
|
//! that file under the 1500-line file-size ratchet.
|
||||||
//!
|
//!
|
||||||
//! Included as `mod ext` inside `tests.rs`, so `use super::*` gives access to
|
//! Included as `mod ext` inside `tests.rs`, so `use super::*` gives access to
|
||||||
//! its `record`, `snap`, and `record_with_env_effort` helpers.
|
//! its `record`, `snap`, and `record_with_env_effort` helpers.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Unit tests for `managed_agents/storage.rs`.
|
//! Unit tests for `managed_agents/storage.rs`.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file so `storage.rs` stays closer to the 1000-line gate;
|
//! Kept in a sibling file so `storage.rs` stays closer to the 1500-line gate;
|
||||||
//! `#[path]`-included from there.
|
//! `#[path]`-included from there.
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Unit tests for `managed_agents/teams.rs`.
|
//! Unit tests for `managed_agents/teams.rs`.
|
||||||
//!
|
//!
|
||||||
//! Kept in a sibling file so `teams.rs` stays under the 1000-line gate;
|
//! Kept in a sibling file so `teams.rs` stays under the 1500-line gate;
|
||||||
//! `#[path]`-included from there.
|
//! `#[path]`-included from there.
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ with a TypeScript lookup table or an id comparison in a component.
|
|||||||
place that resolves it for dialog surfaces and publishes it through
|
place that resolves it for dialog surfaces and publishes it through
|
||||||
`ui/AgentRunLocationContext.tsx`; the field reads that context and lets an
|
`ui/AgentRunLocationContext.tsx`; the field reads that context and lets an
|
||||||
explicit `runLocation` prop win. Do **not** thread the value as a prop
|
explicit `runLocation` prop win. Do **not** thread the value as a prop
|
||||||
through `AgentDefinitionDialog` / `AgentInstanceEditDialog` — both are
|
through `AgentDefinitionDialog` / `AgentInstanceEditDialog` — neither uses
|
||||||
already over the 1000-line ceiling, and neither uses the value itself.
|
the value itself, and the shared context keeps the dialog boundary stable.
|
||||||
Surfaces rendered outside `AgentDialog` (e.g. `EditRespondToDialog`) pass the
|
Surfaces rendered outside `AgentDialog` (e.g. `EditRespondToDialog`) pass the
|
||||||
prop directly. Local names "your
|
prop directly. Local names "your
|
||||||
computer, including files, accounts, and connected tools"; remote names "the
|
computer, including files, accounts, and connected tools"; remote names "the
|
||||||
@@ -216,9 +216,9 @@ with a TypeScript lookup table or an id comparison in a component.
|
|||||||
cosmetic — the Rust command rejects non-local backends because remote effort
|
cosmetic — the Rust command rejects non-local backends because remote effort
|
||||||
is set at deploy time via `policy_env`. Because it reads its inputs from the
|
is set at deploy time via `policy_env`. Because it reads its inputs from the
|
||||||
config surface the dialog already fetches (`useAgentConfigSurface`) and owns
|
config surface the dialog already fetches (`useAgentConfigSurface`) and owns
|
||||||
its own mutation, it does **not** thread new props through the over-1000-line
|
its own mutation, it does **not** thread new props through the dialog (see
|
||||||
dialog (see rule 11): keep effort state inside the section component, never
|
rule 11): keep effort state inside the section component, never as
|
||||||
as dialog-level props. The read-only display is the `thinkingEffort`
|
dialog-level props. The read-only display is the `thinkingEffort`
|
||||||
normalized field rendered by `AgentConfigPanel` via `NormalizedRow`, which
|
normalized field rendered by `AgentConfigPanel` via `NormalizedRow`, which
|
||||||
already shows both facts — `field.value` (canonical, the effort the next
|
already shows both facts — `field.value` (canonical, the effort the next
|
||||||
spawn will launch with) and, when a running ACP session differs,
|
spawn will launch with) and, when a running ACP session differs,
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ class _ChannelRefreshFence {
|
|||||||
/// nothing awaits it, so a throw would only surface as an unhandled error.
|
/// nothing awaits it, so a throw would only surface as an unhandled error.
|
||||||
///
|
///
|
||||||
/// An extension in this part file rather than a method on the notifier because
|
/// An extension in this part file rather than a method on the notifier because
|
||||||
/// `channels_provider.dart` sits against the repository-wide 1000-line file
|
/// `channels_provider.dart` sits against the repository-wide 1200-line file
|
||||||
/// ceiling enforced by `just file-size-check`.
|
/// ceiling enforced by `just file-size-check`.
|
||||||
extension _CatchUpFencing on ChannelsNotifier {
|
extension _CatchUpFencing on ChannelsNotifier {
|
||||||
bool _isCatchUpRetired(
|
bool _isCatchUpRetired(
|
||||||
@@ -188,7 +188,7 @@ Future<T> _fenced<T>(_ChannelRefreshFence fence, Future<T> future) async {
|
|||||||
/// profiles in one round-trip. Returns lowercase pubkey to label.
|
/// profiles in one round-trip. Returns lowercase pubkey to label.
|
||||||
///
|
///
|
||||||
/// Lives in this part file because `channels_provider.dart` sits against the
|
/// Lives in this part file because `channels_provider.dart` sits against the
|
||||||
/// repository-wide 1000-line file ceiling enforced by `just file-size-check`.
|
/// repository-wide 1200-line file ceiling enforced by `just file-size-check`.
|
||||||
Future<Map<String, String>> _resolveDmDisplayNames(
|
Future<Map<String, String>> _resolveDmDisplayNames(
|
||||||
RelaySessionNotifier session,
|
RelaySessionNotifier session,
|
||||||
_ChannelRefreshFence fence,
|
_ChannelRefreshFence fence,
|
||||||
@@ -271,7 +271,7 @@ Future<List<NostrEvent>> _fetchHuddleStarts(
|
|||||||
/// Counts distinct `p`-tagged members per channel from kind:39002 events.
|
/// Counts distinct `p`-tagged members per channel from kind:39002 events.
|
||||||
///
|
///
|
||||||
/// Lives in this part file to keep `channels_provider.dart` under the
|
/// Lives in this part file to keep `channels_provider.dart` under the
|
||||||
/// repository-wide 1000-line ceiling enforced by `just file-size-check`.
|
/// repository-wide 1200-line ceiling enforced by `just file-size-check`.
|
||||||
Map<String, int> _memberCountsByChannelId(Iterable<NostrEvent> memberEvents) {
|
Map<String, int> _memberCountsByChannelId(Iterable<NostrEvent> memberEvents) {
|
||||||
final memberCounts = <String, int>{};
|
final memberCounts = <String, int>{};
|
||||||
for (final event in memberEvents) {
|
for (final event in memberEvents) {
|
||||||
@@ -297,7 +297,7 @@ Map<String, int> _memberCountsByChannelId(Iterable<NostrEvent> memberEvents) {
|
|||||||
/// filter, so a retired response is discarded rather than merged.
|
/// filter, so a retired response is discarded rather than merged.
|
||||||
///
|
///
|
||||||
/// Lives in this part file because `channels_provider.dart` sits against the
|
/// Lives in this part file because `channels_provider.dart` sits against the
|
||||||
/// repository-wide 1000-line file ceiling enforced by `just file-size-check`.
|
/// repository-wide 1200-line file ceiling enforced by `just file-size-check`.
|
||||||
class _ChannelRefreshCoordinator {
|
class _ChannelRefreshCoordinator {
|
||||||
/// Resolves the relay-and-identity scope that is active right now.
|
/// Resolves the relay-and-identity scope that is active right now.
|
||||||
final String Function() currentScope;
|
final String Function() currentScope;
|
||||||
@@ -429,7 +429,7 @@ Future<List<NostrEvent>> _fetchPaginatedChannelEvents(
|
|||||||
/// Thread-interest and unread helpers shared by [ChannelsNotifier].
|
/// Thread-interest and unread helpers shared by [ChannelsNotifier].
|
||||||
///
|
///
|
||||||
/// Lives in this part file because `channels_provider.dart` sits against the
|
/// Lives in this part file because `channels_provider.dart` sits against the
|
||||||
/// repository-wide 1000-line file ceiling enforced by `just file-size-check`.
|
/// repository-wide 1200-line file ceiling enforced by `just file-size-check`.
|
||||||
String? _observedUnreadRootId(NostrEvent event) =>
|
String? _observedUnreadRootId(NostrEvent event) =>
|
||||||
_isBroadcastReply(event) ? null : event.threadReference.rootId;
|
_isBroadcastReply(event) ? null : event.threadReference.rootId;
|
||||||
|
|
||||||
@@ -456,7 +456,7 @@ String _encodeRootIdSet(Set<String> values) => jsonEncode(values.toList());
|
|||||||
/// Records one observed unread event for a channel's badge state.
|
/// Records one observed unread event for a channel's badge state.
|
||||||
///
|
///
|
||||||
/// An extension in this part file rather than a method on the notifier because
|
/// An extension in this part file rather than a method on the notifier because
|
||||||
/// `channels_provider.dart` sits against the repository-wide 1000-line file
|
/// `channels_provider.dart` sits against the repository-wide 1200-line file
|
||||||
/// ceiling enforced by `just file-size-check`. Private members stay reachable:
|
/// ceiling enforced by `just file-size-check`. Private members stay reachable:
|
||||||
/// a part shares its parent's library.
|
/// a part shares its parent's library.
|
||||||
extension _ObservedUnreadRecording on ChannelsNotifier {
|
extension _ObservedUnreadRecording on ChannelsNotifier {
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ class ChannelsNotifier extends AsyncNotifier<List<Channel>> {
|
|||||||
final dedupedMetas = latestMetaPerId.values.toList();
|
final dedupedMetas = latestMetaPerId.values.toList();
|
||||||
|
|
||||||
// Resolve DM participant display names. Extracted into the part file so
|
// Resolve DM participant display names. Extracted into the part file so
|
||||||
// `channels_provider.dart` stays under the 1000-line ceiling enforced by
|
// `channels_provider.dart` stays under the 1200-line ceiling enforced by
|
||||||
// `just file-size-check`.
|
// `just file-size-check`.
|
||||||
final displayNames = await _resolveDmDisplayNames(
|
final displayNames = await _resolveDmDisplayNames(
|
||||||
session,
|
session,
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
|
import { realpathSync } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { runFileSizeCheck } from "../../scripts/check-file-sizes-core.mjs";
|
import { runFileSizeCheck } from "../../scripts/check-file-sizes-core.mjs";
|
||||||
|
import { rules } from "./file-size-policy.mjs";
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const scriptPath = realpathSync(fileURLToPath(import.meta.url));
|
||||||
const projectRoot = path.resolve(__dirname, "..");
|
const projectRoot = path.resolve(path.dirname(scriptPath), "..");
|
||||||
|
|
||||||
const MAX_LINES = 1000;
|
export const policy = {
|
||||||
|
|
||||||
const rules = [
|
|
||||||
{
|
|
||||||
root: "lib",
|
|
||||||
extensions: new Set([".dart"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await runFileSizeCheck({
|
|
||||||
projectRoot,
|
projectRoot,
|
||||||
rules,
|
rules,
|
||||||
label: "Mobile",
|
label: "Mobile",
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.argv[1] &&
|
||||||
|
realpathSync(path.resolve(process.argv[1])) === scriptPath
|
||||||
|
) {
|
||||||
|
await runFileSizeCheck(policy);
|
||||||
|
}
|
||||||
|
|||||||
7
mobile/scripts/file-size-policy.mjs
Normal file
7
mobile/scripts/file-size-policy.mjs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const rules = [
|
||||||
|
{
|
||||||
|
root: "lib",
|
||||||
|
extensions: new Set([".dart"]),
|
||||||
|
maxLines: 1200,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,9 +1,19 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { execFileSync } from "node:child_process";
|
import { execFileSync, spawnSync } from "node:child_process";
|
||||||
import { mkdtempSync } from "node:fs";
|
import {
|
||||||
|
copyFileSync,
|
||||||
|
mkdirSync,
|
||||||
|
mkdtempSync,
|
||||||
|
realpathSync,
|
||||||
|
symlinkSync,
|
||||||
|
writeFileSync,
|
||||||
|
} from "node:fs";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
|
import { policy as desktopPolicy } from "../desktop/scripts/check-file-sizes.mjs";
|
||||||
|
import { policy as mobilePolicy } from "../mobile/scripts/check-file-sizes.mjs";
|
||||||
|
import { policy as webPolicy } from "../web/scripts/check-file-sizes.mjs";
|
||||||
import {
|
import {
|
||||||
allowedLineCount,
|
allowedLineCount,
|
||||||
countLines,
|
countLines,
|
||||||
@@ -12,6 +22,8 @@ import {
|
|||||||
resolveBaseRef,
|
resolveBaseRef,
|
||||||
} from "./check-file-sizes-core.mjs";
|
} from "./check-file-sizes-core.mjs";
|
||||||
|
|
||||||
|
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||||
|
|
||||||
function git(repo, ...args) {
|
function git(repo, ...args) {
|
||||||
// These fixture repositories inherit both hook configuration and Git's
|
// These fixture repositories inherit both hook configuration and Git's
|
||||||
// repository-local environment when this test runs from pre-push. Isolate
|
// repository-local environment when this test runs from pre-push. Isolate
|
||||||
@@ -26,6 +38,62 @@ function git(repo, ...args) {
|
|||||||
}).trim();
|
}).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createEntrypointFixture({
|
||||||
|
surface,
|
||||||
|
files,
|
||||||
|
lineDelta = 1,
|
||||||
|
symlinkEntrypoint = false,
|
||||||
|
}) {
|
||||||
|
const repo = realpathSync(
|
||||||
|
mkdtempSync(path.join(tmpdir(), `file-size-${surface}-`)),
|
||||||
|
);
|
||||||
|
const scriptsDir = path.join(repo, "scripts");
|
||||||
|
const surfaceScriptsDir = path.join(repo, surface, "scripts");
|
||||||
|
mkdirSync(scriptsDir, { recursive: true });
|
||||||
|
mkdirSync(surfaceScriptsDir, { recursive: true });
|
||||||
|
copyFileSync(
|
||||||
|
path.join(repoRoot, "scripts/check-file-sizes-core.mjs"),
|
||||||
|
path.join(scriptsDir, "check-file-sizes-core.mjs"),
|
||||||
|
);
|
||||||
|
for (const fileName of ["check-file-sizes.mjs", "file-size-policy.mjs"]) {
|
||||||
|
copyFileSync(
|
||||||
|
path.join(repoRoot, surface, "scripts", fileName),
|
||||||
|
path.join(surfaceScriptsDir, fileName),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
git(repo, "init", "-b", "main");
|
||||||
|
git(repo, "config", "user.name", "Test");
|
||||||
|
git(repo, "config", "user.email", "test@example.com");
|
||||||
|
git(repo, "add", ".");
|
||||||
|
git(repo, "commit", "-m", "base");
|
||||||
|
const base = git(repo, "rev-parse", "HEAD");
|
||||||
|
git(repo, "switch", "-c", "feature");
|
||||||
|
|
||||||
|
for (const { relativeFile, maxLines } of files) {
|
||||||
|
const governedFile = path.join(repo, surface, relativeFile);
|
||||||
|
const lineCount = maxLines + lineDelta;
|
||||||
|
mkdirSync(path.dirname(governedFile), { recursive: true });
|
||||||
|
writeFileSync(governedFile, `${"line\n".repeat(lineCount - 1)}line`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const realEntrypointPath = path.join(
|
||||||
|
surfaceScriptsDir,
|
||||||
|
"check-file-sizes.mjs",
|
||||||
|
);
|
||||||
|
let entrypointPath = realEntrypointPath;
|
||||||
|
if (symlinkEntrypoint) {
|
||||||
|
entrypointPath = path.join(repo, `${surface}-file-size-check.mjs`);
|
||||||
|
symlinkSync(realEntrypointPath, entrypointPath);
|
||||||
|
}
|
||||||
|
const result = spawnSync(realpathSync(process.execPath), [entrypointPath], {
|
||||||
|
cwd: repo,
|
||||||
|
encoding: "utf8",
|
||||||
|
env: { ...process.env, CHECK_FILE_SIZES_BASE: base },
|
||||||
|
});
|
||||||
|
return { result, relativeFiles: files.map(({ relativeFile }) => relativeFile) };
|
||||||
|
}
|
||||||
|
|
||||||
test("local base resolution uses the branch merge-base and fails without origin/main", () => {
|
test("local base resolution uses the branch merge-base and fails without origin/main", () => {
|
||||||
const repo = mkdtempSync(path.join(tmpdir(), "file-size-base-"));
|
const repo = mkdtempSync(path.join(tmpdir(), "file-size-base-"));
|
||||||
git(repo, "init", "-b", "main");
|
git(repo, "init", "-b", "main");
|
||||||
@@ -47,12 +115,151 @@ test("local base resolution uses the branch merge-base and fails without origin/
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const entrypointCases = [
|
||||||
|
{
|
||||||
|
surface: "desktop",
|
||||||
|
files: [
|
||||||
|
{ relativeFile: "src-tauri/src/oversized.rs", maxLines: 1500 },
|
||||||
|
{ relativeFile: "src-tauri/crates/oversized.rs", maxLines: 1500 },
|
||||||
|
{ relativeFile: "src/app/oversized.ts", maxLines: 1200 },
|
||||||
|
{ relativeFile: "src/features/oversized.tsx", maxLines: 1200 },
|
||||||
|
{ relativeFile: "src/shared/api/oversized.ts", maxLines: 1200 },
|
||||||
|
{ relativeFile: "src/shared/context/oversized.tsx", maxLines: 1200 },
|
||||||
|
{ relativeFile: "src/shared/lib/oversized.ts", maxLines: 1200 },
|
||||||
|
{ relativeFile: "src/shared/ui/oversized.tsx", maxLines: 1200 },
|
||||||
|
{ relativeFile: "src/shared/styles/oversized.css", maxLines: 1200 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
surface: "mobile",
|
||||||
|
files: [{ relativeFile: "lib/oversized.dart", maxLines: 1200 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
surface: "web",
|
||||||
|
files: [
|
||||||
|
{ relativeFile: "src/app/oversized.ts", maxLines: 1000 },
|
||||||
|
{ relativeFile: "src/features/oversized.tsx", maxLines: 1000 },
|
||||||
|
{ relativeFile: "src/shared/api/oversized.ts", maxLines: 1000 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
test("surface entrypoints execute every production rule", () => {
|
||||||
|
for (const fixture of entrypointCases) {
|
||||||
|
const { result, relativeFiles } = createEntrypointFixture(fixture);
|
||||||
|
assert.equal(
|
||||||
|
result.status,
|
||||||
|
1,
|
||||||
|
`${fixture.surface} should reject every ceiling + 1: ${result.stderr || result.stdout}`,
|
||||||
|
);
|
||||||
|
for (const relativeFile of relativeFiles) {
|
||||||
|
assert.ok(
|
||||||
|
result.stderr.includes(relativeFile),
|
||||||
|
`${fixture.surface} should report ${relativeFile}: ${result.stderr}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("surface entrypoints execute through symlinked paths", () => {
|
||||||
|
for (const fixture of entrypointCases) {
|
||||||
|
const { result, relativeFiles } = createEntrypointFixture({
|
||||||
|
...fixture,
|
||||||
|
symlinkEntrypoint: true,
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
result.status,
|
||||||
|
1,
|
||||||
|
`${fixture.surface} symlink should reject ceiling + 1: ${result.stderr || result.stdout}`,
|
||||||
|
);
|
||||||
|
for (const relativeFile of relativeFiles) {
|
||||||
|
assert.ok(
|
||||||
|
result.stderr.includes(relativeFile),
|
||||||
|
`${fixture.surface} symlink should report ${relativeFile}: ${result.stderr}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("surface entrypoints allow every production rule at its ceiling", () => {
|
||||||
|
for (const fixture of entrypointCases) {
|
||||||
|
const { result } = createEntrypointFixture({ ...fixture, lineDelta: 0 });
|
||||||
|
assert.equal(
|
||||||
|
result.status,
|
||||||
|
0,
|
||||||
|
`${fixture.surface} should allow every ceiling: ${result.stderr || result.stdout}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("counts empty, LF, and CRLF content with the existing semantics", () => {
|
test("counts empty, LF, and CRLF content with the existing semantics", () => {
|
||||||
assert.equal(countLines(""), 0);
|
assert.equal(countLines(""), 0);
|
||||||
assert.equal(countLines("one\n"), 2);
|
assert.equal(countLines("one\n"), 2);
|
||||||
assert.equal(countLines("one\r\ntwo"), 2);
|
assert.equal(countLines("one\r\ntwo"), 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("surface entrypoints expose the exact ordered production policies", () => {
|
||||||
|
const policies = [
|
||||||
|
[
|
||||||
|
desktopPolicy,
|
||||||
|
[
|
||||||
|
["src-tauri/src", [".rs"], 1500],
|
||||||
|
["src-tauri/crates", [".rs"], 1500],
|
||||||
|
["src/app", [".ts", ".tsx"], 1200],
|
||||||
|
["src/features", [".ts", ".tsx"], 1200],
|
||||||
|
["src/shared/api", [".ts", ".tsx"], 1200],
|
||||||
|
["src/shared/context", [".ts", ".tsx"], 1200],
|
||||||
|
["src/shared/lib", [".ts", ".tsx"], 1200],
|
||||||
|
["src/shared/ui", [".ts", ".tsx"], 1200],
|
||||||
|
["src/shared/styles", [".css"], 1200],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[mobilePolicy, [["lib", [".dart"], 1200]]],
|
||||||
|
[
|
||||||
|
webPolicy,
|
||||||
|
[
|
||||||
|
["src/app", [".ts", ".tsx"], 1000],
|
||||||
|
["src/features", [".ts", ".tsx"], 1000],
|
||||||
|
["src/shared/api", [".ts", ".tsx"], 1000],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [policy, expectedRules] of policies) {
|
||||||
|
const actualRules = policy.rules.map((rule) => [
|
||||||
|
rule.root,
|
||||||
|
[...rule.extensions],
|
||||||
|
rule.maxLines,
|
||||||
|
]);
|
||||||
|
assert.deepEqual(
|
||||||
|
actualRules,
|
||||||
|
expectedRules,
|
||||||
|
`${policy.label} production rules`,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const rule of policy.rules) {
|
||||||
|
assert.equal(
|
||||||
|
evaluateFileSize({
|
||||||
|
baseLines: null,
|
||||||
|
candidateLines: rule.maxLines,
|
||||||
|
maxLines: rule.maxLines,
|
||||||
|
}).violates,
|
||||||
|
false,
|
||||||
|
`${policy.label} ${rule.root} should allow the ceiling`,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
evaluateFileSize({
|
||||||
|
baseLines: null,
|
||||||
|
candidateLines: rule.maxLines + 1,
|
||||||
|
maxLines: rule.maxLines,
|
||||||
|
}).violates,
|
||||||
|
true,
|
||||||
|
`${policy.label} ${rule.root} should reject ceiling + 1`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("new files use the configured ceiling", () => {
|
test("new files use the configured ceiling", () => {
|
||||||
assert.equal(allowedLineCount(null, 1000), 1000);
|
assert.equal(allowedLineCount(null, 1000), 1000);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
|
|||||||
@@ -1,32 +1,21 @@
|
|||||||
|
import { realpathSync } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { runFileSizeCheck } from "../../scripts/check-file-sizes-core.mjs";
|
import { runFileSizeCheck } from "../../scripts/check-file-sizes-core.mjs";
|
||||||
|
import { rules } from "./file-size-policy.mjs";
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const scriptPath = realpathSync(fileURLToPath(import.meta.url));
|
||||||
const projectRoot = path.resolve(__dirname, "..");
|
const projectRoot = path.resolve(path.dirname(scriptPath), "..");
|
||||||
|
|
||||||
const MAX_LINES = 1000;
|
export const policy = {
|
||||||
|
|
||||||
const rules = [
|
|
||||||
{
|
|
||||||
root: "src/app",
|
|
||||||
extensions: new Set([".ts", ".tsx"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "src/features",
|
|
||||||
extensions: new Set([".ts", ".tsx"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "src/shared/api",
|
|
||||||
extensions: new Set([".ts", ".tsx"]),
|
|
||||||
maxLines: MAX_LINES,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await runFileSizeCheck({
|
|
||||||
projectRoot,
|
projectRoot,
|
||||||
rules,
|
rules,
|
||||||
label: "Web",
|
label: "Web",
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.argv[1] &&
|
||||||
|
realpathSync(path.resolve(process.argv[1])) === scriptPath
|
||||||
|
) {
|
||||||
|
await runFileSizeCheck(policy);
|
||||||
|
}
|
||||||
|
|||||||
19
web/scripts/file-size-policy.mjs
Normal file
19
web/scripts/file-size-policy.mjs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const MAX_LINES = 1000;
|
||||||
|
|
||||||
|
export const rules = [
|
||||||
|
{
|
||||||
|
root: "src/app",
|
||||||
|
extensions: new Set([".ts", ".tsx"]),
|
||||||
|
maxLines: MAX_LINES,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "src/features",
|
||||||
|
extensions: new Set([".ts", ".tsx"]),
|
||||||
|
maxLines: MAX_LINES,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "src/shared/api",
|
||||||
|
extensions: new Set([".ts", ".tsx"]),
|
||||||
|
maxLines: MAX_LINES,
|
||||||
|
},
|
||||||
|
];
|
||||||
Reference in New Issue
Block a user