Skip to content
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ same major line. Should you need to upgrade to a new major, use an explicit
set to `1` to have the URL shown. By default, when Corepack is called
explicitly (e.g. `corepack pnpm …`), it is set to `0`; when Corepack is called
implicitly (e.g. `pnpm …`), it is set to `1`.
The default value cannot be overridden in a `.corepack.env` file.
When standard input is a TTY and no CI environment is detected, Corepack will
ask for user input before starting the download.

Expand All @@ -292,6 +293,14 @@ same major line. Should you need to upgrade to a new major, use an explicit
project. This means that it will always use the system-wide package manager
regardless of what is being specified in the project's `packageManager` field.

- `COREPACK_ENV_FILE` can be set to `0` to request Corepack to not attempt to
load `.corepack.env`; it can be set to a path to specify a different env file.
Only keys that start with `COREPACK_` will be taken into account, not all
keys that start with `COREPACK_` will be taken into account (
`COREPACK_ENABLE_DOWNLOAD_PROMPT` and `COREPACK_ENV_FILE` are ignored).
For Node.js 18.x users, this setting has no effect as that version doesn't
support parsing of `.env` files.

- `COREPACK_HOME` can be set in order to define where Corepack should install
the package managers. By default it is set to `%LOCALAPPDATA%\node\corepack`
on Windows, and to `$HOME/.cache/node/corepack` everywhere else.
Expand Down
2 changes: 2 additions & 0 deletions sources/npmRegistryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export function verifySignature({signatures, integrity, packageName, version}: {
packageName: string;
version: string;
}) {
if (signatures == null) throw new Error(`No compatible signature found in package metadata for ${packageName}@${version}`);

const {npm: keys} = process.env.COREPACK_INTEGRITY_KEYS ?
JSON.parse(process.env.COREPACK_INTEGRITY_KEYS) as typeof defaultConfig.keys :
defaultConfig.keys;
Expand Down
64 changes: 52 additions & 12 deletions sources/specUtils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {UsageError} from 'clipanion';
import fs from 'fs';
import path from 'path';
import semverValid from 'semver/functions/valid';

import {PreparedPackageManagerInfo} from './Engine';
import * as debugUtils from './debugUtils';
import {NodeError} from './nodeUtils';
import * as nodeUtils from './nodeUtils';
import {Descriptor, isSupportedPackageManager} from './types';
import {UsageError} from 'clipanion';
import fs from 'fs';
import path from 'path';
import semverValid from 'semver/functions/valid';
import {parseEnv} from 'util';

import type {PreparedPackageManagerInfo} from './Engine';
import * as debugUtils from './debugUtils';
import type {NodeError} from './nodeUtils';
import * as nodeUtils from './nodeUtils';
import {isSupportedPackageManager} from './types';
import type {LocalEnvFile, Descriptor} from './types';

const nodeModulesRegExp = /[\\/]node_modules[\\/](@[^\\/]*[\\/])?([^@\\/][^\\/]*)$/;

Expand Down Expand Up @@ -72,10 +74,11 @@ export async function setLocalPackageManager(cwd: string, info: PreparedPackageM
};
}

type FoundSpecResult = {type: `Found`, target: string, spec: Descriptor, envFilePath?: string};
export type LoadSpecResult =
| {type: `NoProject`, target: string}
| {type: `NoSpec`, target: string}
| {type: `Found`, target: string, spec: Descriptor};
| FoundSpecResult;

export async function loadSpec(initialCwd: string): Promise<LoadSpecResult> {
let nextCwd = initialCwd;
Expand All @@ -84,6 +87,8 @@ export async function loadSpec(initialCwd: string): Promise<LoadSpecResult> {
let selection: {
data: any;
manifestPath: string;
envFilePath?: string;
localEnv: LocalEnvFile;
} | null = null;

while (nextCwd !== currCwd && (!selection || !selection.data.packageManager)) {
Expand Down Expand Up @@ -111,19 +116,54 @@ export async function loadSpec(initialCwd: string): Promise<LoadSpecResult> {
if (typeof data !== `object` || data === null)
throw new UsageError(`Invalid package.json in ${path.relative(initialCwd, manifestPath)}`);

selection = {data, manifestPath};
let localEnv: LocalEnvFile;
const envFilePath = path.resolve(currCwd, process.env.COREPACK_ENV_FILE ?? `.corepack.env`);
if (process.env.COREPACK_ENV_FILE == `0`) {
debugUtils.log(`Skipping env file as configured with COREPACK_ENV_FILE`);
localEnv = process.env;
} else if (typeof parseEnv !== `function`) {
// TODO: remove this block when support for Node.js 18.x is dropped.
debugUtils.log(`Skipping env file as it is not supported by the current version of Node.js`);
localEnv = process.env;
} else {
debugUtils.log(`Checking ${envFilePath}`);
try {
localEnv = {
...Object.fromEntries(Object.entries(parseEnv(await fs.promises.readFile(envFilePath, `utf8`))).filter(e => e[0].startsWith(`COREPACK_`))),
...process.env,
};
debugUtils.log(`Successfully loaded env file found at ${envFilePath}`);
} catch (err) {
if ((err as NodeError)?.code !== `ENOENT`)
throw err;

debugUtils.log(`No env file found at ${envFilePath}`);
localEnv = process.env;
}
}

selection = {data, manifestPath, localEnv, envFilePath};
}

if (selection === null)
return {type: `NoProject`, target: path.join(initialCwd, `package.json`)};

let envFilePath: string | undefined;
if (selection.localEnv !== process.env) {
envFilePath = selection.envFilePath;
process.env = selection.localEnv;
}

const rawPmSpec = selection.data.packageManager;
if (typeof rawPmSpec === `undefined`)
return {type: `NoSpec`, target: selection.manifestPath};

debugUtils.log(`${selection.manifestPath} defines ${rawPmSpec} as local package manager`);

return {
type: `Found`,
target: selection.manifestPath,
envFilePath,
spec: parseSpec(rawPmSpec, path.relative(initialCwd, selection.manifestPath)),
};
}
2 changes: 2 additions & 0 deletions sources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,5 @@ export interface LazyLocator {
*/
reference: () => Promise<string>;
}

export type LocalEnvFile = Record<string, string | undefined>;
Loading