mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-11 02:29:48 -04:00
* refactor(config): split Config interface into settings + runtime context
Create ConfigContext for runtime state (hooks, finders, workspace graph,
CLI metadata) and keep Config for user-facing settings only. Functions
use Pick<Config, ...> & Pick<ConfigContext, ...> to express which fields
they need from each interface.
getConfig() now returns { config, context, warnings }. The CLI wrapper
returns { config, context } and spreads both when calling command
handlers (to be refactored to separate params in follow-up PRs).
Closes #11195
* fix: address review feedback
- Initialize cliOptions on pnpmConfig so context.cliOptions is never undefined
- Move rootProjectManifestDir assignment before ignoreLocalSettings guard
- Add allProjectsGraph to INTERNAL_CONFIG_KEYS
* refactor: remove INTERNAL_CONFIG_KEYS from configToRecord
configToRecord now accepts Config and ConfigContext separately, so
context fields are never in scope. Only auth-related Config fields
(authConfig, authInfos, sslConfigs) need filtering.
* refactor: eliminate INTERNAL_CONFIG_KEYS from configToRecord
configToRecord now receives the clean Config object and explicitlySetKeys
separately (via opts.config and opts.context), so context fields are
never in scope. main.ts passes the original split objects alongside
the spread for command handlers that need them.
* fix: spelling
* fix: import sorting
* fix: --config.xxx nconf overrides conflicting with --config CLI flag
When `pnpm add` registers `config: Boolean`, nopt captures
--config.xxx=yyy as the --config flag value instead of treating it
as a nconf-style config override. Fix by extracting --config.xxx args
before nopt parsing and re-parsing them separately.
Also rename the split config/context properties on the command opts
object to _config/_context to avoid clashing with the --config CLI option.
1178 lines
31 KiB
TypeScript
1178 lines
31 KiB
TypeScript
/// <reference path="../../../__typings__/index.d.ts"/>
|
|
import path from 'node:path'
|
|
|
|
import { toOutput$ } from '@pnpm/cli.default-reporter'
|
|
import type { Config, ConfigContext } from '@pnpm/config.reader'
|
|
import {
|
|
deprecationLogger,
|
|
hookLogger,
|
|
packageManifestLogger,
|
|
peerDependencyIssuesLogger,
|
|
rootLogger,
|
|
skippedOptionalDependencyLogger,
|
|
statsLogger,
|
|
summaryLogger,
|
|
} from '@pnpm/core-loggers'
|
|
import { PnpmError } from '@pnpm/error'
|
|
import {
|
|
createStreamParser,
|
|
logger,
|
|
} from '@pnpm/logger'
|
|
import chalk from 'chalk'
|
|
import normalizeNewline from 'normalize-newline'
|
|
import { repeat } from 'ramda'
|
|
import { firstValueFrom } from 'rxjs'
|
|
import { map, skip, take } from 'rxjs/operators'
|
|
|
|
import { formatWarn } from '../src/reporterForClient/utils/formatWarn.js'
|
|
|
|
const formatErrorCode = (code: string) => chalk.bgRed.black(`\u2009${code}\u2009`)
|
|
const formatError = (code: string, message: string) => {
|
|
return `${formatErrorCode(code)} ${chalk.red(message)}`
|
|
}
|
|
const DEPRECATED = chalk.red('deprecated')
|
|
const versionColor = chalk.grey
|
|
const ADD = chalk.green('+')
|
|
const SUB = chalk.red('-')
|
|
const h1 = chalk.cyanBright
|
|
|
|
const EOL = '\n'
|
|
|
|
test('prints summary (of current package only)', async () => {
|
|
const prefix = '/home/jane/project'
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: { dir: prefix } as Config & ConfigContext,
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
statsLogger.debug({ added: 5, prefix: `${prefix}/packages/foo` })
|
|
statsLogger.debug({ removed: 1, prefix: `${prefix}/packages/foo` })
|
|
packageManifestLogger.debug({
|
|
initial: {
|
|
name: 'foo',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
'is-13': '^1.0.0',
|
|
},
|
|
devDependencies: {
|
|
'is-negative': '^1.0.0',
|
|
},
|
|
},
|
|
prefix,
|
|
})
|
|
deprecationLogger.debug({
|
|
deprecated: 'This package was deprecated because bla bla bla',
|
|
depth: 0,
|
|
pkgId: 'registry.npmjs.org/bar/2.0.0',
|
|
pkgName: 'bar',
|
|
pkgVersion: '2.0.0',
|
|
prefix,
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'prod',
|
|
id: 'registry.npmjs.org/foo/1.0.0',
|
|
latest: '2.0.0',
|
|
name: 'foo',
|
|
realName: 'foo',
|
|
version: '1.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'prod',
|
|
id: 'registry.npmjs.org/bar/2.0.0',
|
|
latest: '1.0.0', // this won't be printed in summary because latest is less than current version
|
|
name: 'bar',
|
|
realName: 'bar',
|
|
version: '2.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
rootLogger.debug({
|
|
prefix,
|
|
removed: {
|
|
dependencyType: 'prod',
|
|
name: 'foo',
|
|
version: '0.1.0',
|
|
},
|
|
})
|
|
rootLogger.debug({
|
|
prefix,
|
|
removed: {
|
|
dependencyType: 'prod',
|
|
name: 'no-changes',
|
|
version: '1.0.0',
|
|
},
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'prod',
|
|
id: 'registry.npmjs.org/no-changes/2.0.0',
|
|
name: 'no-changes',
|
|
realName: 'no-changes',
|
|
version: '1.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'dev',
|
|
id: 'registry.npmjs.org/qar/2.0.0',
|
|
name: 'qar',
|
|
realName: 'qar',
|
|
version: '2.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
// This log is going to be ignored because it is not in the current prefix
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'optional',
|
|
id: 'registry.npmjs.org/lala/2.0.0',
|
|
name: 'lala',
|
|
realName: 'lala',
|
|
version: '2.0.0',
|
|
},
|
|
prefix: `${prefix}/packages/foo`,
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'optional',
|
|
id: 'registry.npmjs.org/lala/1.1.0',
|
|
name: 'lala',
|
|
realName: 'lala',
|
|
version: '1.1.0',
|
|
},
|
|
prefix,
|
|
})
|
|
rootLogger.debug({
|
|
prefix,
|
|
removed: {
|
|
dependencyType: 'optional',
|
|
name: 'is-positive',
|
|
},
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'optional',
|
|
linkedFrom: '/src/is-linked',
|
|
name: 'is-linked',
|
|
realName: 'is-linked',
|
|
},
|
|
prefix,
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'prod',
|
|
id: 'registry.npmjs.org/winst0n/2.0.0',
|
|
latest: '1.0.0',
|
|
name: 'winston',
|
|
realName: 'winst0n',
|
|
version: '1.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
packageManifestLogger.debug({
|
|
prefix,
|
|
updated: {
|
|
dependencies: {
|
|
'is-negative': '^1.0.0',
|
|
},
|
|
devDependencies: {
|
|
'is-13': '^1.0.0',
|
|
},
|
|
},
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
linkedFrom: '/src/is-linked2',
|
|
name: 'is-linked2',
|
|
realName: 'is-linked2',
|
|
},
|
|
prefix,
|
|
})
|
|
summaryLogger.debug({ prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(skip(2), take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`packages/foo | ${chalk.green('+5')} ${chalk.red('-1')} ${ADD + SUB}${EOL}` +
|
|
`${formatWarn(`${DEPRECATED} bar@2.0.0: This package was deprecated because bla bla bla`)}${EOL}${EOL}` +
|
|
`\
|
|
${h1('dependencies:')}
|
|
${ADD} bar ${versionColor('2.0.0')} ${DEPRECATED}
|
|
${SUB} foo ${versionColor('0.1.0')}
|
|
${ADD} foo ${versionColor('1.0.0')} ${versionColor('(2.0.0 is available)')}
|
|
${SUB} is-13 ${versionColor('^1.0.0')}
|
|
${ADD} is-negative ${versionColor('^1.0.0')}
|
|
${ADD} winston <- winst0n ${versionColor('1.0.0')}
|
|
|
|
${h1('optionalDependencies:')}
|
|
${ADD} is-linked ${chalk.grey(`<- ${path.relative(prefix, '/src/is-linked')}`)}
|
|
${SUB} is-positive
|
|
${ADD} lala ${versionColor('1.1.0')}
|
|
|
|
${h1('devDependencies:')}
|
|
${ADD} is-13 ${versionColor('^1.0.0')}
|
|
${SUB} is-negative ${versionColor('^1.0.0')}
|
|
${ADD} qar ${versionColor('2.0.0')}
|
|
|
|
${h1('node_modules:')}
|
|
${ADD} is-linked2 ${chalk.grey(`<- ${path.relative(prefix, '/src/is-linked2')}`)}
|
|
`)
|
|
})
|
|
|
|
test('prints summary without the filtered out entries', async () => {
|
|
const prefix = '/home/jane/project'
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: {
|
|
dir: prefix,
|
|
} as Config & ConfigContext,
|
|
},
|
|
streamParser: createStreamParser(),
|
|
filterPkgsDiff: (diff) => diff.name !== 'bar',
|
|
})
|
|
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'prod',
|
|
id: 'registry.npmjs.org/foo/1.0.0',
|
|
latest: '2.0.0',
|
|
name: 'foo',
|
|
realName: 'foo',
|
|
version: '1.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'prod',
|
|
id: 'registry.npmjs.org/bar/2.0.0',
|
|
latest: '1.0.0', // this won't be printed in summary because latest is less than current version
|
|
name: 'bar',
|
|
realName: 'bar',
|
|
version: '2.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'dev',
|
|
id: 'registry.npmjs.org/qar/2.0.0',
|
|
latest: '1.0.0', // this won't be printed in summary because latest is less than current version
|
|
name: 'qar',
|
|
realName: 'qar',
|
|
version: '2.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
packageManifestLogger.debug({
|
|
prefix,
|
|
updated: {
|
|
dependencies: {
|
|
'is-negative': '^1.0.0',
|
|
},
|
|
devDependencies: {
|
|
'is-13': '^1.0.0',
|
|
},
|
|
},
|
|
})
|
|
summaryLogger.debug({ prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(EOL + `\
|
|
${h1('dependencies:')}
|
|
${ADD} foo ${versionColor('1.0.0')} ${versionColor('(2.0.0 is available)')}
|
|
|
|
${h1('devDependencies:')}
|
|
${ADD} qar ${versionColor('2.0.0')}
|
|
`)
|
|
})
|
|
|
|
test('does not print deprecation message when log level is set to error', async () => {
|
|
const prefix = '/home/jane/project'
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: { dir: prefix } as Config & ConfigContext,
|
|
},
|
|
reportingOptions: {
|
|
logLevel: 'error',
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
peerDependencyIssuesLogger.debug({
|
|
issuesByProjects: {
|
|
'.': {
|
|
missing: {},
|
|
bad: {
|
|
a: [
|
|
{
|
|
parents: [
|
|
{
|
|
name: 'b',
|
|
version: '1.0.0',
|
|
},
|
|
],
|
|
foundVersion: '2',
|
|
resolvedFrom: [],
|
|
optional: false,
|
|
wantedRange: '3',
|
|
},
|
|
],
|
|
},
|
|
conflicts: [],
|
|
intersections: {},
|
|
},
|
|
},
|
|
})
|
|
deprecationLogger.debug({
|
|
deprecated: 'This package was deprecated because bla bla bla',
|
|
depth: 0,
|
|
pkgId: 'registry.npmjs.org/bar/2.0.0',
|
|
pkgName: 'bar',
|
|
pkgVersion: '2.0.0',
|
|
prefix,
|
|
})
|
|
const err = new PnpmError('SOME_CODE', 'some error')
|
|
logger.error(err, err)
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(formatError('ERR_PNPM_SOME_CODE', 'some error'))
|
|
})
|
|
|
|
test('prints summary for global installation', async () => {
|
|
const prefix = '/home/jane/.nvs/node/10.0.0/x64/pnpm-global/1'
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: {
|
|
dir: prefix,
|
|
global: true,
|
|
} as Config & ConfigContext,
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'prod',
|
|
id: 'registry.npmjs.org/foo/1.0.0',
|
|
latest: '2.0.0',
|
|
name: 'foo',
|
|
realName: 'foo',
|
|
version: '1.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'prod',
|
|
id: 'registry.npmjs.org/bar/2.0.0',
|
|
latest: '1.0.0', // this won't be printed in summary because latest is less than current version
|
|
name: 'bar',
|
|
realName: 'bar',
|
|
version: '2.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
packageManifestLogger.debug({
|
|
prefix,
|
|
updated: {
|
|
dependencies: {
|
|
'is-negative': '^1.0.0',
|
|
},
|
|
devDependencies: {
|
|
'is-13': '^1.0.0',
|
|
},
|
|
},
|
|
})
|
|
summaryLogger.debug({ prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(EOL + `\
|
|
${h1('global:')}
|
|
${ADD} bar ${versionColor('2.0.0')}
|
|
${ADD} foo ${versionColor('1.0.0')} ${versionColor('(2.0.0 is available)')}
|
|
`)
|
|
})
|
|
|
|
test('prints added peer dependency', async () => {
|
|
const prefix = '/home/jane/.nvs/node/10.0.0/x64/pnpm-global/1'
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: {
|
|
dir: prefix,
|
|
} as Config & ConfigContext,
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
packageManifestLogger.debug({
|
|
initial: {},
|
|
prefix,
|
|
})
|
|
packageManifestLogger.debug({
|
|
prefix,
|
|
updated: {
|
|
devDependencies: {
|
|
'is-negative': '^1.0.0',
|
|
},
|
|
peerDependencies: {
|
|
'is-negative': '^1.0.0',
|
|
},
|
|
},
|
|
})
|
|
summaryLogger.debug({ prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(EOL + `\
|
|
${h1('peerDependencies:')}
|
|
${ADD} is-negative ${versionColor('^1.0.0')}
|
|
|
|
${h1('devDependencies:')}
|
|
${ADD} is-negative ${versionColor('^1.0.0')}
|
|
`)
|
|
})
|
|
|
|
test('prints summary correctly when the same package is specified both in optional and prod dependencies', async () => {
|
|
const prefix = '/home/jane/.nvs/node/10.0.0/x64/pnpm-global/1'
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: {
|
|
dir: prefix,
|
|
} as Config & ConfigContext,
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
packageManifestLogger.debug({
|
|
initial: {
|
|
name: 'foo',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
bar: '^2.0.0',
|
|
foo: '^1.0.0',
|
|
},
|
|
optionalDependencies: {
|
|
foo: '^1.0.0',
|
|
},
|
|
},
|
|
prefix,
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'prod',
|
|
id: 'registry.npmjs.org/bar/2.0.0',
|
|
name: 'bar',
|
|
realName: 'bar',
|
|
version: '2.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
packageManifestLogger.debug({
|
|
prefix,
|
|
updated: {
|
|
dependencies: {
|
|
bar: '^2.0.0',
|
|
},
|
|
optionalDependencies: {
|
|
foo: '^1.0.0',
|
|
},
|
|
},
|
|
})
|
|
summaryLogger.debug({ prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(EOL + `\
|
|
${h1('dependencies:')}
|
|
${ADD} bar ${versionColor('2.0.0')}
|
|
`)
|
|
})
|
|
|
|
test('in the installation summary report which dependency types are skipped', async () => {
|
|
const prefix = '/home/jane/.nvs/node/10.0.0/x64/pnpm-global/1'
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: {
|
|
dir: prefix,
|
|
production: true,
|
|
dev: false,
|
|
optional: false,
|
|
} as Config & ConfigContext,
|
|
env: {
|
|
NODE_ENV: 'production',
|
|
},
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
packageManifestLogger.debug({
|
|
initial: {
|
|
name: 'foo',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
bar: '^2.0.0',
|
|
foo: '^1.0.0',
|
|
},
|
|
optionalDependencies: {
|
|
foo: '^1.0.0',
|
|
},
|
|
},
|
|
prefix,
|
|
})
|
|
rootLogger.debug({
|
|
added: {
|
|
dependencyType: 'prod',
|
|
id: 'registry.npmjs.org/bar/2.0.0',
|
|
name: 'bar',
|
|
realName: 'bar',
|
|
version: '2.0.0',
|
|
},
|
|
prefix,
|
|
})
|
|
packageManifestLogger.debug({
|
|
prefix,
|
|
updated: {
|
|
dependencies: {
|
|
bar: '^2.0.0',
|
|
},
|
|
optionalDependencies: {
|
|
foo: '^1.0.0',
|
|
},
|
|
},
|
|
})
|
|
summaryLogger.debug({ prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(EOL + `\
|
|
${h1('dependencies:')}
|
|
${ADD} bar ${versionColor('2.0.0')}
|
|
|
|
${h1('optionalDependencies:')} skipped
|
|
|
|
${h1('devDependencies:')} skipped
|
|
`)
|
|
})
|
|
|
|
test('prints summary when some packages fail', async () => {
|
|
const output$ = toOutput$({
|
|
context: { argv: ['run'], config: { recursive: true } as Config & ConfigContext },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
expect.assertions(1)
|
|
|
|
const err = Object.assign(new PnpmError('RECURSIVE_FAIL', '...'), {
|
|
failures: [
|
|
{
|
|
message: 'a failed',
|
|
prefix: '/a',
|
|
},
|
|
{
|
|
message: 'b failed',
|
|
prefix: '/b',
|
|
},
|
|
{
|
|
message: 'c failed',
|
|
prefix: '/c',
|
|
},
|
|
{
|
|
message: 'd failed',
|
|
prefix: '/d',
|
|
},
|
|
{
|
|
message: 'e failed',
|
|
prefix: '/e',
|
|
},
|
|
{
|
|
message: 'f failed',
|
|
prefix: '/f',
|
|
},
|
|
],
|
|
passes: 7,
|
|
})
|
|
|
|
logger.error(err, err)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`\
|
|
${formatError('ERR_PNPM_RECURSIVE_FAIL', '')}
|
|
|
|
|
|
Summary: ${chalk.red('6 fails')}, 7 passes
|
|
|
|
/a:
|
|
${formatError('ERROR', 'a failed')}
|
|
|
|
/b:
|
|
${formatError('ERROR', 'b failed')}
|
|
|
|
/c:
|
|
${formatError('ERROR', 'c failed')}
|
|
|
|
/d:
|
|
${formatError('ERROR', 'd failed')}
|
|
|
|
/e:
|
|
${formatError('ERROR', 'e failed')}
|
|
|
|
/f:
|
|
${formatError('ERROR', 'f failed')}`)
|
|
})
|
|
|
|
test('prints info', async () => {
|
|
const output$ = toOutput$({
|
|
context: { argv: ['install'] },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
logger.info({ message: 'info message', prefix: process.cwd() })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe('info message')
|
|
})
|
|
|
|
test('prints added/removed stats during installation', async () => {
|
|
const output$ = toOutput$({
|
|
context: { argv: ['install'] },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
const prefix = process.cwd()
|
|
|
|
statsLogger.debug({ added: 5, prefix })
|
|
statsLogger.debug({ removed: 1, prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`Packages: ${chalk.green('+5')} ${chalk.red('-1')}
|
|
${ADD + ADD + ADD + ADD + ADD + SUB}`)
|
|
})
|
|
|
|
test('prints added/removed stats during installation when 0 removed', async () => {
|
|
const output$ = toOutput$({
|
|
context: { argv: ['install'] },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
const prefix = process.cwd()
|
|
|
|
statsLogger.debug({ added: 2, prefix })
|
|
statsLogger.debug({ removed: 0, prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`Packages: ${chalk.green('+2')}
|
|
${ADD + ADD}`)
|
|
})
|
|
|
|
test('prints only the added stats if nothing was removed', async () => {
|
|
const output$ = toOutput$({
|
|
context: { argv: ['install'] },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
const prefix = process.cwd()
|
|
|
|
statsLogger.debug({ removed: 0, prefix })
|
|
statsLogger.debug({ added: 1, prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`Packages: ${chalk.green('+1')}
|
|
${ADD}`)
|
|
})
|
|
|
|
test('prints only the removed stats if nothing was added', async () => {
|
|
const output$ = toOutput$({
|
|
context: { argv: ['install'] },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
const prefix = process.cwd()
|
|
|
|
statsLogger.debug({ removed: 1, prefix })
|
|
statsLogger.debug({ added: 0, prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`Packages: ${chalk.red('-1')}
|
|
${SUB}`)
|
|
})
|
|
|
|
test('prints only the added stats if nothing was removed and a lot added', async () => {
|
|
const output$ = toOutput$({
|
|
context: { argv: ['install'] },
|
|
reportingOptions: { outputMaxWidth: 20 },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
const prefix = process.cwd()
|
|
|
|
statsLogger.debug({ removed: 0, prefix })
|
|
statsLogger.debug({ added: 100, prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`Packages: ${chalk.green('+100')}
|
|
${repeat(ADD, 20).join('')}`)
|
|
})
|
|
|
|
test('prints only the removed stats if nothing was added and a lot removed', async () => {
|
|
const output$ = toOutput$({
|
|
context: { argv: ['install'] },
|
|
reportingOptions: { outputMaxWidth: 20 },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
const prefix = process.cwd()
|
|
|
|
statsLogger.debug({ removed: 100, prefix })
|
|
statsLogger.debug({ added: 0, prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`Packages: ${chalk.red('-100')}
|
|
${repeat(SUB, 20).join('')}`)
|
|
})
|
|
|
|
test('prints at least one remove sign when removed !== 0', async () => {
|
|
const output$ = toOutput$({
|
|
context: { argv: ['install'] },
|
|
reportingOptions: { outputMaxWidth: 20 },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
const prefix = process.cwd()
|
|
|
|
statsLogger.debug({ removed: 1, prefix })
|
|
statsLogger.debug({ added: 100, prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`Packages: ${chalk.green('+100')} ${chalk.red('-1')}
|
|
${repeat(ADD, 19).join('') + SUB}`)
|
|
})
|
|
|
|
test('prints at least one add sign when added !== 0', async () => {
|
|
const output$ = toOutput$({
|
|
context: { argv: ['install'] },
|
|
reportingOptions: { outputMaxWidth: 20 },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
const prefix = process.cwd()
|
|
|
|
statsLogger.debug({ removed: 100, prefix })
|
|
statsLogger.debug({ added: 1, prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`Packages: ${chalk.green('+1')} ${chalk.red('-100')}
|
|
${ADD + repeat(SUB, 19).join('')}`)
|
|
})
|
|
|
|
test('prints just removed during uninstallation', async () => {
|
|
const output$ = toOutput$({
|
|
context: { argv: ['remove'] },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
const prefix = process.cwd()
|
|
|
|
statsLogger.debug({ removed: 4, prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`Packages: ${chalk.red('-4')}
|
|
${SUB + SUB + SUB + SUB}`)
|
|
})
|
|
|
|
test('prints added/removed stats and warnings during recursive installation', async () => {
|
|
const rootPrefix = '/home/jane/repo'
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: { dir: rootPrefix, recursive: true } as Config & ConfigContext,
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
logger.warn({ message: 'Some issue', prefix: '/home/jane/repo/pkg-5' })
|
|
logger.warn({ message: 'Some other issue', prefix: rootPrefix })
|
|
statsLogger.debug({ removed: 1, prefix: '/home/jane/repo' })
|
|
statsLogger.debug({ added: 0, prefix: '/home/jane/repo' })
|
|
statsLogger.debug({ removed: 0, prefix: '/home/jane/repo/pkg-5' })
|
|
statsLogger.debug({ added: 0, prefix: '/home/jane/repo/pkg-5' })
|
|
statsLogger.debug({ added: 2, prefix: '/home/jane/repo/dir/pkg-2' })
|
|
statsLogger.debug({ added: 5, prefix: '/home/jane/repo/pkg-1' })
|
|
statsLogger.debug({ removed: 1, prefix: '/home/jane/repo/pkg-1' })
|
|
deprecationLogger.debug({
|
|
deprecated: 'This package was deprecated because bla bla bla',
|
|
depth: 0,
|
|
pkgId: 'registry.npmjs.org/bar/2.0.0',
|
|
pkgName: 'bar',
|
|
pkgVersion: '2.0.0',
|
|
prefix: '/home/jane/repo/dir/pkg-2',
|
|
})
|
|
statsLogger.debug({ removed: 0, prefix: '/home/jane/repo/dir/pkg-2' })
|
|
// cspell:disable
|
|
statsLogger.debug({ removed: 0, prefix: '/home/jane/repo/loooooooooooooooooooooooooooooooooong/pkg-3' })
|
|
statsLogger.debug({ added: 1, prefix: '/home/jane/repo/loooooooooooooooooooooooooooooooooong/pkg-3' })
|
|
statsLogger.debug({ removed: 1, prefix: '/home/jane/repo/loooooooooooooooooooooooooooooooooong-pkg-4' })
|
|
statsLogger.debug({ added: 0, prefix: '/home/jane/repo/loooooooooooooooooooooooooooooooooong-pkg-4' })
|
|
// cspell:enable
|
|
deprecationLogger.debug({
|
|
deprecated: 'This package was deprecated because bla bla bla',
|
|
depth: 0,
|
|
pkgId: 'registry.npmjs.org/foo/1.0.0',
|
|
pkgName: 'foo',
|
|
pkgVersion: '1.0.0',
|
|
prefix: rootPrefix,
|
|
})
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(skip(8), take(1), map(normalizeNewline)))
|
|
// cspell:disable
|
|
expect(output).toBe(`\
|
|
pkg-5 | ${formatWarn('Some issue')}
|
|
. | ${formatWarn('Some other issue')}
|
|
. | ${chalk.red('-1')} ${SUB}
|
|
pkg-1 | ${chalk.green('+5')} ${chalk.red('-1')} ${ADD + SUB}
|
|
dir/pkg-2 | ${formatWarn(`${DEPRECATED} bar@2.0.0`)}
|
|
dir/pkg-2 | ${chalk.green('+2')} ${ADD}
|
|
.../pkg-3 | ${chalk.green('+1')} ${ADD}
|
|
...ooooooooooooooooooooooooooooong-pkg-4 | ${chalk.red('-1')} ${SUB}
|
|
. | ${formatWarn(`${DEPRECATED} foo@1.0.0`)}`)
|
|
// cspell:enable
|
|
})
|
|
|
|
test('recursive installation: prints only the added stats if nothing was removed and a lot added', async () => {
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['recursive'],
|
|
config: { dir: '/home/jane/repo' } as Config & ConfigContext,
|
|
},
|
|
reportingOptions: { outputMaxWidth: 60 },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
statsLogger.debug({ removed: 0, prefix: '/home/jane/repo/pkg-1' })
|
|
statsLogger.debug({ added: 190, prefix: '/home/jane/repo/pkg-1' })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`pkg-1 | ${chalk.green('+190')} ${repeat(ADD, 12).join('')}`)
|
|
})
|
|
|
|
test('recursive installation: prints only the removed stats if nothing was added and a lot removed', async () => {
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['recursive'],
|
|
config: { dir: '/home/jane/repo' } as Config & ConfigContext,
|
|
},
|
|
reportingOptions: { outputMaxWidth: 60 },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
statsLogger.debug({ removed: 190, prefix: '/home/jane/repo/pkg-1' })
|
|
statsLogger.debug({ added: 0, prefix: '/home/jane/repo/pkg-1' })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`pkg-1 | ${chalk.red('-190')} ${repeat(SUB, 12).join('')}`)
|
|
})
|
|
|
|
test('recursive installation: prints at least one remove sign when removed !== 0', async () => {
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['recursive'],
|
|
config: { dir: '/home/jane/repo' } as Config & ConfigContext,
|
|
},
|
|
reportingOptions: { outputMaxWidth: 62 },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
statsLogger.debug({ removed: 1, prefix: '/home/jane/repo/pkg-1' })
|
|
statsLogger.debug({ added: 100, prefix: '/home/jane/repo/pkg-1' })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`pkg-1 | ${chalk.green('+100')} ${chalk.red('-1')} ${repeat(ADD, 8).join('') + SUB}`)
|
|
})
|
|
|
|
test('recursive installation: prints at least one add sign when added !== 0', async () => {
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['recursive'],
|
|
config: { dir: '/home/jane/repo' } as Config & ConfigContext,
|
|
},
|
|
reportingOptions: { outputMaxWidth: 62 },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
statsLogger.debug({ removed: 100, prefix: '/home/jane/repo/pkg-1' })
|
|
statsLogger.debug({ added: 1, prefix: '/home/jane/repo/pkg-1' })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`pkg-1 | ${chalk.green('+1')} ${chalk.red('-100')} ${ADD + repeat(SUB, 8).join('')}`)
|
|
})
|
|
|
|
test('recursive uninstall: prints removed packages number', async () => {
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['remove'],
|
|
config: { dir: '/home/jane/repo', recursive: true } as Config & ConfigContext,
|
|
},
|
|
reportingOptions: { outputMaxWidth: 62 },
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
statsLogger.debug({ removed: 1, prefix: '/home/jane/repo/pkg-1' })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`pkg-1 | ${chalk.red('-1')} ${SUB}`)
|
|
})
|
|
|
|
test('install: print hook message', async () => {
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: { dir: '/home/jane/repo' } as Config & ConfigContext,
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
hookLogger.debug({
|
|
from: '/home/jane/repo/.pnpmfile.cjs',
|
|
hook: 'readPackage',
|
|
message: 'foo',
|
|
prefix: '/home/jane/repo',
|
|
})
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`${chalk.magentaBright('readPackage')}: foo`)
|
|
})
|
|
|
|
test('recursive: print hook message', async () => {
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['recursive'],
|
|
config: { dir: '/home/jane/repo' } as Config & ConfigContext,
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
hookLogger.debug({
|
|
from: '/home/jane/repo/.pnpmfile.cjs',
|
|
hook: 'readPackage',
|
|
message: 'foo',
|
|
prefix: '/home/jane/repo/pkg-1',
|
|
})
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
|
|
expect(output).toBe(`pkg-1 | ${chalk.magentaBright('readPackage')}: foo`)
|
|
})
|
|
|
|
test('prints skipped optional dependency info message', async () => {
|
|
const prefix = process.cwd()
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: { dir: prefix } as Config & ConfigContext,
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
const pkgId = 'registry.npmjs.org/foo/1.0.0'
|
|
|
|
skippedOptionalDependencyLogger.debug({
|
|
package: {
|
|
id: pkgId,
|
|
name: 'foo',
|
|
version: '1.0.0',
|
|
},
|
|
parents: [],
|
|
prefix,
|
|
reason: 'unsupported_platform',
|
|
})
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$)
|
|
expect(output).toBe(`info: ${pkgId} is an optional dependency and failed compatibility check. Excluding it from installation.`)
|
|
})
|
|
|
|
test('logLevel=default', async () => {
|
|
const prefix = process.cwd()
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: { dir: prefix } as Config & ConfigContext,
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
logger.info({ message: 'Info message', prefix })
|
|
logger.warn({ message: 'Some issue', prefix })
|
|
const err = new PnpmError('SOME_CODE', 'some error')
|
|
logger.error(err, err)
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(skip(2), take(1)))
|
|
expect(output).toBe(`Info message
|
|
${formatWarn('Some issue')}
|
|
${formatError('ERR_PNPM_SOME_CODE', 'some error')}`)
|
|
})
|
|
|
|
test('logLevel=warn', async () => {
|
|
const prefix = process.cwd()
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: { dir: prefix } as Config & ConfigContext,
|
|
},
|
|
reportingOptions: {
|
|
logLevel: 'warn',
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
logger.info({ message: 'Info message', prefix })
|
|
logger.warn({ message: 'Some issue', prefix })
|
|
const err = new PnpmError('SOME_CODE', 'some error')
|
|
logger.error(err, err)
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(skip(1), take(1)))
|
|
expect(output).toBe(`${formatWarn('Some issue')}
|
|
${formatError('ERR_PNPM_SOME_CODE', 'some error')}`)
|
|
})
|
|
|
|
test('logLevel=error', async () => {
|
|
const prefix = process.cwd()
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: { dir: prefix } as Config & ConfigContext,
|
|
},
|
|
reportingOptions: {
|
|
logLevel: 'error',
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
logger.info({ message: 'Info message', prefix })
|
|
logger.warn({ message: 'Some issue', prefix })
|
|
const err = new PnpmError('SOME_CODE', 'some error')
|
|
logger.error(err, err)
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$)
|
|
expect(output).toBe(formatError('ERR_PNPM_SOME_CODE', 'some error'))
|
|
})
|
|
|
|
test('warnings are collapsed', async () => {
|
|
const prefix = process.cwd()
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: { dir: prefix } as Config & ConfigContext,
|
|
},
|
|
reportingOptions: {
|
|
logLevel: 'warn',
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
logger.warn({ message: 'Some issue 1', prefix })
|
|
logger.warn({ message: 'Some issue 2', prefix })
|
|
logger.warn({ message: 'Some issue 3', prefix })
|
|
logger.warn({ message: 'Some issue 4', prefix })
|
|
logger.warn({ message: 'Some issue 5', prefix })
|
|
logger.warn({ message: 'Some issue 6', prefix })
|
|
logger.warn({ message: 'Some issue 7', prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(skip(6), take(1)))
|
|
expect(output).toBe(`${formatWarn('Some issue 1')}
|
|
${formatWarn('Some issue 2')}
|
|
${formatWarn('Some issue 3')}
|
|
${formatWarn('Some issue 4')}
|
|
${formatWarn('Some issue 5')}
|
|
${formatWarn('2 other warnings')}`)
|
|
})
|
|
|
|
test('warnings are not collapsed when append-only is true', async () => {
|
|
const prefix = process.cwd()
|
|
const output$ = toOutput$({
|
|
context: {
|
|
argv: ['install'],
|
|
config: { dir: prefix } as Config & ConfigContext,
|
|
},
|
|
reportingOptions: {
|
|
appendOnly: true,
|
|
logLevel: 'warn',
|
|
},
|
|
streamParser: createStreamParser(),
|
|
})
|
|
|
|
logger.warn({ message: 'Some issue 1', prefix })
|
|
logger.warn({ message: 'Some issue 2', prefix })
|
|
logger.warn({ message: 'Some issue 3', prefix })
|
|
logger.warn({ message: 'Some issue 4', prefix })
|
|
logger.warn({ message: 'Some issue 5', prefix })
|
|
logger.warn({ message: 'Some issue 6', prefix })
|
|
logger.warn({ message: 'Some issue 7', prefix })
|
|
|
|
expect.assertions(1)
|
|
|
|
const output = await firstValueFrom(output$.pipe(skip(6), take(1)))
|
|
expect(output).toBe(formatWarn('Some issue 7'))
|
|
})
|