sam522's picture
node
d4b85c0
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)
}