UNPKG

medusa-dev-cli

Version:
289 lines • 12.1 kB
const chokidar = require(`chokidar`); const _ = require(`lodash`); const del = require(`del`); const fs = require(`fs-extra`); const path = require(`path`); const findWorkspaceRoot = require(`find-yarn-workspace-root`); const { publishPackagesLocallyAndInstall } = require(`./local-npm-registry`); const { checkDepsChanges } = require(`./utils/check-deps-changes`); const { getDependantPackages } = require(`./utils/get-dependant-packages`); const { setDefaultSpawnStdio, promisifiedSpawn, } = require(`./utils/promisified-spawn`); const { traversePackagesDeps } = require(`./utils/traverse-package-deps`); let numCopied = 0; const quit = () => { console.log(`Copied ${numCopied} files`); process.exit(); }; const MAX_COPY_RETRIES = 3; /* * non-existent packages break on('ready') * See: https://github.com/paulmillr/chokidar/issues/449 */ async function watch(root, packages, { scanOnce, quiet, forceInstall, monoRepoPackages, localPackages, packageNameToPath, externalRegistry, }) { setDefaultSpawnStdio(quiet ? `ignore` : `inherit`); // determine if in yarn workspace - if in workspace, force using verdaccio // as current logic of copying files will not work correctly. const yarnWorkspaceRoot = findWorkspaceRoot(); if (yarnWorkspaceRoot && process.env.NODE_ENV !== `test`) { console.log(`Yarn workspace found.`); forceInstall = true; } let afterPackageInstallation = false; let queuedCopies = []; const realCopyPath = (arg) => { const { oldPath, newPath, quiet, resolve, reject, retry = 0 } = arg; fs.copy(oldPath, newPath, (err) => { if (err) { if (retry >= MAX_COPY_RETRIES) { console.error(err); reject(err); return; } else { setTimeout(() => realCopyPath({ ...arg, retry: retry + 1 }), 500 * Math.pow(2, retry)); return; } } // When the medusa binary is copied over, it is not setup with the executable // permissions that it is given when installed via yarn. // This fixes the issue where after running medusa-dev, running `yarn medusa develop` // fails with a permission issue. // @fixes https://github.com/medusajs/medusa/issues/18809 // Binary files we target: // - medusa/bin/medusa.js // -medusa/cli.js // -medusa-cli/cli.js if (/(bin\/medusa.js|medusa(-cli)?\/cli.js)$/.test(newPath)) { fs.chmodSync(newPath, `0755`); } numCopied += 1; if (!quiet) { console.log(`Copied ${oldPath} to ${newPath}`); } resolve(); }); }; const copyPath = (oldPath, newPath, quiet, packageName) => new Promise((resolve, reject) => { const argObj = { oldPath, newPath, quiet, packageName, resolve, reject }; if (afterPackageInstallation) { realCopyPath(argObj); } else { queuedCopies.push(argObj); } }); const runQueuedCopies = () => { afterPackageInstallation = true; queuedCopies.forEach((argObj) => realCopyPath(argObj)); queuedCopies = []; }; const clearJSFilesFromNodeModules = async () => { const packagesToClear = queuedCopies.reduce((acc, { packageName }) => { if (packageName) { acc.add(packageName); } return acc; }, new Set()); await Promise.all([...packagesToClear].map(async (packageToClear) => await del([ `node_modules/${packageToClear}/**/*.{js,js.map}`, `!node_modules/${packageToClear}/node_modules/**/*.{js,js.map}`, `!node_modules/${packageToClear}/src/**/*.{js,js.map}`, ]))); }; // check packages deps and if they depend on other packages from monorepo // add them to packages list const { seenPackages, depTree } = traversePackagesDeps({ root, packages: _.uniq(localPackages), monoRepoPackages, packageNameToPath, }); const allPackagesToWatch = packages ? _.intersection(packages, seenPackages) : seenPackages; const ignoredPackageJSON = new Map(); const ignorePackageJSONChanges = (packageName, contentArray) => { ignoredPackageJSON.set(packageName, contentArray); return () => { ignoredPackageJSON.delete(packageName); }; }; if (forceInstall) { try { if (allPackagesToWatch.length > 0) { await publishPackagesLocallyAndInstall({ packagesToPublish: allPackagesToWatch, packageNameToPath, localPackages, ignorePackageJSONChanges, yarnWorkspaceRoot, externalRegistry, root, }); } else { // run `yarn` const yarnInstallCmd = [`yarn`]; console.log(`Installing packages from public NPM registry`); await promisifiedSpawn(yarnInstallCmd); console.log(`Installation complete`); } } catch (e) { console.log(e); } process.exit(); } if (allPackagesToWatch.length === 0) { console.error(`There are no packages to watch.`); return; } const allPackagesIgnoringThemesToWatch = allPackagesToWatch.filter((pkgName) => !pkgName.startsWith(`medusa-theme`)); const ignored = [ /[/\\]node_modules[/\\]/i, /\.git/i, /\.DS_Store/, /[/\\]__tests__[/\\]/i, /[/\\]__mocks__[/\\]/i, /\.npmrc/i, ].concat(allPackagesIgnoringThemesToWatch.map((p) => new RegExp(`${p}[\\/\\\\]src[\\/\\\\]`, `i`))); const watchers = _.uniq(allPackagesToWatch .map((p) => path.join(packageNameToPath.get(p))) .filter((p) => fs.existsSync(p))); let allCopies = []; const packagesToPublish = new Set(); let isInitialScan = true; let isPublishing = false; const waitFor = new Set(); let anyPackageNotInstalled = false; const watchEvents = [`change`, `add`]; const packagePathMatchingEntries = Array.from(packageNameToPath.entries()); chokidar .watch(watchers, { ignored: [(filePath) => _.some(ignored, (reg) => reg.test(filePath))], }) .on(`all`, async (event, filePath) => { if (!watchEvents.includes(event)) { return; } // match against paths let packageName; for (const [_packageName, packagePath] of packagePathMatchingEntries) { const relativeToThisPackage = path.relative(packagePath, filePath); if (!relativeToThisPackage.startsWith(`..`)) { packageName = _packageName; break; } } if (!packageName) { return; } const prefix = packageNameToPath.get(packageName); // Copy it over local version. // Don't copy over the medusa bin file as that breaks the NPM symlink. if (_.includes(filePath, `dist/medusa-cli.js`)) { return; } const relativePackageFile = path.relative(prefix, filePath); const newPath = path.join(`./node_modules/${packageName}`, relativePackageFile); if (relativePackageFile === `package.json`) { // package.json files will change during publish to adjust version of package (and dependencies), so ignore // changes during this process if (isPublishing) { return; } // Compare dependencies with local version const didDepsChangedPromise = checkDepsChanges({ newPath, packageName, monoRepoPackages, packageNameToPath, isInitialScan, ignoredPackageJSON, }); if (isInitialScan) { // normally checkDepsChanges would be sync, // but because it also can do async GET request // to unpkg if local package is not installed // keep track of it to make sure all of it // finish before installing waitFor.add(didDepsChangedPromise); } const { didDepsChanged, packageNotInstalled } = await didDepsChangedPromise; if (packageNotInstalled) { anyPackageNotInstalled = true; } if (didDepsChanged) { if (isInitialScan) { waitFor.delete(didDepsChangedPromise); // handle dependency change only in initial scan - this is for sure doable to // handle this in watching mode correctly - but for the sake of shipping // this I limit more work/time consuming edge cases. // Dependency changed - now we need to figure out // the packages that actually need to be published. // If package with changed dependencies is dependency of other // medusa package - like for example `medusa-plugin-page-creator` // we need to publish both `medusa-plugin-page-creator` and `medusa` // and install `medusa` in example site project. getDependantPackages({ packageName, depTree, packages, }).forEach((packageToPublish) => { // scheduling publish - we will publish when `ready` is emitted // as we can do single publish then packagesToPublish.add(packageToPublish); }); } } // don't ever copy package.json as this will mess up any future dependency // changes checks return; } const localCopies = [copyPath(filePath, newPath, quiet, packageName)]; // If this is from "cache-dir" also copy it into the site's .cache if (_.includes(filePath, `cache-dir`)) { const newCachePath = path.join(`.cache/`, path.relative(path.join(prefix, `cache-dir`), filePath)); localCopies.push(copyPath(filePath, newCachePath, quiet)); } allCopies = allCopies.concat(localCopies); }) .on(`ready`, async () => { // wait for all async work needed to be done // before publishing / installing await Promise.all(Array.from(waitFor)); if (isInitialScan) { isInitialScan = false; if (packagesToPublish.size > 0) { isPublishing = true; await publishPackagesLocallyAndInstall({ packagesToPublish: Array.from(packagesToPublish), packageNameToPath, localPackages, ignorePackageJSONChanges, externalRegistry, root, }); packagesToPublish.clear(); isPublishing = false; } else if (anyPackageNotInstalled) { // run `yarn` const yarnInstallCmd = [`yarn`]; console.log(`Installing packages from public NPM registry`); await promisifiedSpawn(yarnInstallCmd); console.log(`Installation complete`); } await clearJSFilesFromNodeModules(); runQueuedCopies(); } // all files watched, quit once all files are copied if necessary Promise.all(allCopies).then(() => { if (scanOnce) { quit(); } }); }); } module.exports = watch; //# sourceMappingURL=watch.js.map