Skip to content

fix: P2 quick wins — version pinning, provider guard, micromatch glob#199

Merged
flaglint merged 2 commits into
mainfrom
fix/p2-quick-wins
Jul 1, 2026
Merged

fix: P2 quick wins — version pinning, provider guard, micromatch glob#199
flaglint merged 2 commits into
mainfrom
fix/p2-quick-wins

Conversation

@Krishan27

@Krishan27 Krishan27 commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • action.yml — adds version input (default "latest") so callers can pin to a specific release: uses: flaglint/flaglint-js@v1 with: version: "0.8.0". Routes through FLAGLINT_VERSION env var (no shell injection).

  • src/config.ts — replaces z.enum([...]) for provider with z.string() + a runtime assertSupportedProvider() check. Any unsupported provider now throws a clear error message (exit 2) rather than silently accepting "unleash" / "growthbook" as valid configs.

  • src/validator/index.ts — replaces the hand-rolled 30-line glob implementation in matchesBootstrapPattern() with micromatch.isMatch(). micromatch is already a direct dependency and handles edge cases ({a,b} brace expansion, negation, windows paths) that the DIY regex did not.

  • src/config/tests/config.test.ts — updates two tests that previously expected provider: "unleash" to be accepted; they now assert the new error message.

Test plan

  • npm run typecheck — clean
  • npm run test:run — 520/520 pass
  • Baseline tests (src/commands/tests/baseline.test.ts) already cover all --baseline / --fail-on-new scenarios
  • action.yml version input is safe: value routed through env var before shell sees it

Summary by CodeRabbit

  • New Features

    • Added an optional version input to the FlagLint GitHub Action (default: latest) to select the FlagLint release to run.
  • Bug Fixes

    • Improved configuration validation so unsupported provider values now fail with a clearer “not supported” error.
    • Enhanced bootstrap exclusion matching to handle glob patterns more reliably.
  • Tests

    • Updated test coverage for invalid/unsupported provider handling and error messaging.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4358e983-575e-4a4e-8362-83217fc5627d

📥 Commits

Reviewing files that changed from the base of the PR and between 0e943b2 and b954965.

📒 Files selected for processing (4)
  • action.yml
  • src/config.ts
  • src/config/tests/config.test.ts
  • src/validator/index.ts

📝 Walkthrough

Walkthrough

This PR adds a configurable FlagLint version to the composite action, restricts config provider support to launchdarkly, and switches bootstrap pattern matching to micromatch.

Changes

Configurable FlagLint version in composite action

Layer / File(s) Summary
Version input and invocation
action.yml
Adds a public version input and uses it to set FLAGLINT_VERSION for both FlagLint execution branches.

Restrict provider to launchdarkly

Layer / File(s) Summary
Schema and runtime provider enforcement
src/config.ts
Changes provider schema parsing to accept any string, adds a runtime check that only launchdarkly is allowed, and applies that check during config loading.
Provider validation tests
src/config/tests/config.test.ts
Updates config tests to expect explicit failures for unsupported provider values.

Bootstrap glob matching via micromatch

Layer / File(s) Summary
micromatch-based pattern matching
src/validator/index.ts
Replaces custom glob-to-RegExp matching with micromatch.isMatch, including path normalization and empty-pattern handling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the three main changes: version pinning, provider validation, and micromatch glob matching.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/p2-quick-wins

Comment @coderabbitai help to get the list of available commands.

- action.yml: add `version` input (default "latest"); route through
  FLAGLINT_VERSION env var so npx flaglint@"$FLAGLINT_VERSION" works
  safely without shell injection risk

- src/config.ts: replace z.enum() with z.string() + runtime
  assertSupportedProvider() that throws a clear exit-2 message for any
  provider other than "launchdarkly"; Zod still validates other fields

- src/validator/index.ts: replace 30-line hand-rolled glob implementation
  in matchesBootstrapPattern() with micromatch.isMatch() — micromatch
  is already a direct dependency (used in migrator/apply.ts)

- src/config/tests/config.test.ts: update two tests that previously
  expected provider:"unleash" to pass through; they now assert the new
  unsupported-provider error message

Signed-off-by: Krishan Kant Sharma <krishansharma0327@gmail.com>
@Krishan27 Krishan27 force-pushed the fix/p2-quick-wins branch from 0e943b2 to 4ef1421 Compare July 1, 2026 03:55
@flaglint flaglint merged commit 704f491 into main Jul 1, 2026
8 of 9 checks passed
@flaglint flaglint deleted the fix/p2-quick-wins branch July 1, 2026 03:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/config.ts (1)

19-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Provider type widened to plain string.

Switching from an enum to z.string() means FlagLintConfig["provider"] is now typed as a generic string rather than a literal union, losing compile-time narrowing for any downstream code that switches on provider values. Runtime enforcement via assertSupportedProvider covers correctness, but consider a companion literal type (e.g. type SupportedProvider = "launchdarkly") if provider-specific branching grows later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config.ts` at line 19, The provider field in the config schema is now
widened to a generic string, so downstream code loses compile-time narrowing for
provider-specific branching. Update the config typing around the
`FlagLintConfig`/`provider` schema in `config.ts` to preserve a literal union or
companion `SupportedProvider` type while keeping the existing runtime validation
via `assertSupportedProvider`, so future switches on provider remain type-safe.
src/validator/index.ts (1)

46-49: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Pattern cleaning is redundantly recomputed per usage.

clean is redefined and patterns.map(clean) re-executed on every call from the usages loop (Lines 74-88), even though bootstrapExclude doesn't change across iterations. Cheap to hoist the cleaned pattern list out of the per-usage loop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/validator/index.ts` around lines 46 - 49, The pattern cleanup in
matchesBootstrapPattern is being recomputed on every usage check, which is
unnecessary because bootstrapExclude is stable across the loop. Hoist the
cleaned pattern list out of the per-usage iteration and reuse it when calling
matchesBootstrapPattern, keeping the existing clean normalization logic in
matchesBootstrapPattern or a nearby helper so the comparison still uses the same
symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/config.ts`:
- Around line 55-63: The unsupported-provider path in assertSupportedProvider is
still being treated like a generic failure by loadConfigOrExit, so it always
exits with code 1 instead of the intended code 2. Update assertSupportedProvider
to throw a typed error or otherwise mark this case distinctly, and adjust
loadConfigOrExit to recognize that specific error before the generic catch and
call process.exit(2) for it while leaving other errors unchanged.

In `@src/validator/index.ts`:
- Around line 46-49: Normalize the path before glob matching in
matchesBootstrapPattern so Windows backslash-separated relative paths are
handled consistently. Update the clean helper to also convert backslashes to
forward slashes, alongside stripping the leading ./, and keep using it for both
the file argument and the patterns passed to micromatch.isMatch. This should
align with the other path helpers and ensure bootstrapExclude matches usage.file
correctly.

---

Nitpick comments:
In `@src/config.ts`:
- Line 19: The provider field in the config schema is now widened to a generic
string, so downstream code loses compile-time narrowing for provider-specific
branching. Update the config typing around the `FlagLintConfig`/`provider`
schema in `config.ts` to preserve a literal union or companion
`SupportedProvider` type while keeping the existing runtime validation via
`assertSupportedProvider`, so future switches on provider remain type-safe.

In `@src/validator/index.ts`:
- Around line 46-49: The pattern cleanup in matchesBootstrapPattern is being
recomputed on every usage check, which is unnecessary because bootstrapExclude
is stable across the loop. Hoist the cleaned pattern list out of the per-usage
iteration and reuse it when calling matchesBootstrapPattern, keeping the
existing clean normalization logic in matchesBootstrapPattern or a nearby helper
so the comparison still uses the same symbols.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 24f89175-8aaf-458b-b396-41404009740f

📥 Commits

Reviewing files that changed from the base of the PR and between f8054c8 and 0e943b2.

📒 Files selected for processing (4)
  • action.yml
  • src/config.ts
  • src/config/tests/config.test.ts
  • src/validator/index.ts

Comment thread src/config.ts
Comment thread src/validator/index.ts
Comment on lines 46 to +49
export function matchesBootstrapPattern(file: string, patterns: string[]): boolean {
// Normalise leading "./"
if (patterns.length === 0) return false;
const clean = (s: string): string => s.replace(/^\.\//, "");
const cleanFile = clean(file);

return patterns.some((pattern) => {
const cleanPattern = clean(pattern);
if (cleanFile === cleanPattern) return true;

// Convert glob to a regular expression.
const regexStr = cleanPattern
// Escape regex-special chars except * and ?
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
// Temporarily hide ** before processing single *
.replace(/\*\*/g, "\x00")
// Single * → match any sequence that does NOT cross a /
.replace(/\*/g, "[^/]*")
// ** → match anything including /
.replace(/\x00/g, ".*")
// ? → single non-separator char
.replace(/\?/g, "[^/]");

try {
return new RegExp(`^${regexStr}$`).test(cleanFile);
} catch {
return false;
}
});
return micromatch.isMatch(clean(file), patterns.map(clean));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

micromatch isMatch negation pattern array semantics vs match filter

💡 Result:

In micromatch, the handling of negation patterns in arrays differs significantly between boolean checks and list filtering due to how they resolve inclusion and exclusion [1][2]. Micromatch follows these established conventions regarding negation: 1. When a pattern is a string, patterns are inclusive by default [1]. 2. When an array of patterns is passed, patterns are exclusive by default [1]. Boolean Matching (isMatch) The isMatch(str, patterns) function returns true if any of the given glob patterns match the specified string [3][4]. When using an array of patterns, the behavior of negations (e.g., ['!pattern']) can be counterintuitive because a positive match from any non-negated pattern will still result in a true value [5]. Effectively, isMatch checks if the string matches any of the "include" patterns; it does not inherently act as a filter that must pass every negation constraint unless specifically structured to do so [5]. Array Filtering (match and filter) Methods like match(list, patterns) or the function returned by filter(patterns) are designed to perform set-based filtering [1][2]. These methods implement exclusive-by-default logic: - They start with a set of candidates and apply the patterns to filter the list [1]. - When an array of patterns includes both positive and negative glob patterns, the negative patterns (e.g., '!*.js') correctly exclude matching items from the final result set [1]. Key Differences - isMatch is designed for testing a single string against a set of rules and returns a boolean [3][4]. Because it is "any-match" based, simply providing negative patterns in an array may not exclude the string if other patterns also match it [5]. - filter/match is designed for processing lists [1][2]. It respects the "exclusive by default" behavior, where negative patterns are effectively used to subtract items from the result set [1]. If you require a boolean check that respects all negation patterns as "must not match," you may need to use a custom wrapper or check the string against match to see if it remains in the result set [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File under review ==\n'
sed -n '1,140p' src/validator/index.ts | cat -n

printf '\n== Micromatch dependency context ==\n'
for f in package.json package-lock.json pnpm-lock.yaml yarn.lock; do
  if [ -f "$f" ]; then
    echo "--- $f"
    rg -n 'micromatch' "$f" -n -C 2 || true
  fi
done

printf '\n== Search for matching path normalization / negation handling ==\n'
rg -n 'replace\(/\\\\/g|isMatch\(|micromatch\(' src test . || true

Repository: flaglint/flaglint-js

Length of output: 8195


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Path normalization in scanner/usage flow ==\n'
rg -n 'replace\(/\\\\/g, "/"\)|usage\.file|filePath|scannedFiles|usages' src -C 2

printf '\n== Scanner fingerprint helper ==\n'
sed -n '1,80p' src/scanner/fingerprint.ts | cat -n

printf '\n== Validation call sites / usage shape ==\n'
sed -n '1,140p' src/types.ts | cat -n

Repository: flaglint/flaglint-js

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File source / path shape ==\n'
sed -n '1,220p' src/scanner/source.ts | cat -n 2>/dev/null || true
sed -n '1,220p' src/scanner/index.ts | awk 'NR>=640 && NR<=750 {print NR ":" $0}'
sed -n '1,220p' src/validator/tests/validate.test.ts | awk 'NR>=340 && NR<=390 {print NR ":" $0}'

printf '\n== Micromatch docs in repo ==\n'
rg -n 'bootstrapExclude|matchesBootstrapPattern|isMatch|micromatch' README.md src -C 2

Repository: flaglint/flaglint-js

Length of output: 12050


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File source / path shape ==\n'
sed -n '1,220p' src/scanner/source.ts | cat -n 2>/dev/null || true
sed -n '640,750p' src/scanner/index.ts | cat -n
sed -n '340,390p' src/validator/tests/validate.test.ts | cat -n

printf '\n== Micromatch/docs references ==\n'
rg -n 'bootstrapExclude|matchesBootstrapPattern|isMatch|micromatch' README.md src -C 2

Repository: flaglint/flaglint-js

Length of output: 18080


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== FileSource / path contracts ==\n'
sed -n '1,140p' src/types.ts | cat -n

printf '\n== Source implementations ==\n'
fd -a 'source.ts' src
fd -a 'source.*' src
fd -a '*source*' src

printf '\n== Fast-glob / path normalization usage ==\n'
rg -n 'fast-glob|fg\.|listFiles\(' src -C 2

Repository: flaglint/flaglint-js

Length of output: 4780


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== local-source implementation ==\n'
sed -n '1,220p' src/scanner/local-source.ts | cat -n

printf '\n== any Windows-path normalization/tests ==\n'
rg -n '\\\\|replace\(/\\\\/g, "/"\)|file: .*\\|scanRoot' src test -C 2

Repository: flaglint/flaglint-js

Length of output: 13381


Normalize usage.file before glob matching. LocalFileSource can feed backslash-separated relative paths on Windows, but clean() only strips ./, so bootstrapExclude can miss intended matches there. Add replace(/\\/g, "/") here to match the other path helpers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/validator/index.ts` around lines 46 - 49, Normalize the path before glob
matching in matchesBootstrapPattern so Windows backslash-separated relative
paths are handled consistently. Update the clean helper to also convert
backslashes to forward slashes, alongside stripping the leading ./, and keep
using it for both the file argument and the patterns passed to
micromatch.isMatch. This should align with the other path helpers and ensure
bootstrapExclude matches usage.file correctly.

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.

2 participants