import chalk from "chalk" ; import { join , dirname , resolve } from "./path" ; import { spawnSafeSync } from "./spawnSafe" ; import { PackageManager } from "./detectPackageManager" ; import { removeIgnoredFiles } from "./filterFiles" ; import { writeFileSync , existsSync , mkdirSync , unlinkSync , mkdirpSync , realpathSync , } from "fs-extra" ; import { sync as rimraf } from "rimraf" ; import { copySync } from "fs-extra" ; import { dirSync } from "tmp" ; import { getPatchFiles } from "./patchFs" ; import { getPatchDetailsFromCliString , getPackageDetailsFromPatchFilename , PackageDetails , } from "./PackageDetails" ; import { resolveRelativeFileDependencies } from "./resolveRelativeFileDependencies" ; import { getPackageResolution } from "./getPackageResolution" ; import { parsePatchFile } from "./patch/parse" ; import { gzipSync } from "zlib" ; import { getPackageVersion } from "./getPackageVersion" ; import { maybePrintIssueCreationPrompt , openIssueCreationLink , } from "./createIssue" ; function printNoPackageFoundError ( packageName , packageJsonPath ) { console . error ( `No such package ${ packageName }

  File not found: ${ packageJsonPath }` ) ; } export function makePatch ( { packagePathSpecifier , appPath , packageManager , includePaths , excludePaths , patchDir , createIssue , } ) { const packageDetails = getPatchDetailsFromCliString ( packagePathSpecifier ) ; if ( ! packageDetails ) { console . error ( "No such package" , packagePathSpecifier ) ; return ; } const appPackageJson = require ( join ( appPath , "package.json" ) ) ; const packagePath = join ( appPath , packageDetails . path ) ; const packageJsonPath = join ( packagePath , "package.json" ) ; if ( ! existsSync ( packageJsonPath ) ) { printNoPackageFoundError ( packagePathSpecifier , packageJsonPath ) ; process . exit ( 1 ) ; } const tmpRepo = dirSync ( { unsafeCleanup : true } ) ; const tmpRepoPackagePath = join ( tmpRepo . name , packageDetails . path ) ; const tmpRepoNpmRoot = tmpRepoPackagePath . slice ( 0 , - `/node_modules/${ packageDetails . name }` . length ) ; const tmpRepoPackageJsonPath = join ( tmpRepoNpmRoot , "package.json" ) ; try { const patchesDir = resolve ( join ( appPath , patchDir ) ) ; console . info ( chalk . grey ( "•" ) , "Creating temporary folder" ) ; mkdirpSync ( tmpRepoNpmRoot ) ; writeFileSync ( tmpRepoPackageJsonPath , JSON . stringify ( { dependencies : { [ packageDetails . name ] : getPackageResolution ( { packageDetails , packageManager , appPath , } ) , } , resolutions : resolveRelativeFileDependencies ( appPath , appPackageJson . resolutions || { } ) , } ) ) ; const packageVersion = getPackageVersion ( join ( resolve ( packageDetails . path ) , "package.json" ) ) ; [ ".npmrc" , ".yarnrc" ] . forEach ( ( rcFile ) => { const rcPath = join ( appPath , rcFile ) ; if ( existsSync ( rcPath ) ) { copySync ( rcPath , join ( tmpRepo . name , rcFile ) ) ; } } ) ; if ( packageManager === "yarn" ) { console . info ( chalk . grey ( "•" ) , `Installing ${ packageDetails . name }@${ packageVersion } with yarn` ) ; try { spawnSafeSync ( `yarn` , [ "install" , "--ignore-engines" ] , { cwd : tmpRepoNpmRoot , logStdErrOnError : false , } ) ; } catch ( e ) { spawnSafeSync ( `yarn` , [ "install" , "--ignore-engines" , "--ignore-scripts" ] , { cwd : tmpRepoNpmRoot , } ) ; } } else { console . info ( chalk . grey ( "•" ) , `Installing ${ packageDetails . name }@${ packageVersion } with npm` ) ; try { spawnSafeSync ( `npm` , [ "i" , "--force" ] , { cwd : tmpRepoNpmRoot , logStdErrOnError : false , stdio : "ignore" , } ) ; } catch ( e ) { spawnSafeSync ( `npm` , [ "i" , "--ignore-scripts" , "--force" ] , { cwd : tmpRepoNpmRoot , stdio : "ignore" , } ) ; } } const git = ( ... args ) => spawnSafeSync ( "git" , args , { cwd : tmpRepo . name , env : { ... process . env , HOME : tmpRepo . name } , maxBuffer : 1024 * 1024 * 100 , } ) ; rimraf ( join ( tmpRepoPackagePath , "node_modules" ) ) ; rimraf ( join ( tmpRepoPackagePath , ".git" ) ) ; console . info ( chalk . grey ( "•" ) , "Diffing your files with clean files" ) ; writeFileSync ( join ( tmpRepo . name , ".gitignore" ) , "!/node_modules\n\n" ) ; git ( "init" ) ; git ( "config" , "--local" , "user.name" , "patch-package" ) ; git ( "config" , "--local" , "user.email" , "patch@pack.age" ) ; removeIgnoredFiles ( tmpRepoPackagePath , includePaths , excludePaths ) ; git ( "add" , "-f" , packageDetails . path ) ; git ( "commit" , "--allow-empty" , "-m" , "init" ) ; rimraf ( tmpRepoPackagePath ) ; copySync ( realpathSync ( packagePath ) , tmpRepoPackagePath ) ; rimraf ( join ( tmpRepoPackagePath , "node_modules" ) ) ; rimraf ( join ( tmpRepoPackagePath , ".git" ) ) ; removeIgnoredFiles ( tmpRepoPackagePath , includePaths , excludePaths ) ; git ( "add" , "-f" , packageDetails . path ) ; const diffResult = git ( "diff" , "--cached" , "--no-color" , "--ignore-space-at-eol" , "--no-ext-diff" ) ; if ( diffResult . stdout . length === 0 ) { console . warn ( `⁉️  Not creating patch file for package '${ packagePathSpecifier }'` ) ; console . warn ( `⁉️  There don't appear to be any changes.` ) ; process . exit ( 1 ) ; return ; } try { parsePatchFile ( diffResult . stdout . toString ( ) ) ; } catch ( e ) { if ( ( e as Error ) . message . includes ( "Unexpected file mode string: 120000" ) ) { console . error ( `
⛔️ ${ chalk . red . bold ( "ERROR" ) }

  Your changes involve creating symlinks. patch-package does not yet support
  symlinks.
  
  ️Please use ${ chalk . bold ( "--include" ) } and/or ${ chalk . bold ( "--exclude" ) } to narrow the scope of your patch if
  this was unintentional.
` ) ; } else { const outPath = "./patch-package-error.json.gz" ; writeFileSync ( outPath , gzipSync ( JSON . stringify ( { error : { message : e . message , stack : e . stack } , patch : diffResult . stdout . toString ( ) , } ) ) ) ; console . error ( `
⛔️ ${ chalk . red . bold ( "ERROR" ) }
        
  patch-package was unable to read the patch-file made by git. This should not
  happen.
  
  A diagnostic file was written to
  
    ${ outPath }
  
  Please attach it to a github issue
  
    https://github.com/ds300/patch-package/issues/new?title=New+patch+parse+failed&body=Please+attach+the+diagnostic+file+by+dragging+it+into+here+🙏
  
  Note that this diagnostic file will contain code from the package you were
  attempting to patch.

` ) ; } process . exit ( 1 ) ; return ; } getPatchFiles ( patchDir ) . forEach ( ( filename ) => { const deets = getPackageDetailsFromPatchFilename ( filename ) ; if ( deets && deets . path === packageDetails . path ) { unlinkSync ( join ( patchDir , filename ) ) ; } } ) ; const patchFileName = createPatchFileName ( { packageDetails , packageVersion , } ) ; const patchPath = join ( patchesDir , patchFileName ) ; if ( ! existsSync ( dirname ( patchPath ) ) ) { mkdirSync ( dirname ( patchPath ) ) ; } writeFileSync ( patchPath , diffResult . stdout ) ; console . log ( `${ chalk . green ( "✔" ) } Created file ${ join ( patchDir , patchFileName ) }\n` ) ; if ( createIssue ) { openIssueCreationLink ( { packageDetails , patchFileContents : diffResult . stdout . toString ( ) , packageVersion , } ) ; } else { maybePrintIssueCreationPrompt ( packageDetails , packageManager ) ; } } catch ( e ) { console . error ( e ) ; throw e ; } finally { tmpRepo . removeCallback ( ) ; } } function createPatchFileName ( { packageDetails , packageVersion , } ) { const packageNames = packageDetails . packageNames . map ( ( name ) => name . replace ( /\//g , "+" ) ) . join ( "++" ) ; return `${ packageNames }+${ packageVersion }.patch` ; }