MCPcopy Create free account
hub / github.com/angular/dev-infra / invokeNvmInstall

Function invokeNvmInstall

ng-dev/release/publish/external-commands.ts:205–291  ·  view source on GitHub ↗
(projectDir: string, quiet = false)

Source from the content-addressed store, hash-verified

203 static async invokeNvmInstall(projectDir: string): Promise<void>;
204 static async invokeNvmInstall(projectDir: string, quiet: boolean): Promise<void>;
205 static async invokeNvmInstall(projectDir: string, quiet = false): Promise<void> {
206 if (!existsSync(join(projectDir, '.nvmrc'))) {
207 return;
208 }
209
210 try {
211 const nodeVersionFromNvmrc = readFileSync(join(projectDir, '.nvmrc'), 'utf8').trim();
212
213 // Validate .nvmrc content to prevent command injection/path hijacking
214 if (
215 nodeVersionFromNvmrc.includes('/') ||
216 nodeVersionFromNvmrc.includes('\\') ||
217 nodeVersionFromNvmrc.includes('..')
218 ) {
219 throw new Error('Invalid .nvmrc content: contains path traversal characters');
220 }
221
222 const versionPattern = /^[v0-9.-]+$/;
223 const isValidSemverRange = semver.validRange(nodeVersionFromNvmrc) !== null;
224 if (!versionPattern.test(nodeVersionFromNvmrc) && !isValidSemverRange) {
225 throw new Error('Invalid .nvmrc content: does not match valid version pattern');
226 }
227
228 // We must source nvm.sh so the shell recognizes the 'nvm' command since nvm is not a binary
229 // but a shell function. The dot (.) built-in and && operator require shell: true here.
230 // We redirect stdout of nvm install to stderr so that stdout only contains the result of nvm which.
231 const {stdout} = await ChildProcess.spawn(
232 '. ~/.nvm/nvm.sh && nvm install >&2 && nvm which --silent',
233 [],
234 {
235 cwd: projectDir,
236 mode: 'on-error',
237 shell: true,
238 },
239 );
240
241 const nodeBinPath = stdout.trim();
242 if (nodeBinPath) {
243 const homeDir = os.homedir();
244 const absoluteNodeBinPath = resolve(nodeBinPath);
245 const absoluteProjectDir = resolve(projectDir);
246
247 if (!absoluteNodeBinPath.startsWith(homeDir)) {
248 throw new Error(
249 `Security Error: Resolved Node.js path is outside the home directory: ${nodeBinPath}`,
250 );
251 }
252 if (absoluteNodeBinPath.startsWith(absoluteProjectDir)) {
253 throw new Error(
254 `Security Error: Resolved Node.js path is inside the project directory: ${nodeBinPath}`,
255 );
256 }
257
258 const nodeDir = dirname(absoluteNodeBinPath);
259 const currentPath = process.env['PATH'] || '';
260 const pathParts = currentPath.split(':');
261
262 // Only update if the requested node directory is not already the first entry in PATH.

Callers

nothing calls this directly

Calls 2

resolveFunction · 0.85
errorMethod · 0.45

Tested by

no test coverage detected