feat: changed-files-ignore-pattern option (#3797)

This commit is contained in:
Igor Bezkrovnyi
2021-09-26 23:42:43 +02:00
committed by GitHub
parent ea69daae5d
commit fe5688dc0c
10 changed files with 178 additions and 5 deletions

View File

@@ -0,0 +1,8 @@
---
"@pnpm/common-cli-options-help": minor
"@pnpm/config": minor
"@pnpm/filter-workspace-packages": minor
"pnpm": minor
---
Add option 'changed-files-ignore-pattern' to ignore changed files by glob patterns when filtering for changed projects since the specified commit/branch.

View File

@@ -104,6 +104,10 @@ export const FILTERING = {
description: 'Defines files related to tests. Useful with the changed since filter. When selecting only changed packages and their dependent packages, the dependent packages will be ignored in case a package has changes only in tests. Usage example: pnpm --filter="...[origin/master]" --test-pattern="test/*" test',
name: '--test-pattern <pattern>',
},
{
description: 'Defines files to ignore when filtering for changed projects since the specified commit/branch. Usage example: pnpm --filter="...[origin/master]" --changed-files-ignore-pattern="**/README.md" build',
name: '--changed-files-ignore-pattern <pattern>',
},
{
description: 'Restricts the scope to package names matching the given pattern similar to --filter, but it ignores devDependencies when searching for dependencies and dependents.',
name: '--filter-prod <pattern>',

View File

@@ -133,6 +133,7 @@ export interface Config {
workspaceRoot: boolean
testPattern?: string[]
changedFilesIgnorePattern?: string[]
}
export interface ConfigWithDeprecatedSettings extends Config {

View File

@@ -111,6 +111,7 @@ export const types = Object.assign({
'workspace-packages': [String, Array],
'workspace-root': Boolean,
'test-pattern': [String, Array],
'changed-files-ignore-pattern': [String, Array],
}, npmTypes.types)
export type CliOptions = Record<string, unknown> & { dir?: string }

View File

@@ -622,6 +622,42 @@ test('respects test-pattern', async () => {
}
})
test('respects changed-files-ignore-pattern', async () => {
{
const { config } = await getConfig({
cliOptions: {},
packageManager: {
name: 'pnpm',
version: '1.0.0',
},
})
expect(config.changedFilesIgnorePattern).toBeUndefined()
}
{
prepareEmpty()
const npmrc = [
'changed-files-ignore-pattern[]=.github/**',
'changed-files-ignore-pattern[]=**/README.md',
].join('\n')
await fs.writeFile('.npmrc', npmrc, 'utf8')
const { config } = await getConfig({
cliOptions: {
global: false,
},
packageManager: {
name: 'pnpm',
version: '1.0.0',
},
})
expect(config.changedFilesIgnorePattern).toEqual(['.github/**', '**/README.md'])
}
})
test('dir is resolved to real path', async () => {
prepareEmpty()
const realDir = path.resolve('real-path')

View File

@@ -8,9 +8,9 @@ type ChangeType = 'source' | 'test'
interface ChangedDir { dir: string, changeType: ChangeType }
export default async function changedSince (packageDirs: string[], commit: string, opts: { workspaceDir: string, testPattern?: string[] }): Promise<[string[], string[]]> {
export default async function changedSince (packageDirs: string[], commit: string, opts: { workspaceDir: string, testPattern?: string[], changedFilesIgnorePattern?: string[] }): Promise<[string[], string[]]> {
const repoRoot = path.resolve(await findUp('.git', { cwd: opts.workspaceDir, type: 'directory' }) ?? opts.workspaceDir, '..')
const changedDirs = (await getChangedDirsSinceCommit(commit, opts.workspaceDir, opts.testPattern ?? []))
const changedDirs = (await getChangedDirsSinceCommit(commit, opts.workspaceDir, opts.testPattern ?? [], opts.changedFilesIgnorePattern ?? []))
.map(changedDir => ({ ...changedDir, dir: path.join(repoRoot, changedDir.dir) }))
const pkgChangeTypes = new Map<string, ChangeType | undefined>()
for (const pkgDir of packageDirs) {
@@ -42,7 +42,7 @@ export default async function changedSince (packageDirs: string[], commit: strin
return [changedPkgs, ignoreDependentForPkgs]
}
async function getChangedDirsSinceCommit (commit: string, workingDir: string, testPattern: string[]): Promise<ChangedDir[]> {
async function getChangedDirsSinceCommit (commit: string, workingDir: string, testPattern: string[], changedFilesIgnorePattern: string[]): Promise<ChangedDir[]> {
let diff!: string
try {
diff = (
@@ -63,7 +63,15 @@ async function getChangedDirsSinceCommit (commit: string, workingDir: string, te
return []
}
const changedFiles = diff.split('\n')
const allChangedFiles = diff.split('\n')
const patterns = changedFilesIgnorePattern.filter(
(pattern) => pattern.length
)
const changedFiles = patterns.length
? micromatch.not(allChangedFiles, patterns, {
dot: true,
})
: allChangedFiles
for (const changedFile of changedFiles) {
const dir = path.dirname(changedFile)

View File

@@ -34,6 +34,7 @@ export async function readProjects (
pkgSelectors: PackageSelector[],
opts?: {
linkWorkspacePackages?: boolean
changedFilesIgnorePattern?: string[]
}
) {
const allProjects = await findWorkspacePackages(workspaceDir, {})
@@ -43,6 +44,7 @@ export async function readProjects (
{
linkWorkspacePackages: opts?.linkWorkspacePackages,
workspaceDir,
changedFilesIgnorePattern: opts?.changedFilesIgnorePattern,
}
)
return { allProjects, selectedProjectsGraph }
@@ -56,6 +58,7 @@ export async function filterPackages<T> (
prefix: string
workspaceDir: string
testPattern?: string[]
changedFilesIgnorePattern?: string[]
useGlobDirFiltering?: boolean
}
): Promise<{
@@ -74,6 +77,7 @@ export async function filterPkgsBySelectorObjects<T> (
linkWorkspacePackages?: boolean
workspaceDir: string
testPattern?: string[]
changedFilesIgnorePattern?: string[]
useGlobDirFiltering?: boolean
}
): Promise<{
@@ -90,6 +94,7 @@ export async function filterPkgsBySelectorObjects<T> (
filteredGraph = await filterGraph(graph, allPackageSelectors, {
workspaceDir: opts.workspaceDir,
testPattern: opts.testPattern,
changedFilesIgnorePattern: opts.changedFilesIgnorePattern,
useGlobDirFiltering: opts.useGlobDirFiltering,
})
}
@@ -101,6 +106,7 @@ export async function filterPkgsBySelectorObjects<T> (
prodFilteredGraph = await filterGraph(graph, prodPackageSelectors, {
workspaceDir: opts.workspaceDir,
testPattern: opts.testPattern,
changedFilesIgnorePattern: opts.changedFilesIgnorePattern,
useGlobDirFiltering: opts.useGlobDirFiltering,
})
}
@@ -127,6 +133,7 @@ export default async function filterGraph<T> (
opts: {
workspaceDir: string
testPattern?: string[]
changedFilesIgnorePattern?: string[]
useGlobDirFiltering?: boolean
}
): Promise<{
@@ -156,6 +163,7 @@ async function _filterGraph<T> (
opts: {
workspaceDir: string
testPattern?: string[]
changedFilesIgnorePattern?: string[]
useGlobDirFiltering?: boolean
},
packageSelectors: PackageSelector[]
@@ -178,7 +186,7 @@ async function _filterGraph<T> (
if (selector.diff) {
let ignoreDependentForPkgs: string[] = []
;[entryPackages, ignoreDependentForPkgs] = await getChangedPkgs(Object.keys(pkgGraph),
selector.diff, { workspaceDir: selector.parentDir ?? opts.workspaceDir, testPattern: opts.testPattern })
selector.diff, { workspaceDir: selector.parentDir ?? opts.workspaceDir, testPattern: opts.testPattern, changedFilesIgnorePattern: opts.changedFilesIgnorePattern })
selectEntries({
...selector,
includeDependents: false,

View File

@@ -39,6 +39,7 @@ export const GLOBAL_OPTIONS = pick([
'reporter',
'stream',
'test-pattern',
'changed-files-ignore-pattern',
'use-stderr',
'workspace-packages',
'workspace-root',

View File

@@ -190,6 +190,7 @@ export default async function run (inputArgv: string[]) {
prefix: process.cwd(),
workspaceDir: wsDir,
testPattern: config.testPattern,
changedFilesIgnorePattern: config.changedFilesIgnorePattern,
useGlobDirFiltering: config.useBetaCli,
})
config.selectedProjectsGraph = filterResults.selectedProjectsGraph

View File

@@ -353,6 +353,111 @@ test('test-pattern is respected by the test script', async () => {
expect(output.sort()).toStrictEqual(['project-2', 'project-4'])
})
test('changed-files-ignore-pattern is respected', async () => {
const remote = tempy.directory()
preparePackages([
{
name: 'project-1-no-changes',
version: '1.0.0',
},
{
name: 'project-2-change-is-never-ignored',
version: '1.0.0',
},
{
name: 'project-3-ignored-by-pattern',
version: '1.0.0',
},
{
name: 'project-4-ignored-by-pattern',
version: '1.0.0',
},
{
name: 'project-5-ignored-by-pattern',
version: '1.0.0',
},
])
await execa('git', ['init'])
await execa('git', ['config', 'user.email', 'x@y.z'])
await execa('git', ['config', 'user.name', 'xyz'])
await execa('git', ['init', '--bare'], { cwd: remote })
await execa('git', ['add', '*'])
await execa('git', ['commit', '-m', 'init', '--no-gpg-sign'])
await execa('git', ['remote', 'add', 'origin', remote])
await execa('git', ['push', '-u', 'origin', 'master'])
const npmrcLines = []
await fs.writeFile('project-2-change-is-never-ignored/index.js', '')
npmrcLines.push('changed-files-ignore-pattern[]=**/{*.spec.js,*.md}')
await fs.writeFile('project-3-ignored-by-pattern/index.spec.js', '')
await fs.writeFile('project-3-ignored-by-pattern/README.md', '')
npmrcLines.push('changed-files-ignore-pattern[]=**/buildscript.js')
await fs.mkdir('project-4-ignored-by-pattern/a/b/c', {
recursive: true,
})
await fs.writeFile('project-4-ignored-by-pattern/a/b/c/buildscript.js', '')
npmrcLines.push('changed-files-ignore-pattern[]=**/cache/**')
await fs.mkdir('project-5-ignored-by-pattern/cache/a/b', {
recursive: true,
})
await fs.writeFile('project-5-ignored-by-pattern/cache/a/b/index.js', '')
await writeYamlFile('pnpm-workspace.yaml', { packages: ['**', '!store/**'] })
await execa('git', ['add', '.'])
await execa('git', [
'commit',
'--allow-empty-message',
'-m',
'',
'--no-gpg-sign',
])
await fs.writeFile('.npmrc', npmrcLines.join('\n'), 'utf8')
await execPnpm(['install'])
const getChangedProjects = async (opts?: {
overrideChangedFilesIgnorePatternWithNoPattern: boolean
}) => {
const result = await execPnpmSync(
[
'--filter',
'[origin/master]',
opts?.overrideChangedFilesIgnorePatternWithNoPattern
? '--changed-files-ignore-pattern='
: '',
'ls',
'--depth',
'-1',
'--json',
].filter(Boolean)
)
return JSON.parse(result.stdout.toString())
.map((p: { name: string }) => p.name)
.sort()
}
expect(await getChangedProjects()).toStrictEqual([
'project-2-change-is-never-ignored',
])
expect(
await getChangedProjects({
overrideChangedFilesIgnorePatternWithNoPattern: true,
})
).toStrictEqual([
'project-2-change-is-never-ignored',
'project-3-ignored-by-pattern',
'project-4-ignored-by-pattern',
'project-5-ignored-by-pattern',
])
})
test('do not get confused by filtered dependencies when searching for dependents in monorepo', async () => {
/*
In this test case, we are filtering for 'project-2' and its dependents with