-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
863 additions
and
229 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
'skuba': minor | ||
--- | ||
|
||
lint: Manage `.npmrc` for pnpm projects | ||
|
||
skuba now manages a section of `.npmrc` when a project uses `pnpm` to enable [dependency hoisting](https://pnpm.io/npmrc#dependency-hoisting-settings). It will continue to avoid committing autofixes to the file if it contains auth secrets. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
src/cli/configure/upgrade/patches/7.3.1/moveNpmrcOutOfGitignoreManagedSection.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import fs from 'fs-extra'; | ||
|
||
import * as packageAnalysis from '../../../analysis/package'; | ||
import * as projectAnalysis from '../../../analysis/project'; | ||
|
||
import { tryMoveNpmrcOutOfGitignoreManagedSection } from './moveNpmrcOutOfGitignoreManagedSection'; | ||
|
||
jest | ||
.spyOn(packageAnalysis, 'getDestinationManifest') | ||
.mockResolvedValue({ path: '~/project/package.json' } as any); | ||
|
||
const createDestinationFileReader = jest | ||
.spyOn(projectAnalysis, 'createDestinationFileReader') | ||
.mockReturnValue(() => { | ||
throw new Error('Not implemented!'); | ||
}); | ||
|
||
const writeFile = jest.spyOn(fs.promises, 'writeFile').mockResolvedValue(); | ||
|
||
beforeEach(jest.clearAllMocks); | ||
|
||
describe('tryMoveNpmrcOutOfGitignoreManagedSection', () => { | ||
describe('format mode', () => { | ||
it('moves a .gitignore out', async () => { | ||
createDestinationFileReader.mockReturnValue(() => | ||
Promise.resolve( | ||
`# managed by skuba\nstuff\n.npmrc\nother stuff\n# end managed by skuba`, | ||
), | ||
); | ||
|
||
await expect( | ||
tryMoveNpmrcOutOfGitignoreManagedSection('format', '~/project'), | ||
).resolves.toEqual({ | ||
result: 'apply', | ||
}); | ||
|
||
expect(writeFile.mock.calls.flat().join('\n')).toMatchInlineSnapshot(` | ||
"~/project/.gitignore | ||
# managed by skuba | ||
stuff | ||
other stuff | ||
# end managed by skuba | ||
# Ignore .npmrc. This is no longer managed by skuba as pnpm projects use a managed .npmrc. | ||
# IMPORTANT: if migrating to pnpm, remove this line and add an .npmrc IN THE SAME COMMIT. | ||
# You can use \`skuba format\` to generate the file or otherwise commit an empty file. | ||
# Doing so will conflict with a local .npmrc and make it more difficult to unintentionally commit auth secrets. | ||
.npmrc | ||
" | ||
`); | ||
}); | ||
|
||
it('should be a no-op if ignored then un-ignored', async () => { | ||
createDestinationFileReader.mockReturnValue(() => | ||
Promise.resolve( | ||
`# managed by skuba\nstuff\n.npmrc\nother stuff\n# end managed by skuba\n!.npmrc`, | ||
), | ||
); | ||
|
||
await expect( | ||
tryMoveNpmrcOutOfGitignoreManagedSection('format', '~/project'), | ||
).resolves.toEqual({ | ||
result: 'skip', | ||
reason: 'not ignored', | ||
}); | ||
|
||
expect(writeFile).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should be a no-op if ignored out of managed section', async () => { | ||
createDestinationFileReader.mockReturnValue(() => | ||
Promise.resolve( | ||
`# managed by skuba\nstuff\n.npmrc\nother stuff\n# end managed by skuba\n.npmrc`, | ||
), | ||
); | ||
|
||
await expect( | ||
tryMoveNpmrcOutOfGitignoreManagedSection('format', '~/project'), | ||
).resolves.toEqual({ | ||
result: 'skip', | ||
reason: 'already ignored in unmanaged section', | ||
}); | ||
|
||
expect(writeFile).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should be a no-op if not ignored', async () => { | ||
createDestinationFileReader.mockReturnValue(() => | ||
Promise.resolve(`# managed by skuba\nstuff\n# end managed by skuba`), | ||
); | ||
|
||
await expect( | ||
tryMoveNpmrcOutOfGitignoreManagedSection('format', '~/project'), | ||
).resolves.toEqual({ | ||
result: 'skip', | ||
reason: 'not ignored', | ||
}); | ||
|
||
expect(writeFile).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
|
||
describe('lint mode', () => { | ||
it('flags moving a .gitignore out', async () => { | ||
createDestinationFileReader.mockReturnValue(() => | ||
Promise.resolve( | ||
`# managed by skuba\nstuff\n.npmrc\nother stuff\n# end managed by skuba`, | ||
), | ||
); | ||
|
||
await expect( | ||
tryMoveNpmrcOutOfGitignoreManagedSection('lint', '~/project'), | ||
).resolves.toEqual({ | ||
result: 'apply', | ||
}); | ||
|
||
expect(writeFile).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should be a no-op if ignored then un-ignored', async () => { | ||
createDestinationFileReader.mockReturnValue(() => | ||
Promise.resolve( | ||
`# managed by skuba\nstuff\n.npmrc\nother stuff\n# end managed by skuba\n!.npmrc`, | ||
), | ||
); | ||
|
||
await expect( | ||
tryMoveNpmrcOutOfGitignoreManagedSection('lint', '~/project'), | ||
).resolves.toEqual({ | ||
result: 'skip', | ||
reason: 'not ignored', | ||
}); | ||
|
||
expect(writeFile).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should be a no-op if ignored out of managed section', async () => { | ||
createDestinationFileReader.mockReturnValue(() => | ||
Promise.resolve( | ||
`# managed by skuba\nstuff\n.npmrc\nother stuff\n# end managed by skuba\n.npmrc`, | ||
), | ||
); | ||
|
||
await expect( | ||
tryMoveNpmrcOutOfGitignoreManagedSection('lint', '~/project'), | ||
).resolves.toEqual({ | ||
result: 'skip', | ||
reason: 'already ignored in unmanaged section', | ||
}); | ||
|
||
expect(writeFile).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should be a no-op if not ignored', async () => { | ||
createDestinationFileReader.mockReturnValue(() => | ||
Promise.resolve(`# managed by skuba\nstuff\n# end managed by skuba`), | ||
); | ||
|
||
await expect( | ||
tryMoveNpmrcOutOfGitignoreManagedSection('lint', '~/project'), | ||
).resolves.toEqual({ | ||
result: 'skip', | ||
reason: 'not ignored', | ||
}); | ||
|
||
expect(writeFile).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); |
85 changes: 85 additions & 0 deletions
85
src/cli/configure/upgrade/patches/7.3.1/moveNpmrcOutOfGitignoreManagedSection.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import path from 'path'; | ||
import { inspect } from 'util'; | ||
|
||
import fs from 'fs-extra'; | ||
|
||
import type { PatchFunction, PatchReturnType } from '../..'; | ||
import { log } from '../../../../../utils/logging'; | ||
import { createDestinationFileReader } from '../../../analysis/project'; | ||
|
||
const NPMRC_IGNORE_SECTION = ` | ||
# Ignore .npmrc. This is no longer managed by skuba as pnpm projects use a managed .npmrc. | ||
# IMPORTANT: if migrating to pnpm, remove this line and add an .npmrc IN THE SAME COMMIT. | ||
# You can use \`skuba format\` to generate the file or otherwise commit an empty file. | ||
# Doing so will conflict with a local .npmrc and make it more difficult to unintentionally commit auth secrets. | ||
.npmrc | ||
`; | ||
|
||
const moveNpmrcOutOfGitignoreManagedSection = async ( | ||
mode: 'format' | 'lint', | ||
dir: string, | ||
): Promise<PatchReturnType> => { | ||
const readFile = createDestinationFileReader(dir); | ||
|
||
const gitignore = await readFile('.gitignore'); | ||
|
||
if (!gitignore) { | ||
return { result: 'skip', reason: 'no .gitignore file found' }; | ||
} | ||
|
||
let isIgnored: { inManaged: boolean } | undefined; | ||
let currentlyInManagedSection = false; | ||
|
||
for (const line of gitignore.split('\n')) { | ||
if (line.trim() === '# managed by skuba') { | ||
currentlyInManagedSection = true; | ||
} else if (line.trim() === '# end managed by skuba') { | ||
currentlyInManagedSection = false; | ||
} | ||
|
||
if (line.trim() === '.npmrc' || line.trim() === '/.npmrc') { | ||
isIgnored = { inManaged: currentlyInManagedSection }; | ||
} | ||
|
||
if (line.trim() === '!.npmrc' || line.trim() === '!/.npmrc') { | ||
isIgnored = undefined; | ||
} | ||
} | ||
|
||
if (isIgnored && !isIgnored.inManaged) { | ||
return { result: 'skip', reason: 'already ignored in unmanaged section' }; | ||
} | ||
|
||
if (!isIgnored) { | ||
return { result: 'skip', reason: 'not ignored' }; | ||
} | ||
|
||
if (mode === 'lint') { | ||
return { result: 'apply' }; | ||
} | ||
|
||
const newGitignore = | ||
gitignore | ||
.split('\n') | ||
.filter((line) => line.trim().replace(/^[!/]+/g, '') !== '.npmrc') | ||
.join('\n') | ||
.trim() + NPMRC_IGNORE_SECTION; | ||
|
||
await fs.promises.writeFile(path.join(dir, '.gitignore'), newGitignore); | ||
|
||
return { result: 'apply' }; | ||
}; | ||
|
||
export const tryMoveNpmrcOutOfGitignoreManagedSection = (async ( | ||
mode: 'format' | 'lint', | ||
dir = process.cwd(), | ||
) => { | ||
try { | ||
return await moveNpmrcOutOfGitignoreManagedSection(mode, dir); | ||
} catch (err) { | ||
log.warn('Failed to move .npmrc out of .gitignore managed section.'); | ||
log.subtle(inspect(err)); | ||
return { result: 'skip', reason: 'due to an error' }; | ||
} | ||
}) satisfies PatchFunction; |
Oops, something went wrong.