◉RORK LABJP
●GPT6.1 — GPT-6.1 Sol joined the Rork model menu (Sep 29). It is the latest entry on the changelog●10/12 — 7 days left until React Native 0.88.x is due. Expo SDK 58 stable is only described as early October●NEW — Multiseat Purchases Are Already On: Decide Whether to Keep Them Before October 22●SDK 58 — A fix PR (#50998) for npm install failing in new projects is under review. No stable date has been given yet●SONNET — Claude Sonnet 5.5 is now in Rork (Sep 28). It is described as over 30% faster than Sonnet 5●iOS 27 — From April 2027, App Store uploads require the iOS 27 SDK. There is time to update your build environment calmly●GPT6.1 — GPT-6.1 Sol joined the Rork model menu (Sep 29). It is the latest entry on the changelog●10/12 — 7 days left until React Native 0.88.x is due. Expo SDK 58 stable is only described as early October●NEW — Multiseat Purchases Are Already On: Decide Whether to Keep Them Before October 22●SDK 58 — A fix PR (#50998) for npm install failing in new projects is under review. No stable date has been given yet●SONNET — Claude Sonnet 5.5 is now in Rork (Sep 28). It is described as over 30% faster than Sonnet 5●iOS 27 — From April 2027, App Store uploads require the iOS 27 SDK. There is time to update your build environment calmly
Articles/Dev Tools
⬡ Dev Tools/2026-08-14Intermediate

Your lockfile's dev/prod split won't tell you which licenses your app must credit

A record of classifying every dependency in a production project to decide what belongs on an app's license screen, and where the copyleft findings and the actual shipped artifact turned out to disagree.

Rork575Expo212LicensingOpen SourceApp Store89Indie Dev39

✦ Premium Article

I stalled for half a day over whether to put a "Licenses" row at the bottom of a settings screen.

Adding it was clearly the right thing. What I could not decide was what to list there. A project exported by an AI builder carries hundreds of packages you never picked yourself. Do you list all of them, or only what actually ships? Pasting a template without answering that question is not disclosure. It is the appearance of disclosure.

So I classified the lockfile of a Next.js 16 project I run in production. The short version: the fourteen LGPL packages that made my stomach drop were not shipping at all. And the dependencies that genuinely needed crediting could not be identified from the dev / prod flags alone.

Start by counting the lockfile as it is

package-lock.json at lockfileVersion: 3 records a license field per package. You can inventory every dependency straight from the lockfile, without expanding node_modules.

Here is the script I actually ran. Standard library only.

# license_inventory.py - classify dependency licenses from the lockfile alone
import json, collections, sys
 
path = sys.argv[1] if len(sys.argv) > 1 else "package-lock.json"
with open(path, encoding="utf-8") as f:
    data = json.load(f)
 
packages = data.get("packages", {})
if not packages:
    # lockfileVersion 1 has no "packages" map, so fail loudly here
    raise SystemExit("No packages map. Regenerate the lockfile at version 2 or later.")
 
def classify(meta):
    # Order matters. Check dev first, or devOptional leaks into prod.
    if meta.get("dev"):
        return "dev"
    if meta.get("devOptional"):
        return "devOptional"
    if meta.get("optional"):
        return "optional"
    return "prod"
 
counts = collections.Counter()
by_class = collections.defaultdict(collections.Counter)
unknown = []
 
for name, meta in packages.items():
    if not name:          # the empty key is the root project itself
        continue
    kind = classify(meta)
    counts[kind] += 1
    lic = meta.get("license")
    if isinstance(lic, list):     # older packages sometimes use an array
        lic = " / ".join(lic)
    if lic is None:
        unknown.append(name)
        lic = "(none recorded)"
    by_class[kind][lic] += 1
 
print("total:", sum(counts.values()), dict(counts))
for kind in ("prod", "optional", "dev", "devOptional"):
    if by_class[kind]:
        print(f"--- {kind} ---")
        for lic, n in by_class[kind].most_common():
            print(f"  {n:4d}  {lic}")
print("missing license field:", len(unknown))
for name in unknown[:20]:
    print("  ", name)

The output looked like this.

ClassCountWhat it means
prod430Resolved as a production dependency
dev429Used only by builds, tests, and linters
optional70Platform-specific binaries and similar, installed conditionally
devOptional3Development-side and optional

932 packages in total. prod and dev split almost evenly at 46% each, and the entries carrying copyleft terms came to 27 packages, or 2.9% of the tree. Not one package was missing a license field.

That last part surprised me. With several hundred dependencies I expected a handful of gaps, and there were none. Even at the scale an indie developer works at, the provenance of the tree is tidier than I assumed.

Every copyleft hit was build-only

Scanning the breakdown, the first thing that caught my eye was ten packages under LGPL-3.0-or-later plus four more under Apache-2.0 AND LGPL-3.0-or-later. Fourteen in total. Enough to stop working and start reading.

Listing them showed all fourteen were platform binaries for the image library sharp (@img/sharp-libvips-*). All fourteen classified as dev.

The same pattern held for the thirteen MPL-2.0 packages: lightningcss with its platform binaries, plus the accessibility checker axe-core. Every one of them dev.

LicenseCountClassWhat they actually are
LGPL-3.0-or-later (alone and in combination)14all devsharp's libvips binaries
MPL-2.013all devlightningcss / axe-core
CC-BY-4.01prodcaniuse-lite

The fourteen packages I braced myself for never reach a user's device. The image conversion happened on a build machine, and only the converted output is distributed.

That points at something about method. If you begin a license inventory as a hunt for scary names, the first things you find will almost always be build tooling. Build tools run on developer machines, which makes them likely to ship native binaries, and native binaries are the most likely place for copyleft to appear. Searching in that order spends your attention exactly where it matters least.

MPL-2.0 asks that when you distribute covered software in executable form, you tell recipients how to obtain the corresponding source form (Mozilla Public License 2.0). Read the other way around: if you are not distributing it, that clause is not your problem yet. Establishing what you distribute has to come first.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦You will be able to separate dependencies that reach your shipped artifact from the ones that only run on your build machine, and decide on your own terms what belongs on a license screen
✦You will have a checking order that stops you from freezing the moment you spot a copyleft package name, before confirming whether it ever reaches users at all
✦You will see how 932 dependencies in a production project broke down by class, so you know where to look first in your own project
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

Related Articles

⬡ Dev Tools2026-08-20
A beta-SDK build can reach TestFlight, but it can't reach review
Builds made with a beta Xcode can be distributed through TestFlight, but they cannot be submitted for App Store review. Here is how to check which SDK produced your build, and how to protect your release profile in eas.json.
⬡ Dev Tools2026-09-21
A build that stops with exit code 0 — the flag that silenced the log, and the check that let an empty file through
Two reports landed in the same week: eas build --local stopping with exit code 0 but produced no further output, and a zero-byte stub that never got embedded. Both came down to settings that reduce output. Here is how to audit your own scripts.
⬡ Dev Tools2026-09-07
Adding .easignore stops EAS from reading .gitignore — count what actually ships to the build
EAS Build decides what to upload from .gitignore, and the moment you add .easignore the two swap places. Here is how I count the bundled files before sending, plus a measured result: once a parent directory is excluded, an exclamation mark cannot bring a file back.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links