import fs from 'node:fs'
import path from 'node:path'
import colors from 'picocolors'
import confirm from '@inquirer/confirm'
import { invariant } from './invariant.js'
import { SERVICE_WORKER_BUILD_PATH } from '../config/constants.js'
export 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) {
await copyWorkerScript(publicDir, CWD)
const relativePublicDir = path.relative(CWD, publicDir)
printSuccessMessage([publicDir])
if (args.save) {
if (!savedWorkerDirectories.includes(relativePublicDir)) {
saveWorkerDirectory(packageJsonPath, relativePublicDir)
}
}
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.`)
promptWorkerDirectoryUpdate(
`Do you wish to save "${relativePublicDir}" as the worker directory?`,
packageJsonPath,
relativePublicDir,
)
}
return
}
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 (savedWorkerDirectories.length > 0) {
const copyResults = await Promise.allSettled(
savedWorkerDirectories.map((destination) => {
return copyWorkerScript(destination, CWD).catch((error) => {
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)
if (failedPathsWithErrors.length > 0) {
printFailureMessage(failedPathsWithErrors)
}
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) {
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/quick-start'))}
`)
}
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)
}