Skip to content

[#100] ✨ feat: add phantom dependency check#101

Open
djk01281 wants to merge 1 commit into
mainfrom
repo/100
Open

[#100] ✨ feat: add phantom dependency check#101
djk01281 wants to merge 1 commit into
mainfrom
repo/100

Conversation

@djk01281
Copy link
Copy Markdown

@djk01281 djk01281 commented May 27, 2026

Related Issues

Describe your changes

Summary

image



  • phantomDepCheck option (severity: 'error' | 'warn' | 'off', default 'error').
  • Detects core-js-pure (the polyfill pite injects into the build output) and classifies how the consumer's package.json covers the injected import:
Category Key in dependencies / bundledDependencies? Version known at build time? Build action
phantom No n/a Fails on severity: 'error'
outdated Yes Yes, and the range floor is below what pite injects (3.39.0) Fails on severity: 'error'
unverifiable Yes No, resolved at install: catalog:, workspace:*, latest, git, tarball Always warns
  • For phantom, if core-js-pure is found in peerDependencies / optionalDependencies / devDependencies, the error points to where it sits so the fix is "move it to dependencies".

Impact

  • Libraries whose output injects core-js-pure: must declare core-js-pure >= 3.39.0 in dependencies, otherwise the build fails with a concrete instruction.
  • Libraries declaring catalog: / workspace:* / latest / git / tarball: warned but not blocked, so legitimate monorepo setups keep working. Ensure the resolved version is >= 3.39.0.
  • Libraries that don't inject core-js-pure: plugin passes, nothing to check.

@djk01281 djk01281 self-assigned this May 27, 2026
@npayfebot
Copy link
Copy Markdown
Contributor

npayfebot commented May 27, 2026

✅ Changeset detected

Latest commit: 7f3b8ef

@naverpay/pite package have detected changes.

If no version change is needed, please add skip-detect-change to the label.

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@naverpay/pite ✨ Minor
powered by: naverpay changeset detect-add actions

@npayfebot
Copy link
Copy Markdown
Contributor

npayfebot commented May 27, 2026

NPM Packages

📦 @naverpay/pite

Total Sizes: 15.9 kB

Total Changes: +5.66 kB (+55%) 🔍 (Size Increased)

File Status Previous Size Updated Size Changed
/dist/index.mjs 🛠️ 1.48 kB 1.54 kB +63 B (+4%)
/dist/index.js 🛠️ 1.56 kB 1.62 kB +61 B (+4%)
/dist/phantom-deps.mjs - 1.13 kB +1.13 kB
/dist/phantom-deps.js - 1.18 kB +1.18 kB
/dist/plugins/rollup-plugin-phantom-deps.mjs - 1.61 kB +1.61 kB
/dist/plugins/rollup-plugin-phantom-deps.js - 1.61 kB +1.61 kB
ℹ️ View Unchanged Files
File Status Previous Size Updated Size Changed
/dist/vite-tsup-plugin.mjs - 678 B 678 B -
/dist/vite-tsup-plugin.js - 681 B 681 B -
/dist/util.mjs - 295 B 295 B -
/dist/util.js - 352 B 352 B -
/dist/polyfill.mjs - 520 B 520 B -
/dist/polyfill.js - 576 B 576 B -
/dist/get-vite-entry.mjs - 637 B 637 B -
/dist/get-vite-entry.js - 695 B 695 B -
/dist/dependencies.mjs - 337 B 337 B -
/dist/dependencies.js - 389 B 389 B -
/dist/browserslist.mjs - 264 B 264 B -
/dist/browserslist.js - 323 B 323 B -
/dist/plugins/rollup-plugin-publint.mjs - 742 B 742 B -
/dist/plugins/rollup-plugin-publint.js - 749 B 749 B -

🧩 Dependency Changes

Package Status Previous Version Updated Version Bundle Size (Max) Changed
semver@^7.8.1 - ^7.8.1 100 kB 100 kB

powered by: naverpay size-action

@djk01281 djk01281 force-pushed the repo/100 branch 3 times, most recently from e4f9e06 to 35b809a Compare May 27, 2026 11:55
@djk01281 djk01281 marked this pull request as ready for review May 27, 2026 12:02
@djk01281 djk01281 requested a review from a team as a code owner May 27, 2026 12:02
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, push a new commit or reopen this pull request to trigger a review.

@yujeong-jeon
Copy link
Copy Markdown
Contributor

@claude review this PR please.

@claude
Copy link
Copy Markdown

claude Bot commented May 28, 2026

Claude finished @yujeong-jeon's task in 2m 32s —— View job


Claude PR Review

  • Read changed files
  • Analyze logic & edge cases
  • Post review feedback

Nice, focused addition. The split between pure logic (src/phantom-deps.ts) and presentation (src/plugins/rollup-plugin-phantom-deps.ts) is clean, and reusing PITE_INJECTED['core-js-pure'] for both the babel polyfill version and the check threshold is exactly right — one source of truth.

A few things worth looking at:

🟡 Findings

1. * and "" ranges fall through as outdated, not unverifiablesrc/phantom-deps.ts:109-118

semver.validRange('*') returns '*', and semver.validRange('') also normalizes to '*', so both pass the range === null check. Then semver.minVersion('*') returns 0.0.0, which is lt('3.39.0') → reported as outdated with a misleading message ("declared * (floor 0.0.0)").

Both ranges actually mean "any version" — they're semantically unverifiable at build time (the resolved version depends on what the registry returns at install). Suggest routing them to unverifiable instead. Quick fix:

const range = toSemverRange(declared)
if (range === null || range === '' || semver.validRange(range) === '*') {
    unverifiable.set(name, {declared, required})
    continue
}

2. Pre-release floors get flagged as outdatedsrc/phantom-deps.ts:115-118

If a consumer declares "core-js-pure": "^3.39.0-rc.1", minVersion returns 3.39.0-rc.1, which is semver.lt('3.39.0') → false-positive outdated. Probably rare in practice for core-js-pure, but worth a {includePrerelease: true} or an explicit allowance.

3. findMisplaced returns first match onlysrc/phantom-deps.ts:64-71

If core-js-pure is in both peerDependencies and devDependencies (a common combo for libraries), only peerDependencies is reported. The fix message ("move it to dependencies") still works, but a user reading the output may be confused why their devDependencies entry isn't mentioned. Minor — current behavior is defensible.

4. Wording inconsistency between misplaced-hint and Fix blocksrc/plugins/rollup-plugin-phantom-deps.ts:35 vs :26

When a phantom is misplaced, the line above says "move it to dependencies" and the Fix block below says "add to dependencies". Both are technically right but read as two separate instructions. Consider conditionally swapping the Fix verb to "move" when all phantoms have a misplacedIn.

5. JSON type is unchecked after parsesrc/phantom-deps.ts:86, 66

pkg.dependencies is typed Record<string, string> but comes straight from JSON.parse. If a malformed package.json has "dependencies": "oops" (string instead of object), name in (pkg[field] ?? {}) would throw TypeError: Cannot use 'in' operator. Probably not worth defensive guards everywhere, but a typeof pkg.dependencies === 'object' narrowing at read time would be cheap insurance.

6. No tests for the parsing logic — new file src/phantom-deps.ts

toSemverRange handles npm: aliases, #semver: git refs, workspace: variants — non-trivial parsing that's easy to regress. Worth a small unit test file covering at least: workspace:*, workspace:^1.0.0, npm:@scope/x@^1, #semver:1.x, catalog:, file:./tarball.tgz, plain ^3.39.0, prerelease, and *.

Add tests for phantom-deps parsing →

🔍 Smaller notes

  • src/plugins/rollup-plugin-phantom-deps.ts:96path.relative(cwd, path.join(options.dir, fileName)) produces a nice display path; nothing wrong, just noting it works because options.dir is set for every output in src/index.ts:165.
  • src/phantom-deps.ts:99pkg.bundledDependencies?.includes(name) only checks the array form; npm also accepts bundleDependencies (no d). Probably out of scope, but flagging for completeness.
  • src/index.ts:113 — destructuring default phantomDepCheck: {severity: phantomDepSeverity = 'error'} = {} only applies the inner default when the prop is omitted. Passing phantomDepCheck: {} works as expected; passing phantomDepCheck: {severity: undefined} also works due to destructuring defaults. Good.
  • closeBundle runs on severity: 'warn' and prints the report but does not throw — matches the documented behavior. ✓
  • closeBundle correctly defers errors via this.error() rather than throwing synchronously — good Rollup-API hygiene.

✅ Things I like

  • PITE_INJECTED as a single map shared between the babel polyfill config (src/index.ts:199) and the check threshold. Easy to extend if you ever ship more polyfills.
  • The "Why" explanation block in formatPhantoms — actually answers "why is this an error?" instead of just demanding a fix.
  • unverifiable as a soft warn tier so legitimate catalog:/workspace:* setups aren't blocked.

Overall: ready to merge once the */"" classification is reconsidered (item 1). Everything else is polish.
· branch repo/100

@djk01281
Copy link
Copy Markdown
Author

@claude addressed findings 1, 4, 5

@claude
Copy link
Copy Markdown

claude Bot commented May 28, 2026

Claude finished @djk01281's task in 4m 9s —— View job


Follow-up: verifying fixes for findings 1, 4, 5


✅ Finding 1 — fixed

src/phantom-deps.ts:61-62

const valid = semver.validRange(range)
return valid === null || valid === '*' ? null : range

You put the gate inside toSemverRange rather than at the call site — cleaner than my suggestion since the helper is the single source of truth. It catches every form of "any version" in one shot: *, "", x, x.x, x.x.x all validRange()-normalize to '*' and now return null, so checkPhantomDeps routes them to unverifiable. latest / next were already null from validRange and continue to work the same way. ✓

✅ Finding 4 — fixed

src/plugins/rollup-plugin-phantom-deps.ts:25-32

const fixDependencies = (entries: [string, {required: string; misplacedIn?: string}][]): string[] => {
    const allMisplaced = entries.every(([, p]) => p.misplacedIn)
    const verb = allMisplaced ? 'move to' : 'add to'
    ...
}

When every phantom has a misplacedIn, the Fix block now says "move to", matching the per-line "move it to" hint. When the set is mixed (some misplaced, some genuinely absent), it falls back to "add to", which is still correct because each misplaced entry retains its own per-line hint above. ✓

✅ Finding 5 — fixed

src/phantom-deps.ts:65-73

const findMisplaced = (pkg: Manifest, name: string): string | undefined => {
    for (const field of ['peerDependencies', 'optionalDependencies', 'devDependencies'] as const) {
        const deps = pkg[field]
        if (deps && typeof deps === 'object' && name in deps) {
            return field
        }
    }
    ...
}

The specific crash I flagged (name in (pkg[field] ?? {}) throwing TypeError on a non-object) is now prevented. Practical and localized — no over-engineering. ✓

🔍 One residual edge worth knowing about (no action needed)

src/phantom-deps.ts:101 still calls pkg.bundledDependencies?.includes(name) without confirming it's an array. If a malformed manifest has "bundledDependencies": {}, .includes is undefined on plain objects and would throw TypeError: includes is not a function. Very rare in real package.json files — flagging only for completeness, not asking for a change.


All three findings look correctly addressed — ready to ship.
· branch repo/100

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.

3 participants