2025-02-25 08:07:03 +08:00
import { spawnSync } from "node:child_process" ;
2024-12-16 11:19:18 +08:00
import crypto from "node:crypto" ;
2021-11-29 10:07:11 +08:00
import fs from "node:fs" ;
2024-12-16 11:19:18 +08:00
import os from "node:os" ;
2023-12-17 19:34:12 +00:00
import path from "node:path" ;
2025-05-06 10:34:05 +08:00
import { performance } from "node:perf_hooks" ;
2023-12-17 19:34:12 +00:00
import { outdent } from "outdent" ;
2025-03-10 11:48:01 +08:00
import picocolors from "picocolors" ;
2024-12-16 11:19:18 +08:00
2025-05-21 11:58:26 +08:00
const isWindows = process . platform === "win32" ;
// https://github.com/sindresorhus/nano-spawn/blob/7f3fbe6590eec44f7e90f7735d173258dd80b420/source/windows.js#L71
const escapeFile = isWindows
2025-12-21 23:44:08 +08:00
? ( file ) => file . replace ( /([()\][%!^"`<>&|;, *?])/g , "^$1" )
2025-05-21 11:58:26 +08:00
: ( file ) => file ;
// https://github.com/sindresorhus/nano-spawn/blob/7f3fbe6590eec44f7e90f7735d173258dd80b420/source/windows.js#L66
const escapeArgument = isWindows
? ( argument ) =>
escapeFile (
escapeFile (
`" ${ argument
2025-12-21 23:44:08 +08:00
. replace ( /(\\*)"/g , String . raw `$1$1\"` )
. replace ( /(\\*)$/ , "$1$1" ) } "` ,
2025-05-21 11:58:26 +08:00
),
)
: ( argument ) => argument ;
const spawn = ( command , args , options ) =>
spawnSync (
[
escapeFile ( command ),
... args . map (( argument ) => escapeArgument ( argument )),
]. join ( " " ),
{
... options ,
shell : true ,
},
);
2024-12-16 11:19:18 +08:00
const createTemporaryDirectory = () => {
const directory = path . join (
2025-02-25 08:07:03 +08:00
// The following quoted from https://github.com/es-tooling/module-replacements/blob/27d1acd38f19741e31d2eae561a5c8a914373fc5/docs/modules/tempy.md?plain=1#L20-L21, not sure if it's true
// MacOS and possibly some other platforms return a symlink from `os.tmpdir`.
// For some applications, this can cause problems; thus, we use `realpath`.
fs . realpathSync ( os . tmpdir ()),
2024-12-16 11:19:18 +08:00
crypto . randomBytes ( 16 ). toString ( "hex" ),
);
fs . mkdirSync ( directory );
return directory ;
};
2021-09-09 18:53:31 +08:00
const allowedClients = new Set ([ "yarn" , "npm" , "pnpm" ]);
2025-05-06 10:34:05 +08:00
let client = process . env . PRETTIER_INSTALL_NPM_CLIENT ;
2021-09-09 18:53:31 +08:00
if ( ! allowedClients . has ( client )) {
client = "yarn" ;
}
2022-02-11 17:25:51 +08:00
const directoriesToClean = new Set ();
2025-04-21 18:58:44 +08:00
process . once ( "exit" , cleanUp );
2022-02-11 17:25:51 +08:00
function cleanUp () {
if ( directoriesToClean . size === 0 ) {
return ;
}
2025-03-10 11:48:01 +08:00
console . log ( picocolors . green ( "Removing installed Prettier:" ));
2022-02-11 17:25:51 +08:00
for ( const directory of directoriesToClean ) {
// Node.js<14 don't support `fs.rmSync`
try {
fs . rmSync ( directory , { force : true , recursive : true });
} catch {
// No op
}
if ( fs . existsSync ( directory )) {
2025-03-10 11:48:01 +08:00
console . error ( picocolors . red ( ` - ${ picocolors . inverse ( directory ) } FAIL` ));
2022-02-11 17:25:51 +08:00
} else {
2025-03-10 11:48:01 +08:00
console . log ( picocolors . green ( ` - ${ picocolors . inverse ( directory ) } DONE` ));
2022-02-11 17:25:51 +08:00
}
}
}
2022-10-18 10:01:09 +08:00
function installPrettier ( packageDirectory ) {
2025-05-06 10:34:05 +08:00
const start = performance . now ();
2022-10-18 10:01:09 +08:00
const temporaryDirectory = createTemporaryDirectory ();
directoriesToClean . add ( temporaryDirectory );
2025-05-21 11:58:26 +08:00
const fileName = spawn ( "npm" , [ "pack" ], {
2022-10-18 10:01:09 +08:00
cwd : packageDirectory ,
2025-02-25 08:07:03 +08:00
encoding : "utf8" ,
2021-11-29 10:07:11 +08:00
}). stdout . trim ();
2022-10-18 10:01:09 +08:00
const file = path . join ( packageDirectory , fileName );
const packed = path . join ( temporaryDirectory , fileName );
2021-09-09 18:53:31 +08:00
fs . copyFileSync ( file , packed );
fs . unlinkSync ( file );
2022-10-18 10:01:09 +08:00
const runNpmClient = ( args ) =>
2025-05-21 11:58:26 +08:00
spawn ( client , args , { cwd : temporaryDirectory });
2022-05-06 20:27:33 +08:00
runNpmClient ( client === "pnpm" ? [ "init" ] : [ "init" , "-y" ]);
2021-09-09 18:53:31 +08:00
switch ( client ) {
case "npm" :
// npm fails when engine requirement only with `--engine-strict`
2022-05-06 20:27:33 +08:00
runNpmClient ([ "install" , packed , "--engine-strict" ]);
2021-09-09 18:53:31 +08:00
break ;
case "pnpm" :
// Note: current pnpm can't work with `--engine-strict` and engineStrict setting in `.npmrc`
2022-05-06 20:27:33 +08:00
runNpmClient ([ "add" , packed , "--engine-strict" ]);
2021-09-09 18:53:31 +08:00
break ;
2022-05-06 20:27:33 +08:00
case "yarn" :
2021-09-09 18:53:31 +08:00
// yarn fails when engine requirement not compatible by default
2022-05-06 20:27:33 +08:00
runNpmClient ([ "config" , "set" , "nodeLinker" , "node-modules" ]);
runNpmClient ([ "add" , `prettier@file: ${ packed } ` ]);
// No default
2021-09-09 18:53:31 +08:00
}
fs . unlinkSync ( packed );
2022-02-11 17:25:51 +08:00
console . log (
2025-03-10 11:48:01 +08:00
picocolors . green (
2022-03-17 20:13:28 +08:00
outdent `
Prettier installed
2025-05-06 10:34:05 +08:00
at ${ picocolors . inverse ( temporaryDirectory ) }
2025-03-10 11:48:01 +08:00
from ${ picocolors . inverse ( packageDirectory ) }
2025-05-06 10:34:05 +08:00
with ${ picocolors . inverse ( client ) }
in ${ picocolors . inverse ( ` ${ performance . now () - start } ms` ) } .
2022-02-11 17:25:51 +08:00
` ,
),
);
2022-10-18 10:01:09 +08:00
fs . writeFileSync (
path . join ( temporaryDirectory , "index-proxy.mjs" ),
"export * from 'prettier';" ,
);
return temporaryDirectory ;
2021-11-29 10:07:11 +08:00
}
export default installPrettier ;