All 25 checks with why-it-matters prose, severity, and cross-references to related audits.
Without a modern `exports` field, Node.js 16+ consumers get inconsistent resolution across bundlers and runtimes. Tools like webpack 5 and Vite treat the `exports` map as authoritative — packages that rely on legacy `main`/`module` fields alone can fail outright under strict resolution or expose internal files via unguarded deep imports. ISO 25010 modifiability and interoperability both degrade when entry points are implicit rather than declared. The practical result: import errors in production that only surface at the consumer's build step, not yours.
Why this severity: Critical because misconfigured or absent entry-point declarations cause outright import failures at build time for Node.js 16+ and all strict-resolution bundlers.
sdk-package-quality.package-config.exports-fieldSee full patternShipping an npm package without TypeScript declarations means every consumer is flying blind. IDEs show `any` for all imports, type errors go unreported until runtime, and documentation-on-hover disappears entirely. ISO 25010 analysability collapses — developers must clone your repo just to understand your API surface. For TypeScript projects (now the majority of serious npm consumers), a package without `.d.ts` files is effectively unusable without manual `declare module` workarounds.
Why this severity: Critical because the absence of type declarations renders the package untyped for all TypeScript consumers, eliminating IDE autocompletion, type checking, and compile-time safety.
sdk-package-quality.package-config.type-declarationsSee full patternListing React, Vue, or any consumer-provided framework in `dependencies` instead of `peerDependencies` installs a second copy of that framework in the consumer's `node_modules`. For React specifically, this triggers the infamous "Invalid hook call" error because two separate React instances exist at runtime — a hard crash, not a degraded experience. It also silently doubles the framework's bundle weight. SSDF PS.3.2 flags this as a supply chain concern: you're injecting unverified versions of major frameworks into consumer apps. ISO 25010 interoperability fails when your version range conflicts with the host app.
Why this severity: High because duplicate framework installations cause runtime crashes (React hooks, Vue reactivity) and inflated bundle sizes that directly affect the end user.
sdk-package-quality.package-config.peer-depsSee full patternPackage names on npm are permanent. Publishing `MyUtils` or `helpers` creates a name that cannot be renamed without breaking every downstream install. CamelCase and underscores in package names violate npm naming rules and can trigger resolution issues in some bundlers. Generic unscoped names (`utils`, `lib`, `tools`) are either already taken or so ambiguous that consumers cannot distinguish your package from dozens of identically named ones. A scoped name (`@yourorg/my-utils`) is the only way to guarantee namespace ownership.
Why this severity: High because package names are immutable post-publish — a non-compliant or generic name cannot be corrected without abandoning the published identifier and re-publishing under a new name.
sdk-package-quality.package-config.package-namingSee full patternWithout a `LICENSE` field and `LICENSE` file, your package defaults to exclusive copyright — consumers who install it are technically violating copyright law even for internal use. Enterprise legal teams routinely reject dependencies with no license declaration. SPDX license expressions enable automated license scanning in CI pipelines (SSDF PO.1.2). A package without a clear SPDX identifier won't pass compliance checks at many organizations, meaning your package gets blocked regardless of its technical quality.
Why this severity: Medium because missing license data triggers automated blocking by enterprise compliance scanners and exposes users to legal risk, though the package still functions technically.
sdk-package-quality.package-config.license-fieldSee full patternWithout a `files` allowlist or `.npmignore`, running `npm publish` sends your entire repository — test fixtures, CI configs, editor settings, source TypeScript, internal tooling, and potentially `.env.example` files — to every consumer who installs your package. ISO 25010 confidentiality is directly impacted: internal implementation details that weren't meant to be public are now permanently embedded in a versioned npm release. SLSA L2 requires artifact provenance; publishing unpredicted files breaks that guarantee. Bloated packages also slow installs for all downstream users.
Why this severity: High because the absence of a `files` allowlist exposes internal source code, test fixtures, and configuration files to all npm consumers permanently, with no way to remove them from published versions.
sdk-package-quality.package-config.files-fieldSee full patternA package whose default entry point exports nothing useful forces consumers to discover and import from internal paths — paths that encode your directory structure and break silently when you refactor. ISO 25010 interoperability fails when `import { Thing } from 'your-package'` returns `undefined`. This also breaks tree-shaking, IDE autocompletion, and any tool that introspects the package's public API via its declared entry point. The entry point is a contract; an empty or useless one is a broken contract.
Why this severity: Critical because a misconfigured entry point means the primary import path resolves to nothing, causing immediate runtime failures for every consumer of the package.
sdk-package-quality.api-surface.clear-entry-pointSee full patternA package that only exports a single default namespace object forces bundlers to include the entire package in every consumer's bundle, regardless of which methods they actually use. ISO 25010 resource-utilization degrades linearly with how much dead code consumers are forced to carry. Tools like webpack, Rollup, and Vite perform tree-shaking on named exports but cannot eliminate unused properties of a default export object. For frontend consumers, this directly inflates page weight and Lighthouse scores.
Why this severity: High because default-only exports defeat tree-shaking across all major bundlers, forcing consumers to ship your entire API surface even when they use one function.
sdk-package-quality.api-surface.named-exportsSee full patternWhen every error a package throws is a plain `Error` with a string message, consumers are forced to parse error messages with regex or `includes()` checks — fragile code that breaks the moment you change wording. ISO 25010 fault-tolerance and testability both suffer: you cannot write a `catch (e) { if (e instanceof NetworkError)` guard without custom error types. For SDK packages that wrap external APIs, distinguishing a 429 from a 500 from a network timeout requires typed errors. Without them, consumers either swallow all errors or crash on recoverable conditions.
Why this severity: High because generic `Error` instances force consumers into string-parsing for error discrimination, producing fragile error handling that breaks on any message-text change.
sdk-package-quality.api-surface.error-classesSee full patternCode that executes at import time bypasses the consumer's control plane. A top-level `fetch()` fires before the consumer has configured anything; a top-level `console.log` pollutes test output and server logs; a `globalThis` mutation can conflict with other packages or the host environment. ISO 25010 compatibility and fault-tolerance both degrade when a package modifies global state the moment it's imported. In server-side rendering, side-effecting imports can crash the render pass. In test environments, they produce flaky behavior depending on import order.
Why this severity: Critical because side effects on import execute before the consumer opts in, enabling silent global mutations, unsolicited network calls, and SSR crashes that are invisible until production.
sdk-package-quality.api-surface.no-side-effectsSee full patternCallback-based async APIs are incompatible with `async/await`, require consumers to wrap calls in `new Promise()` to compose them, and produce inverted error-handling patterns compared to modern JavaScript. ISO 25010 interoperability degrades when your API cannot be composed with `Promise.all`, `Promise.race`, or `async` middleware chains. Callback-only APIs also make it harder to surface errors correctly — thrown errors inside callbacks are swallowed by the event loop if the callback doesn't explicitly handle them.
Why this severity: Medium because callback-only APIs block `async/await` usage and complicate error propagation, but do not cause immediate failures — they require consumers to add boilerplate wrapping.
sdk-package-quality.api-surface.async-promisesSee full patternGlobal `init()` or `configure()` functions that mutate module-level state break every scenario with multiple concurrent consumers: test suites that need different API keys per test, monorepo apps using the same package with different configurations, and multi-tenant servers handling requests with per-user credentials. ISO 25010 testability and modifiability both require that configuration be isolatable per instance. Module-level state also prevents the garbage collector from cleaning up instances and leaks across test runs.
Why this severity: Low because the limitation only surfaces when multiple configurations are required in one process — a constraint many simple consumer apps never hit — but the fix is cheap and the pattern is easily corrected.
sdk-package-quality.api-surface.config-patternSee full patternAn API that returns `Promise<any>` for network requests or data operations forces every TypeScript consumer to cast results manually — `const users = await client.get('/users') as User[]`. This eliminates compile-time type checking at exactly the boundary where runtime errors are most common. ISO 25010 modifiability suffers because any change to the response shape goes undetected until runtime. The consumer cannot trust the type system to catch contract mismatches between their models and your API's actual output.
Why this severity: Low because the impact is limited to TypeScript consumers and does not cause runtime failures — the type ergonomics are poor but the functionality remains intact.
sdk-package-quality.api-surface.type-genericsSee full patternA CJS-only package blocks ESM consumers entirely — they cannot use static `import` syntax and must resort to dynamic `import()` or `createRequire` workarounds. ESM-only packages break all CommonJS consumers (many Node.js scripts, Jest without experimental VM modules, older tools). ISO 25010 interoperability requires supporting both resolution systems until the ecosystem fully migrates. SSDF PS.3.2 flags format mismatches as a distribution integrity concern: the format published must match what consumers expect given the `type` field and `exports` conditions.
Why this severity: High because CJS-only packages are incompatible with ESM-native bundlers and Node.js projects using `type: "module"`, and ESM-only packages break all CJS consumers.
sdk-package-quality.build-distribution.dual-formatSee full patternEvery runtime dependency you ship is inherited by every consumer. `lodash` (full) adds ~72KB minified; `moment` adds ~230KB. A focused SDK with full `lodash` as a runtime dependency imposes that weight on every browser bundle that installs it — directly increasing page load times and Lighthouse performance scores for users who never touch the offending methods. ISO 25010 resource-utilization flags this; SLSA L1 supply chain concerns apply because each runtime dependency is an additional attack surface. CWE-1357 covers use of unnecessarily broad software components.
Why this severity: High because bloated runtime dependencies inflate every consumer's install size and bundle weight, and each transitive dependency expands the supply chain attack surface.
sdk-package-quality.build-distribution.bundle-sizeSee full patternWithout `"sideEffects": false` in `package.json`, webpack and Rollup conservatively assume every module in your package has side effects and include all of it in every consumer bundle. Even if the consumer only calls one function, they get the entire package. ISO 25010 resource-utilization is directly impacted: consumers pay the full bundle cost regardless of actual usage. CJS-only output compounds this — tree-shaking is an ESM feature, so CJS packages cannot be tree-shaken at all, regardless of the `sideEffects` declaration.
Why this severity: High because the absence of `sideEffects: false` causes all major bundlers to include the entire package in consumer builds, even when only a fraction of the API is used.
sdk-package-quality.build-distribution.tree-shakingSee full patternWhen a consumer encounters a bug that originates inside your package, their stack trace shows transpiled or minified output — line numbers and variable names from `dist/index.cjs` line 1 that bear no resemblance to the original source. Without source maps, debugging requires manually cloning your repo, matching the version, and mapping the call site. ISO 25010 analysability degrades directly: the time-to-understand a stack trace goes from seconds to minutes.
Why this severity: Low because source maps affect debugging ergonomics only — the absence does not cause runtime failures, and determined consumers can still diagnose issues by reading the compiled output.
sdk-package-quality.build-distribution.source-mapsSee full patternA missing `prepublishOnly` script means a developer can run `npm publish` against stale or missing `dist/` output — shipping a broken release to every consumer before anyone notices. ISO 25010 fault-tolerance requires that the release process itself catch this class of error. SLSA L1 requires that the build is reproducible and that the published artifact reflects the declared source version. A manual build-then-publish workflow is an invitation for version skew between source and registry.
Why this severity: Medium because the gap between build and publish is a procedural failure mode that produces broken releases — not a security issue, but a reliability one that affects all consumers of the broken version.
sdk-package-quality.build-distribution.build-scriptSee full patternEvery dependency in `dependencies` is installed by every consumer, even in production. TypeScript, `@types/*`, test runners, and build tools in `dependencies` instead of `devDependencies` inflate the install footprint of every project that depends on yours. `node-fetch` as a runtime dependency in a package that declares `engines: { node: '>=18' }` installs a redundant polyfill of a built-in. CWE-1357 covers use of unnecessarily broad components; SLSA L1 and SSDF PO.3.2 both require that the published artifact's dependency tree is intentional and minimal.
Why this severity: Medium because misplaced or unnecessary runtime dependencies increase install size and supply chain surface area for all consumers, without providing any functional benefit.
sdk-package-quality.build-distribution.minimal-depsSee full patternA README without install command, import statement, and a working usage example forces every prospective consumer to read source code or issue trackers before they can evaluate the package. Adoption stalls at the discovery stage: npm page visitors bounce, stars never convert to weekly downloads, and support channels flood with questions the quickstart should answer. For SDKs, this directly undermines user-experience and content-integrity — the two taxons this pattern is tagged under — because the package's public contract is invisible at the moment of evaluation.
Why this severity: High because a missing or boilerplate README blocks nearly every downstream adoption decision and multiplies support load.
sdk-package-quality.docs-maintenance.readme-quickstartSee full patternConsumers upgrading from `v1.x` to `v2.x` need to know what broke and what migrated. Without a `CHANGELOG.md`, they must diff the entire package history, read all commit messages, or trial-upgrade hoping nothing explodes. ISO 25010 modifiability requires that the system's evolution be trackable. A changelog is also a trust signal: maintained packages have changelogs; abandoned packages don't. For packages consumed in regulated or enterprise environments, documented version history is often a procurement requirement.
Why this severity: High because consumers cannot safely assess upgrade risk without documented version history, leading to either blocked upgrades (security debt) or blind upgrades (breakage risk).
sdk-package-quality.docs-maintenance.changelogSee full patternA package stuck at `version: 1.0.0` (the `npm init` default) with no changelog and no git tags has never been intentionally versioned. Consumers cannot tell whether `1.0.0` is stable or a placeholder, and cannot specify version constraints in their `package.json` with any confidence. Worse, breaking API changes shipped without a major bump silently break consumers on `npm update` — the canonical semver violation. ISO 25010 modifiability requires that the package's evolution be communicated through its version number.
Why this severity: High because breaking changes without major version bumps silently break consumer projects on routine dependency updates, with no warning from the version range they specified.
sdk-package-quality.docs-maintenance.semverSee full patternTypeScript types tell consumers what parameters exist; JSDoc tells them what those parameters mean, when to use the function, and what edge cases to watch for. A package with zero API documentation forces every consumer to read your source code or experiment by trial and error. ISO 25010 analysability is the directly impacted quality attribute: the time to understand and correctly use your API is the measure. For SDK packages that wrap external services, undocumented error conditions and edge cases are the primary source of consumer integration bugs.
Why this severity: Low because missing API docs increase integration time and error rates but do not cause runtime failures — the package functions; it's just harder to use correctly.
sdk-package-quality.docs-maintenance.api-referenceSee full patternThe npm package page is often the first place a consumer lands after `npm install`. Without a `repository` field, the page has no link to the source code — consumers cannot report bugs, read the source to understand behavior, or check if the project is actively maintained. ISO 25010 analysability requires that the package's provenance be traceable. A missing `bugs` URL means issues go unreported or are sent to the wrong place. These fields take 60 seconds to add and have zero downside.
Why this severity: Low because missing repository metadata degrades discoverability and issue-reporting ergonomics but has no impact on package functionality or security.
sdk-package-quality.docs-maintenance.repo-fieldsSee full patternAn empty or missing `keywords` array in `package.json` removes the package from npm's keyword-indexed search results, so developers looking for an SDK in your domain will never surface it through the registry's discovery surface. Generic keywords like `javascript` or `node` are functionally equivalent to no keywords — they collide with hundreds of thousands of unrelated packages. This failure maps directly to the `findability` taxon and directly suppresses organic installs, GitHub traffic, and ecosystem integrations that depend on npm search.
Why this severity: Info because the package still installs and functions correctly; only discovery is degraded, not runtime behavior or security.
sdk-package-quality.docs-maintenance.keywordsSee full patternRun this audit in your AI coding tool (Claude Code, Cursor, Bolt, etc.) and submit results here for scoring and benchmarks.
Open SDK / Package Quality Audit