Skip to content

feat(ws-changed): filter and classify a changeset by file extension - #123

Merged
pyramation merged 1 commit into
mainfrom
feat/ws-changed-extensions
Aug 15, 2026
Merged

feat(ws-changed): filter and classify a changeset by file extension#123
pyramation merged 1 commit into
mainfrom
feat/ws-changed-extensions

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

ws-changed answers "which packages are affected?" but has no notion of what kind of file changed, so a CI lane can only ask the one coarse question. That's how a diff of three package.json script strings ends up rebuilding a dev image and running a SQL lint lane. git-changed already has ext/include/exclude; this teaches ws-changed the same filter, plus the reporting a lane needs to decide on its own.

A FileFilter narrows the changed files before they're attributed to packages, so the same diff answers several questions:

// which packages have changed SQL? (and their dependents)
wsChanged({ overrides: { provider: 'pgpm', files: { ext: '.sql' } } }).result.packages;

// what kind of change was it, per package?
wsChanged().result.extensionsByPackage; // { 'my-pkg': ['.md', '.ts'] }

Filtering before ownership is the point, and it has two consequences worth stating:

  • a dropped file can't make a package changed, and it also can't trigger global — a .sql lane isn't told "everything is affected" by a changed pnpm-lock.yaml. Ask the unfiltered question when you want the lockfile's blast radius;
  • extensionsByPackage covers changed packages only, never dependents — they own no changed file, so "what changed in pkg" stays distinct from "what pkg is affected by".

New on AffectedResult:

extensions: string[];                            // ['.sql', '.ts'] across the changeset
extensionsByPackage: Record<string, string[]>;   // { core: ['.sql'] }
ignored: string[];                                // changed paths the filter dropped

ext accepts the shapes git-changed accepts ('sql', '.sql', 'ts,tsx', ['.ts','.tsx']), normalized to lowercased .ext, so a filter moves between the two packages without translation. extOf deliberately treats a dotfile or extensionless name as having no extension (.gitignore, Makefile, bin/ws-changed''), which means they match no ext filter rather than accidentally matching every one.

Surfaces: files in config, WS_CHANGED_EXT in the environment (so a CI lane needn't carry its own config file), and --ext / --files / --not-files / --exts on the CLI:

ws-changed --provider pgpm --ext .sql --exts
ws-changed --ext ts,tsx --not-files '**/generated/**'

77 tests pass; new coverage includes uppercase/multi-dot/dotfile extensions, absolute paths, include/exclude globs, a filtered-out global trigger, and dependents staying out of extensionsByPackage.

Follow-up, not here: routing constructive-db's dev-image.yml and proxy-e2e-tests.yaml through this instead of blanket YAML paths: entries.

Link to Devin session: https://app.devin.ai/sessions/0512ac5790334f939a7bd0c2a8442162
Requested by: @pyramation

@pyramation pyramation self-assigned this Aug 15, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit 4280b7b into main Aug 15, 2026
61 checks passed
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.

1 participant