Skip to content

npm install (no args) silently skips devDependencies #17

@davideast

Description

@davideast

Summary

When invoked with no package argument, npm install in almostnode resolves and installs only dependencies from package.json. The devDependencies are silently skipped — there is no warning, no flag, no --production involved.

Real npm installs both dependencies and devDependencies by default; only npm install --production (or NODE_ENV=production) drops devDeps.

Repro

import { createContainer } from "almostnode";

const c = createContainer();
c.vfs.mkdirSync("/work", { recursive: true });
c.vfs.writeFileSync("/work/package.json", JSON.stringify({
  name: "test",
  version: "0.0.0",
  dependencies: { "react": "^18.3.1" },
  devDependencies: { "vite": "^5.4.8" },
}, null, 2));

const r = await c.run("npm install", { cwd: "/work" });
console.log(r.stdout);
// Resolving react@^18.3.1 ... Installed 1 packages
// (vite is NOT downloaded)

console.log(c.vfs.existsSync("/work/node_modules/react"));  // true
console.log(c.vfs.existsSync("/work/node_modules/vite"));   // false  ← bug

Why this matters

Modern JS projects use devDependencies for build/test tooling (vite, webpack, tsc, vitest, etc.). When an agent (or human) follows the standard pattern — write package.json with framework runtime in dependencies and the build tool in devDependencies, then npm install — the project compiles but the dev/build command (node ./node_modules/vite/bin/vite.js, npm run dev, etc.) fails with Cannot find module. The user/agent then loops trying to figure out why.

This compounds badly with #16 (the util.styleText issue): when npm create vite fails for that reason, the human workaround is to write package.json by hand. That workaround relies on npm install actually installing devDeps. With this bug, it doesn't.

Suspected cause

Looking at src/npm/index.ts installFromPackageJson:

async installFromPackageJson(options: InstallOptions = {}): Promise<InstallResult> {
  ...
  // Resolve all dependencies
  const resolved = await resolveDependencies(name, version, {
    registry: this.registry,
    includeDev: options.includeDev,       // ← undefined by default
    includeOptional: options.includeOptional,
    onProgress,
  });
  ...
}

And the npm shell command (src/shims/child_process.ts, the handleNpmInstall path) presumably calls installFromPackageJson({}) with no options, so includeDev defaults to undefined/false.

Suggested fix

In src/shims/child_process.ts, the npm install (no-args) path should set includeDev: true to match real npm's default behavior. Optionally also add --production / --omit=dev flag parsing to match npm's actual contract (NODE_ENV=production → omit dev; --omit=dev → omit dev).

// before:
return packageManager.installFromPackageJson({ onProgress });

// after:
const isProd = process.env.NODE_ENV === 'production' || hasFlag('--omit=dev') || hasFlag('--production');
return packageManager.installFromPackageJson({
  onProgress,
  includeDev: !isProd,
});

Environment

  • almostnode 0.2.14
  • mode: createContainer() (trusted main-thread)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions