Fast dependency installer for Node.js services in a monorepo.
nsc-fast-install analyzes the compiled JavaScript entrypoint of a service, finds the packages that are actually required at runtime, and copies only those packages into the service dist/node_modules.
The tool is intended for Docker and CI builds where copying the full root node_modules is too heavy.
In many monorepos dependencies are installed once in the repository root:
backend/
package.json
package-lock.json
node_modules/
services/
gate/
shared/
users/
This is convenient for development, but inefficient for service images. A service usually needs only a small part of the root dependency tree, while COPY node_modules puts everything into the image.
nsc-fast-install builds a smaller runtime node_modules for a specific service.
- Resolves the service runtime entrypoint.
- Reads
tsconfig.json, including JSONC syntax andextends. - Reads the compiled JavaScript file from
outDir. - Scans static
require()andimportdependencies. - Resolves packages using Node-compatible resolution, including
package.json#exports. - Expands transitive dependencies from
package-lock.json. - Recursively adds installed runtime
optionalDependenciesof copied npm packages, such as platform packages used by native modules. - Handles npm workspaces and copies workspace packages as real directories, not symlinks.
- Writes the result to
<outDir>/node_modules, to<output>/node_modules, or to the exact path passed with--nodeModulesOutput.
npm i @lad-tech/nsc-fast-install -DRun the tool from the monorepo root after the service has already been built:
npx nsc-fast-install --service services/gateOr pass an explicit entrypoint:
npx nsc-fast-install --entryPoint services/gate/start.tsBy default, dependencies are copied to:
services/gate/dist/node_modules
The tool analyzes compiled JavaScript, not TypeScript source. Build the service first:
npm run build --workspace services/gate
npx nsc-fast-install --service services/gateIf outDir or the compiled entrypoint does not exist, the command fails with a non-zero exit code.
For --service, the default strategy is runtime.
Runtime strategy checks source runtime files first:
start.tsservice.tsindex.tspackage.json#main
This is the right default for Docker/service builds where package.json#main may point to a library export such as dist/Api/index.js, while the real runtime entry is start.ts.
To prefer package.json#main, use:
npx nsc-fast-install --service services/gate --entryStrategy mainThe resolver supports Node-style package.json#exports, including package subpaths.
Examples that are resolved correctly:
import { SchemaBuilder } from 'shared/SchemaBuilder';
import { WorkspaceFilesClient } from 'shared/clients/WorkspaceFilesClient';
import { UPLOAD_STREAM_MAGIC } from 'shared/helpers/uploadProtocol';
import schema from 'shared/clients/WorkspaceFiles/service.schema.json';Supported export shapes include:
{
"name": "shared",
"exports": {
".": "./dist/index.js",
"./SchemaBuilder": "./dist/SchemaBuilder.js",
"./clients/*": "./dist/clients/*.js",
"./helpers/uploadProtocol": "./dist/helpers/uploadProtocol.js",
"./clients/*/service.schema.json": "./dist/clients/*/service.schema.json"
}
}The tool reads npm workspaces from the root package.json:
{
"workspaces": ["services/*"]
}Workspace packages are treated as valid dependencies even when they are not listed in the root dependencies.
For example, this is valid:
// services/gate/package.json
{
"name": "gate",
"dependencies": {
"shared": "workspace:*"
}
}When npm creates a workspace symlink:
node_modules/shared -> ../services/shared
nsc-fast-install dereferences it and copies the actual package directory into the target node_modules. The Docker context receives a normal directory instead of a symlink.
Workspace dependencies are also expanded recursively. If gate depends on shared, and shared depends on logger, both workspace packages are copied.
The missing dependency check uses:
- root
package.jsondependencies; - target service
package.jsondependencies; - workspace package names;
- dependencies of referenced workspace packages.
This means service-local workspace dependencies do not need to be duplicated in the root dependencies.
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
COPY services ./services
RUN npm ci
RUN npm run build --workspace services/gate
RUN npx nsc-fast-install --service services/gate
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/services/gate/dist ./dist
CMD ["node", "dist/start.js"]Adjust the final CMD to match your service output path.
| Option | Type | Default | Description |
|---|---|---|---|
--entryPoint <path> |
string |
- | Source or compiled entrypoint, for example services/gate/start.ts. |
--service <path> |
string |
- | Service directory, for example services/gate. |
--entryStrategy <runtime|main> |
string |
runtime |
Entrypoint strategy for --service. |
--output <path> |
string |
outDir |
Parent directory where node_modules will be created. Relative paths are resolved from the service directory. |
--nodeModulesOutput <path> |
string |
- | Exact target node_modules path. Cannot be combined with --output. |
--exclude <list> |
string |
frontend |
Comma-separated directory names to skip. |
--tsconfig <name> |
string |
tsconfig.json |
Service tsconfig filename. |
--skipOptionalRuntimeDeps |
boolean |
false |
Do not copy installed runtime optionalDependencies of selected npm packages. |
--dryRun |
boolean |
false |
Print dependencies without copying or deleting target node_modules. |
--json |
boolean |
false |
Print machine-readable dry-run JSON. Implies --dryRun. |
--verbose |
boolean |
false |
Print resolver and copy details. |
--version |
boolean |
- | Print package version. |
Use --dryRun to inspect what would be copied:
npx nsc-fast-install --service services/gate --dryRunDry-run mode does not remove or create the target node_modules.
For CI and Docker scripts, use JSON output:
npx nsc-fast-install --service services/gate --jsonThe JSON payload includes entrypoint, targetNodeModules, deps, and missing.
The npm package includes compiled JavaScript, source maps, generated declaration files, README, changelog, and the generated dist/package.json used by the CLI version command.
dist/package.json is created during build/prepack; it is not meant to be edited by hand.
- Node.js 22.14 or newer.
- Node.js project with
package-lock.json. - Compiled JavaScript output must exist before running the tool.
tsconfig.jsonmay use comments, trailing commas, andextends.- Static imports or requires must be present in the compiled output. Dynamic runtime-only imports cannot always be detected.
- npm workspaces are supported through the root
workspacesfield. - Runtime
optionalDependenciesare copied only when they are installed in the sourcenode_modules; missing optional packages for other platforms are ignored.
The service has not been built, or compilerOptions.outDir points to a different directory.
Build the service first.
The source entrypoint was found, but the matching JavaScript file was not found in outDir.
Check:
- whether the service build completed successfully;
compilerOptions.rootDir;- whether
package.json#mainpoints to a library entry instead of a runtime entry; - whether you need
--entryPointfor this service.
The compiled file contains an import that cannot be resolved from the monorepo root or from the importing file.
Check:
package.json#exports;- workspace symlinks in root
node_modules; baseUrland emitted JS paths;- whether the import exists in the compiled output.
A runtime package is used but is not declared in the root package, target service package, or workspace packages.
Add it to the package that owns the dependency.