Spaces:
Running
Running
const fs = require('fs') | |
const path = require('path') | |
const colors = require('picocolors') | |
const confirm = require('@inquirer/confirm').default | |
const invariant = require('./invariant') | |
const { SERVICE_WORKER_BUILD_PATH } = require('../config/constants') | |
module.exports = async function init(args) { | |
const CWD = args.cwd || process.cwd() | |
const publicDir = args._[1] ? normalizePath(args._[1]) : undefined | |
const packageJsonPath = path.resolve(CWD, 'package.json') | |
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) | |
const savedWorkerDirectories = Array.prototype | |
.concat((packageJson.msw && packageJson.msw.workerDirectory) || []) | |
.map(normalizePath) | |
if (publicDir) { | |
// If the public directory was provided, copy the worker script | |
// to that directory only. Even if there are paths stored in "msw.workerDirectory", | |
// those will not be touched. | |
await copyWorkerScript(publicDir, CWD) | |
const relativePublicDir = path.relative(CWD, publicDir) | |
printSuccessMessage([publicDir]) | |
if (args.save) { | |
// Only save the public path if it's not already saved in "package.json". | |
if (!savedWorkerDirectories.includes(relativePublicDir)) { | |
saveWorkerDirectory(packageJsonPath, relativePublicDir) | |
} | |
} | |
// Explicitly check if "save" was not provided (was null). | |
// You can also provide the "--no-save" option, and then "args.save" | |
// will equal to false. | |
else if (args.save == null) { | |
console.log(`\ | |
${colors.cyan( | |
'INFO', | |
)} In order to ease the future updates to the worker script, | |
we recommend saving the path to the worker directory in your package.json.`) | |
// If the "--save" flag was not provided, prompt to save | |
// the public path. | |
promptWorkerDirectoryUpdate( | |
`Do you wish to save "${relativePublicDir}" as the worker directory?`, | |
packageJsonPath, | |
relativePublicDir, | |
) | |
} | |
return | |
} | |
// Calling "init" without a public directory but with the "--save" flag is a no-op. | |
invariant( | |
args.save == null, | |
'Failed to copy the worker script: cannot call the "init" command without a public directory but with the "--save" flag. Either drop the "--save" flag to copy the worker script to all paths listed in "msw.workerDirectory", or add an explicit public directory to the command, like "npx msw init ./public".', | |
) | |
// If the public directory was not provided, check any existing | |
// paths in "msw.workerDirectory". When called without the public | |
// directory, the "init" command must copy the worker script | |
// to all the paths stored in "msw.workerDirectory". | |
if (savedWorkerDirectories.length > 0) { | |
const copyResults = await Promise.allSettled( | |
savedWorkerDirectories.map((destination) => { | |
return copyWorkerScript(destination, CWD).catch((error) => { | |
// Inject the absolute destination path onto the copy function rejections | |
// so it's available in the failed paths array below. | |
throw [toAbsolutePath(destination, CWD), error] | |
}) | |
}), | |
) | |
const successfulPaths = copyResults | |
.filter((result) => result.status === 'fulfilled') | |
.map((result) => result.value) | |
const failedPathsWithErrors = copyResults | |
.filter((result) => result.status === 'rejected') | |
.map((result) => result.reason) | |
// Notify about failed copies, if any. | |
if (failedPathsWithErrors.length > 0) { | |
printFailureMessage(failedPathsWithErrors) | |
} | |
// Notify about successful copies, if any. | |
if (successfulPaths.length > 0) { | |
printSuccessMessage(successfulPaths) | |
} | |
} | |
} | |
/** | |
* @param {string} maybeAbsolutePath | |
* @param {string} cwd | |
* @returns {string} | |
*/ | |
function toAbsolutePath(maybeAbsolutePath, cwd) { | |
return path.isAbsolute(maybeAbsolutePath) | |
? maybeAbsolutePath | |
: path.resolve(cwd, maybeAbsolutePath) | |
} | |
/** | |
* @param {string} destination | |
* @param {string} cwd | |
* @returns {Promise<string>} | |
*/ | |
async function copyWorkerScript(destination, cwd) { | |
// When running as a part of "postinstall" script, "cwd" equals the library's directory. | |
// The "postinstall" script resolves the right absolute public directory path. | |
const absolutePublicDir = toAbsolutePath(destination, cwd) | |
if (!fs.existsSync(absolutePublicDir)) { | |
await fs.promises | |
.mkdir(absolutePublicDir, { recursive: true }) | |
.catch((error) => { | |
throw new Error( | |
invariant( | |
false, | |
'Failed to copy the worker script at "%s": directory does not exist and could not be created.\nMake sure to include a relative path to the public directory of your application.\n\nSee the original error below:\n\n%s', | |
absolutePublicDir, | |
error, | |
), | |
) | |
}) | |
} | |
console.log('Copying the worker script at "%s"...', absolutePublicDir) | |
const workerFilename = path.basename(SERVICE_WORKER_BUILD_PATH) | |
const workerDestinationPath = path.resolve(absolutePublicDir, workerFilename) | |
fs.copyFileSync(SERVICE_WORKER_BUILD_PATH, workerDestinationPath) | |
return workerDestinationPath | |
} | |
/** | |
* @param {Array<string>} paths | |
*/ | |
function printSuccessMessage(paths) { | |
console.log(` | |
${colors.green('Worker script successfully copied!')} | |
${paths.map((path) => colors.gray(` - ${path}\n`))} | |
Continue by describing the network in your application: | |
${colors.red(colors.bold('https://mswjs.io/docs/getting-started'))} | |
`) | |
} | |
function printFailureMessage(pathsWithErrors) { | |
console.error(`\ | |
${colors.red('Copying the worker script failed at following paths:')} | |
${pathsWithErrors | |
.map(([path, error]) => colors.gray(` - ${path}`) + '\n' + ` ${error}`) | |
.join('\n\n')} | |
`) | |
} | |
/** | |
* @param {string} packageJsonPath | |
* @param {string} publicDir | |
*/ | |
function saveWorkerDirectory(packageJsonPath, publicDir) { | |
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) | |
console.log( | |
colors.gray('Updating "msw.workerDirectory" at "%s"...'), | |
packageJsonPath, | |
) | |
const prevWorkerDirectory = Array.prototype.concat( | |
(packageJson.msw && packageJson.msw.workerDirectory) || [], | |
) | |
const nextWorkerDirectory = Array.from( | |
new Set(prevWorkerDirectory).add(publicDir), | |
) | |
const nextPackageJson = Object.assign({}, packageJson, { | |
msw: { | |
workerDirectory: nextWorkerDirectory, | |
}, | |
}) | |
fs.writeFileSync( | |
packageJsonPath, | |
JSON.stringify(nextPackageJson, null, 2), | |
'utf8', | |
) | |
} | |
/** | |
* @param {string} message | |
* @param {string} packageJsonPath | |
* @param {string} publicDir | |
* @returns {void} | |
*/ | |
function promptWorkerDirectoryUpdate(message, packageJsonPath, publicDir) { | |
return confirm({ | |
theme: { | |
prefix: colors.yellowBright('?'), | |
}, | |
message, | |
}).then((answer) => { | |
if (answer) { | |
saveWorkerDirectory(packageJsonPath, publicDir) | |
} | |
}) | |
} | |
/** | |
* Normalizes the given path, replacing ambiguous path separators | |
* with the platform-specific path separator. | |
* @param {string} input Path to normalize. | |
* @returns {string} | |
*/ | |
function normalizePath(input) { | |
return input.replace(/[\\|\/]+/g, path.sep) | |
} | |