Write the publish manifest

package.json tells Node.js what users may import and tells pnpm what to install, pack, and publish.

A publishable SDK manifest

{
  "name": "@your-scope/quiz-sdk",
  "version": "0.1.0",
  "description": "A typed SDK for fetching validated quiz questions.",
  "license": "Apache-2.0",
  "private": true,
  "type": "module",
  "exports": "./src/index.ts",
  "scripts": {
    "build": "tsc -p tsconfig.build.json",
    "prepack": "pnpm build"
  },
  "files": [
    "dist",
    "src/index.ts"
  ],
  "publishConfig": {
    "access": "public",
    "types": "./dist/index.d.ts",
    "exports": {
      ".": {
        "types": "./dist/index.d.ts",
        "import": "./dist/index.js"
      }
    }
  },
  "dependencies": {
    "@your-scope/quiz-contracts": "workspace:*",
    "arktype": "^2.2.3"
  },
  "devDependencies": {
    "@your-scope/quiz-api": "workspace:*"
  }
}

Keep private: true during development. Remove it after every release check passes. The rest of this lesson explains why each field exists.

Identity and legal metadata

name
The permanent registry identity. A scoped name belongs to an npm user or organization.
version
The immutable release identifier. npm will not let you replace the same name and version later.
description
Helps users understand search results and the package page.
license
Names the license using an SPDX identifier. Include the full matching text in LICENSE.
repository, homepage, and bugs
Add real URLs after the repository exists. In a monorepo, include the package directory in the repository object.
"repository": {
  "type": "git",
  "url": "git+https://github.com/your-name/quiz-sdk.git",
  "directory": "packages/sdk"
},
"homepage": "https://github.com/your-name/quiz-sdk#readme",
"bugs": {
  "url": "https://github.com/your-name/quiz-sdk/issues"
}

Do not invent repository URLs. Incorrect metadata breaks provenance and sends users to the wrong project.

Module format and public entry point

type: "module" tells Node.js to treat .js files as ES modules. The exports map defines which files consumers can import.

This workspace uses source during local development:

"exports": "./src/index.ts"

pnpm applies the publish-only override before packing:

"publishConfig": {
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  }
}

Users receive JavaScript and declarations from dist. They do not execute raw TypeScript. The exports map also blocks unsupported deep imports.

ESM-only is a compatibility choice

The import condition does not promise CommonJS require support. Add a tested require build only when consumers need it. Claiming both formats without testing both is worse than publishing ESM-only clearly.

Allow only intended files

"files": [
  "dist",
  "src/index.ts"
]

The files array is an allowlist. Tests, local configuration, and unrelated source stay out. npm always includes package.json, README, and LICENSE when present.

Keeping source alongside source maps improves debugging and editor navigation. For a small SDK, the size cost is minor. Large packages should measure before deciding.

Classify dependencies by who needs them

dependencies
Packages imported by published runtime JavaScript. Consumers install them automatically.
devDependencies
Tools and private packages needed only to develop, test, or build. Consumers do not install them as part of your package.
peerDependencies
Host libraries that the consumer must provide so your package shares the same instance, often frameworks or plugins. Do not use peers merely to reduce install size.
optionalDependencies
Runtime features that can be absent without making the package unusable. Code must handle their absence.

If emitted JavaScript contains import "arktype", ArkType belongs in dependencies. A successful local workspace build can hide a wrong classification because root dependencies are nearby.

Prevent stale build output

"scripts": {
  "build": "tsc -p tsconfig.build.json",
  "prepack": "pnpm build"
}

pnpm pack does not run an ordinary script named build. It does run the special prepack lifecycle. This guarantees that every tarball contains output generated from current source.

Write the files humans need

The package-root README.md should show installation, the smallest working call, configuration, important return types, and expected errors. npm renders it on the package page.

Choose a license intentionally. MIT is short and permissive. Apache-2.0 is also permissive and includes an explicit patent grant. License selection can have legal consequences, so organizations should follow their legal policy.

Official references

Check your understanding

  1. Why is ArkType a dependency while Vitest is a development dependency?

    Show answer

    Published JavaScript runs ArkType in consumer projects. Vitest runs only while maintainers test the package.

  2. Why keep private: true until release checks finish?

    Show answer

    It turns an accidental publish command into a local error. Remove it only from packages you have deliberately prepared for publication.