Test Yourself!

These ten questions run the length of the chapter, and each turns on a behavior that catches experienced developers: caret ranges below 1.0, hoisting, what npm 2,036 ci really checks, configuration precedence, registry metadata, OS package removal, two Node.js 2,131 binaries in one image, default ranges in other ecosystems, Java's version mediation and pinned script approvals. Every snippet ran on this chapter's WSL2 6 Ubuntu 26.04 225 host with the tool versions used throughout (npm 11.19.0, pnpm 12.6.0 69,400 , uv 0.12.19 90,229 , Cargo 1.98.1 6,602 , Maven 3.9.16 129 , Gradle 9.7.1 19,597 ), in an empty ~/v5-ch1/ty folder. Read each numbered block, write down what it prints and why, and only then check Appendix H, which gives the real output, the reason and the section it comes from. Several questions leave files or state behind, so run them in order, each block in a new shell.

Setup

Question 9 compares two Java projects that declare the same two dependencies, one for each build tool.

Setting up Question 9's Maven and Gradle projectsShell
mkdir -p ~/v5-ch1/ty/m9 ~/v5-ch1/ty/g9 && cd ~/v5-ch1/ty
cat > m9/pom.xml <<'EOF'
<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.booknest</groupId><artifactId>q9</artifactId><version>1.0.0</version>
  <dependencies>
    <dependency>
      <groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>33.7.1-jre</version>
    </dependency>
    <dependency>
      <groupId>com.google.errorprone</groupId><artifactId>error_prone_annotations</artifactId>
      <version>2.40.0</version>
    </dependency>
  </dependencies>
</project>
EOF
echo 'rootProject.name = "q9"' > g9/settings.gradle.kts
cat > g9/build.gradle.kts <<'EOF'
plugins { java }
repositories { mavenCentral() }
dependencies {
  implementation("com.google.guava:guava:33.7.1-jre")
  implementation("com.google.errorprone:error_prone_annotations:2.40.0")
}
EOF

Questions

Questions 1-4: ranges, layouts, npm ci and configurationShell
# 1. Which versions does each range accept? (Section 1.9.1)
npx -y semver -r '^0.2.3' 0.2.4 0.3.0 1.0.0
npx -y semver -r '^1.2.3-beta.1' 1.2.3-beta.2 1.2.4-beta.1 1.2.4 2.0.0
# 2. express 5.2.1 depends on debug. Can your own code require it? (Sections 1.4.2 and 1.9.2)
mkdir -p ~/v5-ch1/ty/n ~/v5-ch1/ty/p
cd ~/v5-ch1/ty/n && npm init -y >/dev/null && npm install -s express@5.2.1
node -e 'require("debug"); console.log("npm: ok")'
cd ~/v5-ch1/ty/p && echo '{"name":"p"}' > package.json && pnpm add -s express@5.2.1 >/dev/null
node -e 'require("debug"); console.log("pnpm: ok")' 2>&1 | grep -m1 -E 'Error|ok'
# 3. Narrow a range the lockfile still satisfies, then one it cannot. (Sections 1.2.3, 1.10.4)
mkdir ~/v5-ch1/ty/ci && cd ~/v5-ch1/ty/ci && cp ~/v5-ch1/booknest/package*.json .
npm pkg set dependencies.pg='^8.20.0' && npm ci -s; echo "exit $?"
jq -r '.packages[""].dependencies.pg' package-lock.json
npm pkg set dependencies.pg='^8.24.0' && npm ci -s 2>/dev/null; echo "exit $?"
# 4. The project .npmrc, an environment variable and a flag disagree. (Section 1.10.1)
mkdir ~/v5-ch1/ty/rc && cd ~/v5-ch1/ty/rc && npm init -y >/dev/null
echo 'registry=http://localhost:31873/' > .npmrc && npm config get registry
export npm_config_registry=https://registry.npmmirror.com/ && npm config get registry
npm config get registry --registry=https://example.com/ && unset npm_config_registry
Questions 5-7: registry metadata and OS package managersShell
# 5. What does the install-time metadata say about bcrypt 6.0.0's scripts? (Sections 1.10.1, 1.15.5)
curl -s -H 'Accept: application/vnd.npm.install-v1+json' https://registry.npmjs.org/bcrypt \
  | jq -c '.versions["6.0.0"] | {scripts, hasInstallScript}'
# 6. Install jq, remove it again and count the packages. (Sections 1.11.1 and 1.11.2)
docker run --rm --name l1-q6a ubuntu:26.04 bash -c 'apt-get update -qq; n=$(dpkg -l | grep -c ^ii)
  apt-get install -y -qq --no-install-recommends jq >/dev/null 2>&1
  apt-get remove -y -qq jq >/dev/null 2>&1; echo "ubuntu: $n -> $(dpkg -l | grep -c ^ii)"
  apt-get autoremove -y -qq >/dev/null 2>&1; echo "after autoremove: $(dpkg -l | grep -c ^ii)"'
docker run --rm --name l1-q6b alpine:3.24 sh -c 'n=$(apk info | wc -l)
  apk add -q --no-cache jq; apk del -q jq; echo "alpine: $n -> $(apk info | wc -l)"'
# 7. Add Alpine's own Node.js to the official Node.js image. Which node runs? (Section 1.11.2)
docker run --rm --name l1-q7 node:24-alpine sh -c \
  'apk add -q --no-cache nodejs >/dev/null 2>&1; node --version; which -a node'
Questions 8-10: other ecosystems and install-script approvalShell
# 8. What range does each tool write for a new dependency? (Sections 1.14.1 and 1.14.2)
cd ~/v5-ch1/ty && mkdir r8 && cd r8 && npm init -y >/dev/null && npm install -s ms
jq -r .dependencies.ms package.json
cd ~/v5-ch1/ty && uv init -q u8 && cd u8 && uv add -q requests && grep requests pyproject.toml
cd ~/v5-ch1/ty && cargo new -q c8 && cd c8 && cargo add -q serde_json && grep serde_json Cargo.toml
# 9. guava needs error_prone_annotations 2.50.0; you declare 2.40.0 yourself. (Section 1.14.3)
cd ~/v5-ch1/ty/m9 && mvn -B -q dependency:tree -DoutputFile=tree.txt && grep error_prone tree.txt
cd ~/v5-ch1/ty/g9 && gradle -q dependencies --configuration runtimeClasspath | grep error_prone
# 10. You approved 1.99.0's postinstall; now 1.99.1 is out. What happens? (Section 1.15.5)
cd ~/v5-ch1/supply/consumer && N=~/v5-ch1/tools/npm12/node_modules/.bin/npm
jq -c '{dependencies, allowScripts}' package.json
$N update --foreground-scripts --no-audit --no-fund 2>&1 | grep -E 'changed|blocked|postinstall\]'