Skip to content

feat: add new npm script to compare the build output against master - #1212

Open
mu-hun wants to merge 29 commits into
local_optimization_configfrom
feature/#1211
Open

feat: add new npm script to compare the build output against master#1212
mu-hun wants to merge 29 commits into
local_optimization_configfrom
feature/#1211

Conversation

@mu-hun

@mu-hun mu-hun commented Jul 9, 2026

Copy link
Copy Markdown
Member

Turns the manual copy-paste workflow from DEVELOPMENT.md into a single interactive command. Please see details in the below issue.

This PR is related to #1211 issue.

Note

  • About extra commits starting with fix:, which were not supported in the previous script.

@mu-hun
mu-hun requested a review from Alex-302 July 9, 2026 07:16
@mu-hun
mu-hun marked this pull request as draft July 9, 2026 07:17
@mu-hun

mu-hun commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

The new script reported diffs on my end... I will check this issue later and fix it.

@Alex-302 I will keep this PR in draft status until the above problem is fixed. Please take a look for an initial review.

@mu-hun mu-hun left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No diffs were reported after running twice. so, I'm converting this PR as ready for review.

Step 8/9: Report
=== Regression Test Report ===
Branch (reference): master          @ 29ee5260e069fdb10a9873babfb32a213e06f57c
Branch (feature):   feature/#1211 @ 400ad9c11100f9c634aeeb6077bec194eb5d7dc6
Build command:      yarn generate-cache && yarn build:local --no-patches-prepare --strip-generated-meta
Platforms compared: android cli extension ios mac mac_v2 mac_v3 windows 

--- Rule Files ---
Total .txt files compared: 3361
Files with diffs:          0

--- Metadata Files (informational) ---
filters.json/filters.js diffs: 32  (version counter noise, not a regression)

--- Verdict ---
✓ PASS — no rule file diffs

Step 9/9: Cleanup
✓ cleaning up worktrees and build output   
✨  Done in 553.45s.

Comment on lines +411 to +415
# --- Step 6: sync changed worktree's filters/ to the $BASED_BRANCH baseline ---

step_header 6 "Sync filters/ baseline"
if ! run_with_spinner "syncing filters/ to $BASED_BRANCH baseline" "$LOG_SYNC_BASELINE" \
git -C "$CHANGED_WORK_TREE" checkout "$MASTER_SHA" -- filters/; then

@mu-hun mu-hun Jul 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to double-check if we actually need a step to sync the filters/ directory.

I'm wondering if the --use-cache flag is supposed to skip the filters directory build, and if so, how that skip actually works. The arguments being passed to the compile function look similar, so while reviewing, it occurred to me that it might be downloading filters from a remote source and building them there.

// Determine which filtersDir to pass to the compiler
const effectiveFiltersDir = useCache ? cachedFiltersDir : filtersDir;
if (useCache) {
await prepareCachedFiltersDir();
}
try {
await compile(
effectiveFiltersDir,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P.S. How about requesting a review from the dev team after our team review is done?

This instruction was originally written at 5b7f70f#diff-fc2a310fcfedfefb0046def697f932def80cbb1e78c689bc0af3c8eab3e33eceR116-R132 with dev team review.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean reviev of the SH script?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P.S. How about requesting a review from the dev team after our team review is done?

Right. This shell script.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if the --use-cache flag is supposed to skip the filters directory build, and if so, how that skip actually works. The arguments being passed to the compile function look similar, so while reviewing, it occurred to me that it might be downloading filters from a remote source and building them there.

I'm waiting for an answer to my question above. Is it possible to skip the filters directory build when the compile function is emitted with the passed cachedFiltersDir?

@slvvko I checked your most recent work in this project. Can you answer my question? cc @maximtop

https://github.com/AdguardTeam/FiltersRegistry/graphs/contributors?from=7%2F19%2F2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if the --use-cache flag is supposed to skip the filters directory build

Doesn't this skip the build? The option itself involves the use of local filter.txt files

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't skip the filters directory build — compile() runs the full pipeline either way, and the arguments look similar on purpose.

What --use-cache changes is the input: before compiling, we copy filters to temp/filters_cached/ and replace every template.txt with a shadow template containing only @include "./filter.txt". So when the compiler calls FiltersDownloader.resolveIncludes, it resolves a local file instead of downloading remote sources. The compilation itself (platform outputs, removeRulePatterns, patches) still runs, which is required so that --use-cache produces identical output to a normal build.

@mu-hun
mu-hun marked this pull request as ready for review July 10, 2026 03:05
@mu-hun
mu-hun requested a review from zloyden July 10, 2026 03:05

@Alex-302 Alex-302 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Add detection of old results when executing and a request to remove it
image
  • Path to logs better print complete (support Win/Mac). It is hard to find the location.
Image

Also I suggested to use temp dir of the repo - in any case it is gitignored.

  • Logs extension must be .log

  • Use cached build (generate-cache + build:local) instead of a plain build?

better Use cached sources instead of a regular build? without (generate-cache + build:local) - it is explained in docs.

  • Step 2/9: Build mode

Use cached build (generate-cache + build:local) instead of a plain build?
[Y/N] (default: Y):

I think the default shoulde No

  • Step 9/9: Cleanup
    → Kept for later use: temp/reg-master-build, temp/reg-changed-build, platforms_master_build, platforms_changed_build

=> Cleanup skipped. + paths.

Btw why platforms_changed_build and platforms_master_build are created in the root of the repo, but not in temp dir or the repo?

@Alex-302

Alex-302 commented Jul 10, 2026

Copy link
Copy Markdown
Member

Please also check AI review

Bugs

  • source "$META_FILE" breaks on branch names containing /CHANGED_BRANCH=feature/ABC-123 is executed as a command by source. Save values with quotes: CHANGED_BRANCH="$CHANGED_BRANCH".
  • printf "\033[%dA" emits raw escape codes in non-TTY — cursor-up sequence [~line 300] is gated by [ -t 1 ], but draw_branch_menu itself is not. In CI / piped output the whole interactive menu would produce garbage. Either abort early when not a TTY or guard the menu entirely.
  • No SIGINT/SIGTERM handler — Ctrl+C during worktree setup, install, or build leaves stale worktrees, platforms_*_build/ dirs, and $META_FILE behind. On re-run the script may silently reuse corrupted state. Add a trap that cleans up worktrees, output dirs, $META_FILE, and $LOCK_DIR.
  • No local check for $BASED_BRANCHgit rev-parse "$BASED_BRANCH" fails if the branch (master) isn't fetched locally. Should check or fall back to origin/master.

Robustness

  • Reused worktree skips yarn install even if deps changed — Step 5 only installs when node_modules/ is missing. If a worktree is reused (Step 4 user says "yes") but package.json / yarn.lock differ from the detached commit, the build will use stale dependencies. Compare lockfile hashes before skipping.
  • report_failure + exit 1 pattern repeated 5+ times — every step duplicates report_failure "... FAILED" "$log"; exit 1. Extract into a helper like run_or_fail(label, log, ...cmd).
  • No set -euo pipefail — some command failures may go undetected. Script currently relies on manual $? checks, which are thorough but not exhaustive.

Maintainability

  • Build flags hardcoded in two places--no-patches-prepare --strip-generated-meta appears in build_branch() and in generate_report(). Extract to a single variable.
  • TOTAL_STEPS=9 is never used outside step_header — could just hardcode 9 in the header calls, or use $TOTAL_STEPS consistently in all references including step_header's own format string.
  • draw_branch_menu is called unconditionally — even when $CURRENT_BRANCH is empty (detached HEAD) the full menu renders. Works but the "no default" message on line ~261 only warns, doesn't fall back to a sensible default like the first branch.
  • No shellcheck validation in CI — the script has # shellcheck disable=SC1090 for the source "$META_FILE" line but shellcheck is not part of yarn lint. Consider adding it or at least running shellcheck manually.
  • Interactive-only design — no --branch, --cached, --no-cleanup CLI flags. Makes it unusable in CI. Low priority if CI has its own pipeline.

@mu-hun

mu-hun commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

Since --generate-cache and --download-cache are doesn't require for --use-cache from our latest discussions, I will update add new steps:

  • ask to run --generate-cache
  • ask to run --download-stats

Comment thread scripts/compare-build-output-against-master.sh Outdated
@mu-hun

mu-hun commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

reply to #1212 (comment)

3 items are real to scope for this fixes. ec3fe51 (this PR), 728f3c6 (this PR), 4273084 (this PR)

6 of the 12 are false positives — already handled correctly:

  • source "$META_FILE" with an unquoted branch name containing / and # (e.g. feature/#1211) sources fine — verified with a standalone repro; # only starts a comment at the start of a word, not mid-token after =.
  • Non-TTY escape-code leakage — every printf with a raw escape sequence in draw_branch_menu/the arrow-key loop is already gated by [ -t 1 ] or uses the C_* vars (empty when not a TTY); verified with piped/</dev/null repro producing clean output.
  • No SIGINT/SIGTERM handler — the existing trap 'rm -rf "$LOCK_DIR"' EXIT already fires on SIGINT (verified: bash runs EXIT traps on signal termination). Worktrees/platform dirs intentionally persist across an interrupted run (same as the "cleanup skipped" resumable design) and are never silently reused as corrupt state, since $META_FILE — the sole gate for Step 0's reuse offer — is only written after both builds fully succeed.
  • TOTAL_STEPS claim is self-contradictory (already used in step_header's own format string).
  • Detached-HEAD branch-menu fallback already defaults SELECTED_INDEX=0 (first branch) when no current-branch match is found.
  • set -euo pipefail too risky against the script's background-job/expected-failure patterns for the benefit, given manual checks already cover every critical path — if-checks, including patterns that would break under pipefail/errexit.

Interactive-only design is intended for checking locally. Should I make compatibility to CI?

How to test it?

It could test at e968794 (this PR) but after merge commit testing is backlog.

  1. git reset --hard e96879475a2456818be049613f610c93cb3256ad
  2. yarn compare-build-output Please select the feature/#1211-test branch at first step.

[skip ci] I'm planning to run after next released FiltersCompiler version.

checkout unreleased FiltersCompiler's build and move it to each node_module (git worktrees)

Because. It's hard to manually swap between the 7 - 9 steps.

@mu-hun
mu-hun requested a review from Alex-302 July 23, 2026 04:31
@Alex-302

Copy link
Copy Markdown
Member

Interactive-only design is intended for checking locally. Should I make compatibility to CI?

No. For local testing only. At least for now, there's no need for that.

@mu-hun mu-hun left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regression test script is now runnable.

Step 9/11: Build both branches
✓ [master] build   ✓ [feature/#1211] build   
✗ [master] build FAILED — see /Users/muhun/github.com/AdguardTeam/FiltersRegistry/temp/logs/master-build.log
--- /Users/muhun/github.com/AdguardTeam/FiltersRegistry/temp/logs/master-build.log ---
(node:20997) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
(Use `node --trace-deprecation ...` to show where the warning was created)
error Command "download-stats" not found.

Please clear the yarn cache for the filters-compiler version to refresh the unreleased development version — yarn cache clean @adguard/filters-compiler. Without doing this, you will encounter the following error:

import { compile, localOptimizationStatistics, OptimizationStatsError } from '@adguard/filters-compiler';
                                               ^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: The requested module '@adguard/filters-compiler' does not provide an export named 'OptimizationStatsError'

Comment thread scripts/compare-build-output-against-master.sh Outdated
@Alex-302

Alex-302 commented Aug 11, 2026

Copy link
Copy Markdown
Member

@mu-hun it seems broken usage of include + skip yarn build --include=1,2,3 --skip=2

image

Even include=1,2,3 does not work

image

In master it works. Please check. According to docs it must be supported. Also ask llm to perform review against master (as I did. You can do this before publishing changes and, accordingly, fix any confirmed issues).


AI Review: PR #1186 — Local downloading of optimization statistics for the build

Scope: the PR branch (local_optimization_config, merged into feature/#1211) was
reviewed against @adguard/filters-compiler@3.2.12. Verified locally:
yarn test (71/71 pass) and yarn lint (code + types + markdown) are green.

The PR itself is solid overall. The findings below are ordered by impact. For each
one: what the problem is, what it can lead to, whether it is a regression, and
whether it exists in master (origin: introduced by PR #1186 vs pre-existing).

# Finding Severity Regression? Origin
1 --include + --skip become mutually exclusive in every mode, docs contradict code High Yes Introduced by PR #1186
2 filterId lost when wrapping OptimizationStatsError Medium No Introduced by PR #1186
3 Partial snapshot pitfall: scoped download leaves stale stats behind Medium No Introduced by PR #1186
4 "Offline" build is not actually offline (percent.json is always remote) Medium No Mixed: behavior pre-existing, claim new
5 --report silently ignored under --download-stats Low No Introduced by PR #1186
6 Test mock missing OptimizationStatsError; error path untested Low No Introduced by PR #1186
7 Dangerous git reset --hard in the documented cleanup steps Low No Introduced by PR #1186
8 Bash script placed inside the Vitest __tests__/ directory Low No Introduced by PR #1186

Origin verified against master (git): none of these findings exist there — with one
nuance, item 4. Master has no local optimization stats at all (everything —
percent.json and stats.json — is fetched from the remote server by the compiler),
so items 2, 3, 5, 6, 7 and 8 concern code, tests, and docs that are entirely new in
this PR. For item 4, the underlying behavior (percent.json always fetched remotely)
is inherited from the compiler and was already true in master; what the PR adds is
the "offline" claim that contradicts it.


1. --include + --skip become mutually exclusive in every mode (docs contradict code)

Essence. The new check in validateFlags() (scripts/build/build-config.ts) rejects
--include together with --skip for all commands — a plain yarn build,
--generate-cache, --use-cache, and --download-stats alike. Before this PR, passing
both was legal: the compiler applied them as intersection minus exclusion (a filter is
built only if it is in the include list AND not in the skip list). Meanwhile
DEVELOPMENT.md still documents that combination as valid:

yarn build --include=1,2,3 --skip=2 # intersection minus exclusion. Excessive, but it works

and the "Invalid combinations" section only mentions the restriction under
--download-stats. So the code and the docs contradict each other.

What it can lead to. A previously working, documented command now exits with an error.
Anyone with such a command in a script, CI step, or muscle memory gets a hard failure.
The docs mislead readers about what is valid.

Regression? Yes. This is a breaking CLI behavior change against previously
documented behavior, and the compiler still supports the combination. Minimal fix:
restrict the check to --download-stats only (the compiler throws there anyway, the
registry-side check just produces a nicer message), or keep it global but update both
doc sections (valid + invalid lists) in DEVELOPMENT.md.


2. UX error: filterId lost when wrapping OptimizationStatsError

Essence. When a local stats snapshot is incomplete, build.js catches
OptimizationStatsError and rethrows a generic message:

if (useCache && error instanceof OptimizationStatsError) {
    throw new Error('Run --download-stats to download the latest statistics.', { cause: error });
}

The compiler's error type was extended (in this same feature) to carry structured fields
filterId and sourcePath — precisely so the registry could tell the user which
filter is missing, instead of matching on error.message. But build.js discards both
fields and prints only the generic hint.

What it can lead to. With a partial snapshot (e.g. after a scoped
yarn download-stats --include=...), the build fails without saying which filter is
missing. The user must manually diff the downloaded files against the filter list to
figure it out — exactly the friction the structured error was meant to remove.

Regression? No. New code, not a regression — it is a lost opportunity: the
compiler-side groundwork exists but is unused. Fix is small: include error.filterId
(and error.sourcePath) in the rethrown message.


3. Partial snapshot pitfall: scoped download leaves stale stats behind

Essence. yarn download-stats --include=1,2,3 overwrites only the selected
stats.json files. Any older stats.json files for other filters stay on disk. old stats for some filters,
new stats for others. If a filter has no stats file at all, the build fails with the "Run --download-stats" error.

What it can lead to. Silent, mixed-version snapshots: the build is reproducible
against a snapshot that never existed as a whole. Optimized output for some filters is
computed from stale statistics, so local results can diverge from production for no
obvious reason. Debugging is confusing: "I just downloaded stats — why does filter 4
fail?"

Regression? No. New feature; the behavior is partially documented ("Existing
stats.json files are overwritten"), but the pitfall is not. Fix options: clear the
stats directory before downloads, or explicitly warn in the docs that a scoped
download leaves a partial cache that affects subsequent build:local runs.


4. "Offline" build is not actually offline (percent.json is always remote)

Essence. The PR description promises "building filters offline against a pinned stats
snapshot". In reality only per-filter stats.json files are read from disk.
percent.json — the file that defines which filters are optimizable and their targets —
is fetched from chrome.adtidy.org on every compile, even when
localOptimizationStatistics.use() was called. The registry's own test acknowledges
this: "compile() always fetches percent.json remotely, even under use()."

What it can lead to. On a machine without network access, yarn build:local fails at
the percent.json fetch — the advertised offline workflow does not work. Also, because
percent.json changes remotely, two builds of the same pinned stats snapshot can differ
(the set of optimizable filters is not pinned).

Regression? No. This behavior predates the PR: in master the compiler already
fetched percent.json (and everything else) from the remote server — the registry had
no local stats at all. So the root behavior is pre-existing; what the PR introduces is
only the "offline" claim that contradicts it. Note that not writing percent.json
locally is a deliberate design choice ("it can't be edited locally") — but then the
docs should stop implying full offline builds.


Minor issues

5. --report silently ignored under --download-stats

Essence. --download-stats --report=foo.txt passes validation, but no report file is
ever written (reports are only created by the compile step).

What it can lead to. Do rejects when --download-stats combined with --report

Regression? No. New flag combination; should be rejected and documented.

6. OptimizationStatsError thrown test case missing.

Essence. The catch branch (error instanceof OptimizationStatsError) is never
exercised by any test.

Regression? No. New tests; they pass today, but they are fragile and incomplete.
Fix: add a test for the OptimizationStatsError.

7. Dangerous git reset --hard in the documented cleanup steps This is ok for us, leave as is

Essence. DEVELOPMENT.md ends the "compare against master" workflow with:

git worktree remove /tmp/reg-master-build -f & git worktree remove /tmp/reg-changed-build -f
git reset --hard && rm -rf platforms_master_build platforms_changed_build

All branch-specific changes live in the /tmp worktrees; the main checkout is never
modified by the workflow. So git reset --hard in the main repo discards nothing related
to it — but it destroys any uncommitted work the developer has in the main working tree.

What it can lead to. Data loss for anyone who follows the doc while having
uncommitted changes in the main checkout.

Regression? No. New documentation, but the instruction is unsafe. rm -rf of the
two output directories is sufficient; git reset --hard should be removed.

8. Bash script placed inside the Vitest __tests__/ directory

Essence. scripts/build/__tests__/regression-test-against-master.sh is a Bash script
sitting inside a Vitest test directory. It is not a Vitest test, is not run by yarn test, and is easy to mistake for one.

What it can lead to. Confusion about what the directory contains; a future cleanup of
"test files" could delete it. (Already addressed in the follow-up PR #1212, which moves
it to scripts/compare-build-output-against-master.sh.)

Regression? No. Organizational nit.


Bottom line

None of the findings block merging: tests and lint pass, and the core feature works as
intended. Worth fixing before/right after merge:

  1. #1 (docs/code contradiction) — must be resolved before merge, it is the only
    true regression
    this PR introduces: a previously working, documented command
    (yarn build --include=… --skip=…) now errors out.
  2. #2, #3, #4 — small code/doc changes that remove real user-facing footguns.
    Of these, only #4 has a pre-existing root cause (the compiler always fetched
    percent.json remotely); #2 and #3 are entirely new code from this PR.
  3. #5#8 — nice-to-haves, mostly documentation and test hygiene; none of them exist
    in master (the code, tests, and docs they concern are all new in this PR).

Origin summary: compared against master, everything in this review except the
percent.json behavior in #4 is new in PR #1186. Master has no local optimization
stats at all — it always fetched percent.json and stats.json from the remote
server — so items 2, 3, 5, 6, 7 and 8 cannot exist in master, and item 4's underlying
behavior predates the PR.

@mu-hun

mu-hun commented Aug 11, 2026

Copy link
Copy Markdown
Member Author

it seems broken usage of include + skip yarn build --include=1,2,3 --skip=2

@Alex-302 Please update the node_modules. After updating, It will throw the Error: --include and --skip are mutually exclusive as described in first review section — "--include + --skip become mutually exclusive in every mode (docs contradict code)"

FiltersRegistry on feature/#1211 [$!] is 📦 v1.1.0 via v26.0.0 took 48s 
❯ yarn build --include=1,2,3 --skip=2
$ tsx scripts/build/build.js --include=1,2,3 --skip=2

Error: --include and --skip are mutually exclusive.
See Command Compatibility in DEVELOPMENT.md for valid combinations.

Even include=1,2,3 does not work

It works after updated.

FiltersRegistry on feature/#1211 [$!] is 📦 v1.1.0 via v26.0.0 took 2s 
❯ yarn build --include=1,2,3         
$ tsx scripts/build/build.js --include=1,2,3
(node:74365) [DEP0205] DeprecationWarning: `module.register()` is deprecated. Use `module.registerHooks()` instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
2026-08-11T18:34:59:836: Using custom platforms configuration
2026-08-11T18:34:59:840: Redefining platform WINDOWS

@Alex-302

Copy link
Copy Markdown
Member

It works after updated.

Weird, sure. But modules were frest

image

become mutually exclusive

Must work. In build plan skip can be used to temporary build some filters. With a separate setting, there is less risk of forgetting to re-enable the excluded filter. In general no resons to break this feature, at least for general usage.

image

@mu-hun

mu-hun commented Aug 11, 2026

Copy link
Copy Markdown
Member Author

Error: --include and --skip are mutually exclusive. This new validation was implemented following our discussion, where it was agreed to prevent the simultaneous use of both flags within the --download-stats command.

#1186 (comment): The generate-cache and use-cache flags are currently not being rejected when --include and --skip are used concurrently. Should we modify the logic to reject the input when both flags are provided simultaneously, as proposed in this change?

I have push new commit to the parent PR, describe this details at ea70a38

@mu-hun

mu-hun commented Aug 11, 2026

Copy link
Copy Markdown
Member Author
  1. UX error: filterId is omitted when wrapping an OptimizationStatsError.

The filterId is logged as Unable to retrieve optimization stats for ${filterId}, at ${sourcePath}... within the FiltersCompiler component.

Based on the current source code implementation:

export class OptimizationStatsError extends Error {
    code = 'OPTIMIZATION_STATS_UNAVAILABLE' as const;

    constructor(
        public filterId: number,
        public sourcePath: string,
        options?: ErrorOptions,
    ) {
        super(
            `Unable to retrieve optimization stats for ${filterId}, at ${sourcePath}. `
            + 'Please ensure the stats file exists and is accessible.',
            options,
        );
        this.name = 'OptimizationStatsError';
    }
}

@Alex-302

Alex-302 commented Aug 13, 2026

Copy link
Copy Markdown
Member

@mu-hun

I have push new commit to the parent PR, describe this details at ea70a38

You implemented regression. The fact that you've documented a change in behavior doesn't solve the problem. include and skip must must be compatible at least with prod commands.

In master they can be combined

image

@Alex-302

Copy link
Copy Markdown
Member

There might be a bug in the master
yarn build --generate-cache --include=1,2,3 --skip=2
downloaded only 1, but not 3

@mu-hun

mu-hun commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

Regarding #1186 (comment), We agreed to prevent the simultaneous use of both flags within --generate-cache and --use-cache. Should I revert this decision?

@Alex-302

Copy link
Copy Markdown
Member

@mu-hun

We agreed to prevent the simultaneous use of both flags within --generate-cache and --use-cache. Should I revert this discussion?

It does not look like a problem. --generate-cache + --use-cache is like a regular build without parameters.

@mu-hun

mu-hun commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

Okay, I'll allow the combination to --generate-cache, --download-stats, and --use-cache flags.

Note that --download-stats requires a change on the FilterCompiler side. Please wait.

@Alex-302

Alex-302 commented Aug 13, 2026

Copy link
Copy Markdown
Member

There might be a bug in the master
yarn build --generate-cache --include=1,2,3 --skip=2
downloaded only 1, but not 3

@mu-hun Please check #1223
AI generated, but looks ok.

It seems actual only for Windows with Powershell terminal. In bash it works. Assigned low priority.

mu-hun added 4 commits August 24, 2026 16:42
Turns the manual copy-paste workflow from DEVELOPMENT.md into a single interactive
command: prompts for the branch to compare, build mode, and cleanup preference, then
builds master and the branch in parallel git worktrees with a progress spinner, and
prints the same pass/fail rule-file comparison as before.

Resolves #1211
The report's diff loop only walked platforms_master_build/, so a .txt
file the compare branch adds that master doesn't have was invisible to
the comparison and could report PASS despite a real, uncompared change.
git worktree add can fail as "already registered" if temp/ was ever
deleted manually instead of via this script's own cleanup, leaving
stale bookkeeping under .git/worktrees/. Confirmed this repo already
has one such stale entry (reg-changed-local).
Two simultaneous invocations collide on the same fixed paths (temp/reg-master-build, temp/reg-meta.env, same log files) with no detection — last writer wins, silently.
mu-hun added 6 commits August 24, 2026 16:42
Was hardcoded identically in build_branch() and in generate_report()'s
displayed build_cmd string.
Applied to the 4 call sites that report and exit immediately
(worktree setup, both installs, filters/ sync). The other 5
report_failure sites don't exit immediately (they set BUILD_FAILED
and keep going, or just warn on cleanup), so they keep calling
report_failure directly.
[skip ci] I'm planning to run after next released FiltersCompiler version.

Per team discussion, --generate-cache and stats download aren't
required on every cached-mode run
Adds two new steps (3: Generate cache, 4: Download stats), both
defaulting to No/skip, shown only when cached build mode was chosen

in Step 2. build_branch() now conditionally runs `yarn generate-cache`
and/or `yarn download-stats` before build:local based on the
answers, instead of always running generate-cache. The choices are
also persisted to $META_FILE so a reused report (Step 0) shows the
build command that was actually run.

TOTAL_STEPS bumped 9 -> 11, renumbering steps 3-9 to 5-11.
TOTAL_STEPS back to 9, steps renumbered 4-9 accordingly.

@mu-hun mu-hun left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Alex-302 Hi, I've improved some step descriptions based on your suggestion.

Note: Please leave any change requests and suggestions about comparing scripts in this PR, not in the child PRs.

Comment thread scripts/compare-build-output-against-master.sh Outdated
Comment thread scripts/compare-build-output-against-master.sh Outdated
@mu-hun
mu-hun requested review from Alex-302 and removed request for Alex-302 and zloyden August 26, 2026 03:22
Comment thread scripts/compare-build-output-against-master.sh Outdated
@mu-hun
mu-hun requested a review from 105th August 26, 2026 11:57

@maximtop maximtop left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cleanup step is the one that needs fixing before merge — it deletes the whole shared temp/ directory, not just this run's artifacts. Rest is inline.

git worktree remove "$CHANGED_WORK_TREE" -f 2>/dev/null || rm -rf "$CHANGED_WORK_TREE"
# Remove everything under $TEMP_DIR_NAME except $LOG_DIR_NAME, so logs
# from this run stay available for inspection after cleanup.
find "$TEMP_DIR_NAME" -mindepth 1 -maxdepth 1 ! -name "$LOG_DIR_NAME" -exec rm -rf {} +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This deletes far more than the script creates: it also removes temp/optimization/stats/, temp/platforms/ (the patch-generation baseline) and temp/filters_cached/. The cleanup prompt only mentions worktrees and platforms_*_build, so answering "yes" silently destroys state that yarn download-stats, yarn build:patches and cached builds rely on. Please restrict it to the four paths this script actually created (the two worktrees plus the two platforms_*_build dirs).


if [ -f "$META_FILE" ] && [ -d "$PLATFORMS_MASTER" ] && [ -d "$PLATFORMS_CHANGED" ]; then
# shellcheck disable=SC1090
source "$META_FILE"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The meta file is written with unquoted values (CHANGED_BRANCH=$CHANGED_BRANCH below) and loaded here with source. Git refnames allow $() and backticks, so a branch named feature/$(...) would execute on the next run that reaches Step 0. Quoting the values on write (CHANGED_BRANCH="$CHANGED_BRANCH") or parsing the file line-by-line instead of sourcing closes it.

Comment thread DEVELOPMENT.md
- If `platforms_master_build/` and `platforms_changed_build/` already exist
from a previous run, it offers to generate the report from them instead of rebuilding.
- If a worktree from a previous run is still present, it offers to reuse it
(keeping `node_modules`, skipping reinstall) instead of recreating it from

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This claims worktree reuse skips the reinstall, but Step 5 runs yarn install unconditionally (since c794cb8) — the documented fast path doesn't exist. Either fix the doc or make the install conditional.

@mu-hun mu-hun Aug 28, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm considering to fixing the docs. But I'm unsure about the wordings.

  • If a worktree from a previous run is still present, it offers to reuse it
    instead of removing and re-adding it. yarn install still runs, but over
    the kept node_modules it only reconciles what changed.
  • If a worktree from a previous run is still present, it offers to reuse it
    instead of removing and re-adding it. yarn install still runs — the
    reused tree is checked out onto a different commit. but against the kept
    node_modules, not from scratch.
  • If a worktree from a previous run is still present, it offers to reuse it
    instead of removing and re-adding it. Worth it when the compared commits
    haven't changed much since the last run; yarn install still runs, just
    incrementally over the kept node_modules.

Which one do you think is best? 🤔

echo "Reuse it instead of recreating?"
if confirm default_no; then
echo "${C_CYAN}${ARROW}${C_RESET} [$label] reusing existing worktree at $path"
git -C "$path" checkout -f "$sha"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this checkout fails (locked files, broken worktree), the function still returns success and the build runs against whatever content happens to be in the worktree — a comparison against the wrong code with no error surfaced. Check the exit status before return 0.

# e.g. if temp/ was ever deleted manually instead of via `git worktree remove`.
# To verify: `rm -rf temp/reg-master-build`, then rerun — `git worktree add`
# would otherwise fail as "already registered".
git worktree prune

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running git worktree prune at startup touches the whole repository, not just this script's worktrees — it can remove admin files for unrelated worktrees the user still considers valid (e.g. one on an unmounted volume). Scope it to the two known paths or drop it.


BUILD_FAILED=false

if wait "$PID_MASTER_BUILD"; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The master/changed result-collection blocks here and just below are near-identical (~13 lines each), differing only in labels and variable names. A small helper taking label + pid + log path would collapse them.

Comment thread scripts/compare-build-output-against-master.sh Outdated
Comment thread DEVELOPMENT.md Outdated
Comment thread DEVELOPMENT.md Outdated
Comment thread AGENTS.md Outdated
@105th

105th commented Aug 26, 2026

Copy link
Copy Markdown
Member

Code Review — combined (3 reviewers)

Reviewed via three independent review passes (open-code-review/OCR, a detailed line-by-line review, and a generic review sub-agent), then deduplicated and re-verified against the head of this PR.

Scope: scripts/compare-build-output-against-master.sh (new, 546 lines — the focus), DEVELOPMENT.md, AGENTS.md, package.json, deleted scripts/build/__tests__/regression-test-against-master.sh. (Per discussion, SHA-pinning-vs-@master concerns are intentionally out of scope.)

The script is well-written, well-commented, and faithfully automates the manual workflow — parallelization, locking, pass/fail propagation, and the new reverse "ADDED" check are sound, and several worried-about behaviors (worktree re-add after rm -rf, worktrees under temp/, stale registrations) were verified fine empirically. The blockers below concern repeated-run data integrity, signal handling, and shell safety of the generated meta file.


High

H1 — source of the unquoted meta file can break or execute on the next run (security)
scripts/compare-build-output-against-master.sh:238 (source "$META_FILE"), written at :506-513

The heredoc writes fields unquoted, including CHANGED_BRANCH=$CHANGED_BRANCH. Git refs legally allow $, `, ;, ( ) — a branch such as feature/1$(cmd) is written verbatim and executed when the file is sourced on the next run's Step 0. Even a branch containing a space corrupts parsing (CHANGED_BRANCH='feature'). This is triggerable entirely within the tool's normal flow (Step 1 lets the user pick any local branch). Reproduced end-to-end.

Suggested fix: write values shell-safe, e.g. printf 'CHANGED_BRANCH=%q\n', or stop source-ing and parse key=value pairs with grep/cut.

H2 — Ctrl-C can't stop the run; forced termination orphans the build children (concurrency/signals)
:32-38 (trap), :421-424 / :463-466 (background children), :107-124 (spinner loop)

Background children are spawned with job control off (they inherit SIGINT=SIG_IGN), and inside the kill -0 spinner loop the shell does not exit on SIGINT (note: macOS ships bash 3.2). A terminal Ctrl-C does not kill the yarn install/build, and a forced kill (SIGTERM/HUP) runs the EXIT trap (lock is removed) but orphans the yarn children, which keep writing into the worktrees and can collide with the next run.

Suggested fix: trap 'kill 0 2>/dev/null; rm -rf "$LOCK_DIR"; exit 130' INT TERM HUP; prefer a wait-based spinner loop; consider recommending bash ≥ 4 in the script header.


Medium

M1 — "Removing previous build output" removes nothing; a partial failure leaves a stale/mixed comparison that the next run silently reuses
:248 (message), :475/:489 (rm only on the success path), :502 (early exit), :506 (meta write)

The dirs are only removed inside each branch's success path. If the master build succeeds and the changed build fails, the script exits at :502 leaving new master output + old changed output + the previous reg-meta.env. The next run's Step 0 then passes its existence check and offers to reuse a mixed old/new comparison with misleading SHAs, with no warning.

Suggested fix: rm -rf "$PLATFORMS_MASTER" "$PLATFORMS_CHANGED" "$META_FILE" immediately when the user declines reuse (at minimum rm -f "$META_FILE"), so a later failure can't leave a meta that no longer matches the copied output.

M2 — Worktree reuse: discarded checkout exit code + stale platforms/ can corrupt the report
:389-395 (reuse path), :475-491 (copy), root cause in scripts/build/build.js (only cleans temp/platforms, merges into existing platforms/)

  • git -C "$path" checkout -f "$sha" has its exit code discarded (return 0 is unconditional) — a failed checkout silently proceeds to build from stale state.
  • On a reused worktree whose last build was a different branch, stale platform dirs/files survive the rebuild, and cp -r (a merge, not a mirror) copies them into the report → false DIFF/ADDED entries.

Suggested fix: git -C "$path" checkout -f "$sha" || return 1 (and have callers check setup_worktree); before reuse builds run git -C "$path" clean -fdx -- platforms/ (or copy with rsync --delete).

M3 — Cleanup wipes unrelated temp/ state used by other build tooling (resources)
:529 (cleanup_all)

find "$TEMP_DIR_NAME" -mindepth 1 -maxdepth 1 ! -name logs -exec rm -rf {} + deletes everything under repo temp/ except logs/ — including temp/filters_cached and temp/optimization/stats, the caches generate-cache / download-stats / build:local reuse. Cleanup defaults to Yes.

Suggested fix: scope deletion to the script's own artifacts (the two worktrees + platforms_{master,changed}_build + reg-meta.env).

M4 — Doc/code mismatch: "skipping reinstall" vs always-install
DEVELOPMENT.md:147 vs script comment at :417 ("Always runs, even for a reused worktree") and Step 5

The doc promises a reused worktree means "keeping node_modules, skipping reinstall", but yarn install is unconditionally run for both worktrees every time (fast with cache/lockfile, but it is not skipped and may hit the network).

Suggested fix: reword the doc ("keeping node_modules so yarn install is fast") or make Step 5 conditional on reuse.


Low / nits

  • L1:223 printf "$diff_list\n" uses data (file paths) as the format string (shellcheck SC2059); a % in a filename corrupts output. Low real impact here (numeric filenames) but trivially fixed: printf '%b\n' "$diff_list".
  • L2:439 Step 6 baseline sync only overwrites files that exist in master; files added by the compare branch under filters/ survive, so revision.json counters can still diverge. Consider git rm -r -q --ignore-unmatch filters before the checkout.
  • L3:206-213 + :229-231 the new reverse ADDED check makes any intentionally added file flip the verdict to FAIL. Consider making ADDED informational or documenting that ADDED ⇒ manual review required.
  • L4:375 CHANGED_SHA=$(git rev-parse ...) is unguarded while MASTER_SHA (:371) is guarded; Step 0 sources a possibly truncated/empty meta with no validation. Mirror the master guard; validate the meta file before sourcing.
  • L5:95 vs :392 reuse defaults are inconsistent: build-output reuse defaults Yes, worktree reuse defaults No.
  • L6:104-109 spinner frame slicing is character-based and breaks under LANG=C (byte slicing of braille glyphs).
  • L7:183 / :201 ! -name "*.patch" is dead next to -name "*.txt" (inherited from the deleted script).

Discarded after double-check: grep -cE "filters\.(json|js)" pattern looseness and missing pipefail on meta_diffs — that path is only informational ("version counter noise, not a regression"), so impact is cosmetic.


Summary

# Priority File:Line Type Issue Suggested fix
H1 high :238 / :506-513 security source of unquoted branch name → stored command injection / breakage %q quoting or parse instead of source
H2 high :32-38, :421-424, :463-466 concurrency Ctrl-C can't stop children; orphans on forced kill INT/TERM/HUP trap killing children; wait-based spinner; bash≥4 note
M1 med :248, :472-500, :506 state mgmt stale/mixed output silently reused after partial failure rm the 3 artifacts on decline; rm -f meta
M2 med :389-395, :475-491 correctness discarded checkout exit + stale platforms/ on reuse → false diffs `
M3 med :529 resources cleanup wipes unrelated temp/ caches delete only the script's own artifacts
M4 med DEVELOPMENT.md:147 doc drift "skipping reinstall" ≠ always install reword doc / conditional install
L1 low :223 shell printf format-string misuse printf '%b\n'
L2 low :439 baseline branch-added filters/ files never removed git rm --ignore-unmatch filters first
L3 low :206-231 semantics ADDED flips verdict to FAIL informational or documented
L4 low :375, :238 edge cases unguarded rev-parse / unvalidated meta guards + validation
L5 nit :95 vs :392 UX inconsistent reuse defaults align
L6 nit :104-109 cosmetic spinner locale-dependence ASCII frames
L7 nit :183, :201 cleanup dead ! -name "*.patch" drop

@105th 105th left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

posted review report

mu-hun added 10 commits August 28, 2026 19:54
Align to other shell scripts with pure whitespace change.

Suggested in #1212 (comment)
platforms_master_build/ and platforms_changed_build/ live under temp/, not
the repo root.

Discussed in #1212 (comment)
Print recorded short SHAs under the prompt to identify when kept output matches fresh commits. Append "(now at <short>)" to branch lines when the branch has moved since the last build to clarify stale reuse status.

Suggested in #1212 (comment)
The workflow section framed the cached path as one "generate-cache + build:local" choice;

This script actually asks three yes/no prompts: cached sources, then generate-cache, then download-stats.

Mentioned in #1212 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants