chore(): start moving some packages to the core directory (#7215)

This commit is contained in:
Adrien de Peretti
2024-05-03 13:37:41 +02:00
committed by GitHub
parent fdee748eed
commit bbccd6481d
1436 changed files with 275 additions and 756 deletions

33
packages/cli/medusa-dev-cli/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
decls
dist
# verdaccio local storage
verdaccio

View File

@@ -0,0 +1,157 @@
# Change Log
## 0.0.32
### Patch Changes
- [#4421](https://github.com/medusajs/medusa/pull/4421) [`5a4580b6a`](https://github.com/medusajs/medusa/commit/5a4580b6a8c305b7803c8d929843ca1f39677404) Thanks [@adrien2p](https://github.com/adrien2p)! - chore(medusa-dev-cli): Cleanup plugin setup
## 0.0.31
### Patch Changes
- [#3293](https://github.com/medusajs/medusa/pull/3293) [`e8e7d7bb5`](https://github.com/medusajs/medusa/commit/e8e7d7bb5355877b3d0b85a079b6b57cfe7391c9) Thanks [@patrick-medusajs](https://github.com/patrick-medusajs)! - fix(medusa-dev): include packages/ subdirectories in discovery
## 0.0.31-rc.0
### Patch Changes
- [#3293](https://github.com/medusajs/medusa/pull/3293) [`e8e7d7bb5`](https://github.com/medusajs/medusa/commit/e8e7d7bb5355877b3d0b85a079b6b57cfe7391c9) Thanks [@patrick-medusajs](https://github.com/patrick-medusajs)! - fix(medusa-dev): include packages/ subdirectories in discovery
## 0.0.30
### Patch Changes
- [#3217](https://github.com/medusajs/medusa/pull/3217) [`8c5219a31`](https://github.com/medusajs/medusa/commit/8c5219a31ef76ee571fbce84d7d57a63abe56eb0) Thanks [@adrien2p](https://github.com/adrien2p)! - chore: Fix npm packages files included
## 0.0.29
### Patch Changes
- [#3185](https://github.com/medusajs/medusa/pull/3185) [`08324355a`](https://github.com/medusajs/medusa/commit/08324355a4466b017a0bc7ab1d333ee3cd27b8c4) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore: Patches all dependencies + minor bumps `winston` to include a [fix for a significant memory leak](https://github.com/winstonjs/winston/pull/2057)
## 0.0.28
### Patch Changes
- [#3025](https://github.com/medusajs/medusa/pull/3025) [`93d0dc1bd`](https://github.com/medusajs/medusa/commit/93d0dc1bdcb54cf6e87428a7bb9b0dac196b4de2) Thanks [@adrien2p](https://github.com/adrien2p)! - fix(medusa): test, build and watch scripts
## 0.0.27
### Patch Changes
- [#2360](https://github.com/medusajs/medusa/pull/2360) [`196595cb6`](https://github.com/medusajs/medusa/commit/196595cb651d058b7da8711604ba57e5c0ee8a55) Thanks [@srindom](https://github.com/srindom)! - Avoid dev cli auth
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.0.26](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.24...medusa-dev-cli@0.0.26) (2022-07-05)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.25](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.24...medusa-dev-cli@0.0.25) (2022-07-05)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.24](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.23...medusa-dev-cli@0.0.24) (2021-12-08)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.23](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.22...medusa-dev-cli@0.0.23) (2021-11-11)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.22](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.20...medusa-dev-cli@0.0.22) (2021-10-18)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.21](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.20...medusa-dev-cli@0.0.21) (2021-10-18)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.20](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.19...medusa-dev-cli@0.0.20) (2021-09-15)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.19](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.18...medusa-dev-cli@0.0.19) (2021-09-14)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.18](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.17...medusa-dev-cli@0.0.18) (2021-07-26)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.17](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.15...medusa-dev-cli@0.0.17) (2021-07-15)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.16](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.15...medusa-dev-cli@0.0.16) (2021-07-15)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.15](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.14...medusa-dev-cli@0.0.15) (2021-07-02)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.14](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.13...medusa-dev-cli@0.0.14) (2021-06-22)
### Bug Fixes
- release assist ([668e8a7](https://github.com/medusajs/medusa/commit/668e8a740200847fc2a41c91d2979097f1392532))
## [0.0.13](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.12...medusa-dev-cli@0.0.13) (2021-06-09)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.12](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.11...medusa-dev-cli@0.0.12) (2021-06-09)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.11](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.10...medusa-dev-cli@0.0.11) (2021-06-09)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.10](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.9...medusa-dev-cli@0.0.10) (2021-06-09)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.9](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.8...medusa-dev-cli@0.0.9) (2021-06-08)
### Bug Fixes
- babel ([81960d5](https://github.com/medusajs/medusa/commit/81960d51812f093e04271f50ffe5de9bce17c06b))
## [0.0.8](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.5...medusa-dev-cli@0.0.8) (2021-04-28)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.7](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.6...medusa-dev-cli@0.0.7) (2021-04-20)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.6](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.5...medusa-dev-cli@0.0.6) (2021-04-20)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.5](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.4...medusa-dev-cli@0.0.5) (2021-04-13)
### Bug Fixes
- working ([a9b1d75](https://github.com/medusajs/medusa/commit/a9b1d75074d2786df6dfca9064b3d9657a664d6d))
- yarn lon ([3c49467](https://github.com/medusajs/medusa/commit/3c4946762c25220c18913f46537f777a55a209ec))
## [0.0.4](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.3...medusa-dev-cli@0.0.4) (2021-03-17)
**Note:** Version bump only for package medusa-dev-cli
## 0.0.3 (2021-03-17)
### Features
- dev cli ([#203](https://github.com/medusajs/medusa/issues/203)) ([695b1fd](https://github.com/medusajs/medusa/commit/695b1fd0a54a247502cb48ffb73d060356293b76)), closes [#199](https://github.com/medusajs/medusa/issues/199)
## 0.0.2 (2021-03-17)
### Features
- dev cli ([#203](https://github.com/medusajs/medusa/issues/203)) ([695b1fd](https://github.com/medusajs/medusa/commit/695b1fd0a54a247502cb48ffb73d060356293b76)), closes [#199](https://github.com/medusajs/medusa/issues/199)

View File

@@ -0,0 +1,74 @@
# medusa-dev-cli
A command-line tool for local Medusa development. When doing development work on
Medusa core, this tool allows you to copy the changes to the various
Medusa packages to Medusa projects.
## Install
`npm install -g medusa-dev-cli`
## Configuration / First time setup
The medusa-dev-cli tool needs to know where your cloned Medusa repository is
located. You typically only need to configure this once.
`medusa-dev --set-path-to-repo /path/to/my/cloned/version/medusa`
## How to use
Navigate to the project you want to link to your forked Medusa repository and
run:
`medusa-dev`
The tool will then scan your project's package.json to find its Medusa
dependencies and copy the latest source from your cloned version of Medusa into
your project's node_modules folder. A watch task is then created to re-copy any
modules that might change while you're working on the code, so you can leave
this program running.
Typically you'll also want to run `npm run watch` in the Medusa repo to set up
watchers to build Medusa source code.
## Revert to current packages
If you've recently run `medusa-dev` your `node_modules` will be out of sync with current published packages. In order to undo this, you can remove the `node_modules` directory or run:
```shell
git checkout package.json; yarn --force
```
or
```shell
git checkout package.json; npm install --force
```
### Other commands
#### `--packages`
You can prevent the automatic dependencies scan and instead specify a list of
packages you want to link by using the `--packages` option:
`medusa-dev --packages @medusajs/medusa medusa-interfaces`
#### `--scan-once`
With this flag, the tool will do an initial scan and copy and then quit. This is
useful for setting up automated testing/builds of Medusa projects from the latest
code.
#### `--quiet`
Don't output anything except for a success message when used together with
`--scan-once`.
#### `--copy-all`
Copy all modules/files in the medusa source repo in packages/
#### `--force-install`
Disables copying files into node_modules and forces usage of local npm repository.

View File

@@ -0,0 +1,13 @@
module.exports = {
globals: {
"ts-jest": {
tsconfig: "tsconfig.spec.json",
isolatedModules: false,
},
},
transform: {
"^.+\\.[jt]s?$": "ts-jest",
},
testEnvironment: `node`,
moduleFileExtensions: [`js`, `jsx`, `ts`, `tsx`, `json`],
}

View File

@@ -0,0 +1,54 @@
{
"name": "medusa-dev-cli",
"description": "CLI helpers for contributors working on Medusa",
"version": "0.0.32",
"author": "Sebastian Rindom <skrindom@gmail.com>",
"bin": {
"medusa-dev": "./dist/index.js"
},
"files": [
"dist"
],
"dependencies": {
"chokidar": "^3.5.3",
"configstore": "^5.0.1",
"del": "^6.0.0",
"execa": "^4.1.0",
"find-yarn-workspace-root": "^2.0.0",
"fs-extra": "^9.0.1",
"glob": "^8.1.0",
"got": "^11.8.6",
"is-absolute": "^1.0.0",
"lodash": "^4.17.21",
"signal-exit": "^3.0.7",
"verdaccio": "^4.10.0",
"yargs": "^15.4.1"
},
"devDependencies": {
"cross-env": "^7.0.3",
"jest": "^25.5.4",
"ts-jest": "^25.5.1",
"typescript": "^4.4.4"
},
"homepage": "https://github.com/medusajs/medusa/tree/master/packages/medusa-dev-cli#readme",
"keywords": [
"medusa"
],
"license": "MIT",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa.git",
"directory": "packages/medusa-dev-cli"
},
"scripts": {
"prepublishOnly": "cross-env NODE_ENV=production tsc --build",
"test": "jest --passWithNoTests -- src",
"build": "tsc",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16"
},
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}

View File

@@ -0,0 +1,178 @@
import path from "path"
import glob from "glob"
import fs from "fs"
import Configstore from "configstore"
import { kebabCase, snakeCase } from "lodash"
import { featureFlagTemplate } from "./template"
import pkg from "../../package.json"
export const buildFFCli = (cli) => {
cli.command({
command: `ff`,
desc: "Manage Medusa feature flags",
builder: (yargs) => {
yargs
.command({
command: "create <name>",
desc: "Create a new feature flag",
builder: {
name: {
demandOption: true,
coerce: (name) => kebabCase(name),
description: "Name of the feature flag",
type: "string",
},
description: {
alias: "d",
demandOption: true,
description: "Description of the feature flag",
type: "string",
},
},
handler: async (argv) => {
const medusaLocation = getRepoRoot()
const featureFlagPath = buildPath(argv.name, medusaLocation)
if (fs.existsSync(featureFlagPath)) {
console.error(`Feature flag already exists: ${featureFlagPath}`)
return
}
const flagSettings = collectSettings(argv.name, argv.description)
writeFeatureFlag(flagSettings, featureFlagPath)
},
})
.command({
command: "list",
desc: "List available feature flags",
handler: async () => {
const medusaLocation = getRepoRoot()
const flagGlob = buildFlagsGlob(medusaLocation)
const featureFlags = glob.sync(flagGlob, {
ignore: ["**/index.*"],
})
const flagData = featureFlags.map((flag) => {
const flagSettings = readFeatureFlag(flag)
return {
...flagSettings,
file_name: path.basename(flag, ".js"),
}
})
console.table(flagData)
},
})
.command({
command: "delete <name>",
desc: "Delete a feature flag",
builder: {
name: {
demand: true,
coerce: (name) => kebabCase(name),
description: "Name of the feature flag",
type: "string",
},
},
handler: async (argv) => {
const medusaLocation = getRepoRoot()
const featureFlagPath = buildPath(argv.name, medusaLocation)
if (fs.existsSync(featureFlagPath)) {
fs.unlinkSync(featureFlagPath)
}
console.log(`Feature flag deleted: ${featureFlagPath}`)
},
})
.demandCommand(1, "Please specify an action")
},
})
}
const getRepoRoot = () => {
const conf = new Configstore(pkg.name)
const medusaLocation = conf.get(`medusa-location`)
if (!medusaLocation) {
console.error(
`
You haven't set the path yet to your cloned
version of medusa. Do so now by running:
medusa-dev --set-path-to-repo /path/to/my/cloned/version/medusa
`
)
process.exit()
}
return medusaLocation
}
const readFeatureFlag = (flagPath) => {
const flagSettings = require(flagPath).default
return flagSettings
}
const buildFlagsGlob = (repoRoot) => {
return path.join(
repoRoot,
"packages",
"medusa",
"dist",
"loaders",
"feature-flags",
`*.js`
)
}
const buildPath = (kebabCaseName, repoRoot) => {
return path.join(
repoRoot,
"packages",
"medusa",
"src",
"loaders",
"feature-flags",
`${kebabCaseName}.ts`
)
}
const collectSettings = (name, description) => {
const snakeCaseName = snakeCase(name)
return {
key: snakeCaseName,
description: description,
defaultValue: false,
envKey: `MEDUSA_FF_${snakeCaseName.toUpperCase()}`,
}
}
const writeFeatureFlag = (settings, featureFlagPath) => {
const featureFlag = featureFlagTemplate(settings)
fs.writeFileSync(featureFlagPath, featureFlag)
logFeatureFlagUsage(featureFlagPath, settings)
}
const logFeatureFlagUsage = (flagPath, flagSettings) => {
console.log(`Feature flag created: ${flagPath}`)
console.log(`
To use this feature flag, add the following to your medusa-config.js:
{
...,
featureFlags: {
${flagSettings.key}: true
}
}
or set the environment variable:
export ${flagSettings.envKey}=true
To add guarded code use the featureFlagRouter:
if (featureFlagRouter.isEnabled("${flagSettings.key}")) {
// do something
}
`)
}

View File

@@ -0,0 +1,13 @@
export const featureFlagTemplate = ({
key,
description,
defaultValue,
envKey,
}) => {
return `export default {
key: "${key}",
description: "${description}",
default_value: ${defaultValue},
env_key: "${envKey}",
}`
}

View File

@@ -0,0 +1,184 @@
#!/usr/bin/env node
const Configstore = require(`configstore`)
const pkg = require(`../package.json`)
const _ = require(`lodash`)
const yargs = require(`yargs/yargs`)
const path = require(`path`)
const os = require(`os`)
const fs = require(`fs-extra`)
const glob = require("glob")
const watch = require(`./watch`)
const { getVersionInfo } = require(`./utils/version`)
const { buildFFCli } = require("./feature-flags")
const cli = yargs()
cli.command({
command: `*`,
description: `Start the Medusa dev CLI`,
builder: (yargs) => {
yargs
.usage(`Usage: medusa-dev [options]`)
.alias(`q`, `quiet`)
.nargs(`q`, 0)
.describe(`q`, `Do not output copy file information`)
.alias(`s`, `scan-once`)
.nargs(`s`, 0)
.describe(`s`, `Scan once. Do not start file watch`)
.alias(`p`, `set-path-to-repo`)
.nargs(`p`, 1)
.describe(
`p`,
`Set path to medusa repository.
You typically only need to configure this once.`
)
.nargs(`force-install`, 0)
.describe(
`force-install`,
`Disables copying files into node_modules and forces usage of local npm repository.`
)
.nargs(`external-registry`, 0)
.describe(
`external-registry`,
`Run 'yarn add' commands without the --registry flag.`
)
.alias(`C`, `copy-all`)
.nargs(`C`, 0)
.describe(
`C`,
`Copy all contents in packages/ instead of just medusa packages`
)
.array(`packages`)
.describe(`packages`, `Explicitly specify packages to copy`)
.help(`h`)
.alias(`h`, `help`)
.nargs(`v`, 0)
.alias(`v`, `version`)
.describe(`v`, `Print the currently installed version of Medusa Dev CLI`)
},
handler: async (argv) => {
const conf = new Configstore(pkg.name)
if (argv.version) {
console.log(getVersionInfo())
process.exit()
}
let pathToRepo = argv.setPathToRepo
if (pathToRepo) {
if (pathToRepo.includes(`~`)) {
pathToRepo = path.join(os.homedir(), pathToRepo.split(`~`).pop())
}
conf.set(`medusa-location`, path.resolve(pathToRepo))
process.exit()
}
const havePackageJsonFile = fs.existsSync(`package.json`)
if (!havePackageJsonFile) {
console.error(`Current folder must have a package.json file!`)
process.exit()
}
const medusaLocation = conf.get(`medusa-location`)
if (!medusaLocation) {
console.error(
`
You haven't set the path yet to your cloned
version of medusa. Do so now by running:
medusa-dev --set-path-to-repo /path/to/my/cloned/version/medusa
`
)
process.exit()
}
// get list of directories to crawl for package declarations
const monoRepoPackagesDirs = []
try {
const monoRepoPkg = JSON.parse(
fs.readFileSync(path.join(medusaLocation, "package.json"))
)
for (const workspace of monoRepoPkg.workspaces.packages) {
if (!workspace.startsWith("packages")) {
continue
}
const workspacePackageDirs = glob.sync(workspace, {
cwd: medusaLocation.toString(),
})
monoRepoPackagesDirs.push(...workspacePackageDirs)
}
} catch (err) {
console.error(
`Unable to read and parse the workspace definition from medusa package.json`
)
process.exit(1)
}
// get list of packages from monorepo
const packageNameToPath = new Map()
const monoRepoPackages = monoRepoPackagesDirs.map((dirName) => {
try {
const localPkg = JSON.parse(
fs.readFileSync(path.join(medusaLocation, dirName, `package.json`))
)
if (localPkg?.name) {
packageNameToPath.set(
localPkg.name,
path.join(medusaLocation, dirName)
)
return localPkg.name
}
} catch (error) {
// fallback to generic one
}
packageNameToPath.set(dirName, path.join(medusaLocation, dirName))
return dirName
})
const localPkg = JSON.parse(fs.readFileSync(`package.json`))
// intersect dependencies with monoRepoPackages to get list of packages that are used
const localPackages = _.intersection(
monoRepoPackages,
Object.keys(_.merge({}, localPkg.dependencies, localPkg.devDependencies))
)
if (!argv.packages && _.isEmpty(localPackages)) {
console.error(
`
You haven't got any medusa dependencies into your current package.json
You probably want to pass in a list of packages to start
developing on! For example:
medusa-dev --packages medusa medusa-js
If you prefer to place them in your package.json dependencies instead,
medusa-dev will pick them up.
`
)
if (!argv.forceInstall) {
process.exit()
} else {
console.log(
`Continuing other dependencies installation due to "--forceInstall" flag`
)
}
}
watch(medusaLocation, argv.packages, {
localPackages,
quiet: argv.quiet,
scanOnce: argv.scanOnce,
forceInstall: argv.forceInstall,
monoRepoPackages,
packageNameToPath,
externalRegistry: argv.externalRegistry,
})
},
})
buildFFCli(cli)
cli.parse(process.argv.slice(2))

View File

@@ -0,0 +1,19 @@
const signalExit = require(`signal-exit`);
const cleanupTasks = new Set();
exports.registerCleanupTask = (taskFn) => {
cleanupTasks.add(taskFn);
return () => {
const result = taskFn();
cleanupTasks.delete(taskFn);
return result;
};
};
signalExit(() => {
if (cleanupTasks.size) {
console.log(`Process exitted in middle of publishing - cleaning up`);
cleanupTasks.forEach((taskFn) => taskFn());
}
});

View File

@@ -0,0 +1,79 @@
const startVerdaccio = require(`verdaccio`).default
const fs = require(`fs-extra`)
const _ = require(`lodash`)
let VerdaccioInitPromise = null
const { verdaccioConfig } = require(`./verdaccio-config`)
const { publishPackage } = require(`./publish-package`)
const { installPackages } = require(`./install-packages`)
const startServer = () => {
if (VerdaccioInitPromise) {
return VerdaccioInitPromise
}
console.log(`Starting local verdaccio server`)
// clear storage
fs.removeSync(verdaccioConfig.storage)
VerdaccioInitPromise = new Promise((resolve) => {
startVerdaccio(
verdaccioConfig,
verdaccioConfig.port,
verdaccioConfig.storage,
`1.0.0`,
`medusa-dev`,
(webServer, addr, pkgName, pkgVersion) => {
// console.log(webServer)
webServer.listen(addr.port || addr.path, addr.host, () => {
console.log(`Started local verdaccio server`)
resolve()
})
}
)
})
return VerdaccioInitPromise
}
exports.startVerdaccio = startServer
exports.publishPackagesLocallyAndInstall = async ({
packagesToPublish,
localPackages,
packageNameToPath,
ignorePackageJSONChanges,
yarnWorkspaceRoot,
externalRegistry,
root,
}) => {
await startServer()
const versionPostFix = Date.now()
const newlyPublishedPackageVersions = {}
for (const packageName of packagesToPublish) {
newlyPublishedPackageVersions[packageName] = await publishPackage({
packageName,
packagesToPublish,
packageNameToPath,
versionPostFix,
ignorePackageJSONChanges,
root,
})
}
const packagesToInstall = _.intersection(packagesToPublish, localPackages)
await installPackages({
packagesToInstall,
yarnWorkspaceRoot,
newlyPublishedPackageVersions,
externalRegistry,
})
}

View File

@@ -0,0 +1,163 @@
const path = require(`path`)
const fs = require(`fs-extra`)
const { promisifiedSpawn } = require(`../utils/promisified-spawn`)
const { registryUrl } = require(`./verdaccio-config`)
const installPackages = async ({
packagesToInstall,
yarnWorkspaceRoot,
newlyPublishedPackageVersions,
externalRegistry,
}) => {
console.log(
`Installing packages from local registry:\n${packagesToInstall
.map((packageAndVersion) => ` - ${packageAndVersion}`)
.join(`\n`)}`
)
let installCmd
if (yarnWorkspaceRoot) {
// this is very hacky - given root, we run `yarn workspaces info`
// to get list of all workspaces and their locations, and manually
// edit package.json file for packages we want to install
// to make sure there are no mismatched versions of same package
// in workspaces which should preserve node_modules structure
// (packages being mostly hoisted to top-level node_modules)
const { stdout: yarnVersion } = await promisifiedSpawn([
`yarn`,
[`--version`],
{ stdio: `pipe` },
])
const workspaceCommand = !yarnVersion.startsWith("1") ? "list" : "info"
const { stdout } = await promisifiedSpawn([
`yarn`,
[`workspaces`, workspaceCommand, `--json`],
{ stdio: `pipe` },
])
let workspacesLayout
try {
workspacesLayout = JSON.parse(JSON.parse(stdout).data)
} catch (e) {
/*
Yarn 1.22 doesn't output pure json - it has leading and trailing text:
```
$ yarn workspaces info --json
yarn workspaces v1.22.0
{
"z": {
"location": "z",
"workspaceDependencies": [],
"mismatchedWorkspaceDependencies": []
},
"y": {
"location": "y",
"workspaceDependencies": [],
"mismatchedWorkspaceDependencies": []
}
}
Done in 0.48s.
```
So we need to do some sanitization. We find JSON by matching substring
that starts with `{` and ends with `}`
*/
const regex = /^[^{]*({.*})[^}]*$/gs
const sanitizedStdOut = regex.exec(stdout)
if (sanitizedStdOut?.length >= 2) {
// pick content of first (and only) capturing group
const jsonString = sanitizedStdOut[1]
try {
workspacesLayout = JSON.parse(jsonString)
} catch (e) {
console.error(
`Failed to parse "sanitized" output of "yarn workspaces info" command.\n\nSanitized string: "${jsonString}`
)
// not exitting here, because we have general check for `workspacesLayout` being set below
}
}
}
if (!workspacesLayout) {
console.error(
`Couldn't parse output of "yarn workspaces info" command`,
stdout
)
process.exit(1)
}
const handleDeps = (deps) => {
if (!deps) {
return false
}
let changed = false
Object.keys(deps).forEach((depName) => {
if (packagesToInstall.includes(depName)) {
deps[depName] = `medusa-dev`
changed = true
}
})
return changed
}
Object.keys(workspacesLayout).forEach((workspaceName) => {
const { location } = workspacesLayout[workspaceName]
const pkgJsonPath = path.join(yarnWorkspaceRoot, location, `package.json`)
if (!fs.existsSync(pkgJsonPath)) {
return
}
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, `utf8`))
let changed = false
changed |= handleDeps(pkg.dependencies)
changed |= handleDeps(pkg.devDependencies)
changed |= handleDeps(pkg.peerDependencies)
if (changed) {
console.log(`Changing deps in ${pkgJsonPath} to use @medusa-dev`)
fs.outputJSONSync(pkgJsonPath, pkg, {
spaces: 2,
})
}
})
// package.json files are changed - so we just want to install
// using verdaccio registry
const yarnCommands = [`install`]
if (!externalRegistry) {
yarnCommands.push(`--registry=${registryUrl}`)
}
installCmd = [`yarn`, yarnCommands]
} else {
const yarnCommands = [
`add`,
...packagesToInstall.map((packageName) => {
const packageVersion = newlyPublishedPackageVersions[packageName]
return `${packageName}@${packageVersion}`
}),
`--exact`,
]
if (!externalRegistry) {
yarnCommands.push(`--registry=${registryUrl}`)
}
installCmd = [`yarn`, yarnCommands]
}
try {
await promisifiedSpawn(installCmd)
console.log(`Installation complete`)
} catch (error) {
console.error(`Installation failed`, error)
process.exit(1)
}
}
exports.installPackages = installPackages

View File

@@ -0,0 +1,158 @@
const fs = require(`fs-extra`)
const path = require(`path`)
const { promisifiedSpawn } = require(`../utils/promisified-spawn`)
const { registryUrl } = require(`./verdaccio-config`)
const NPMRCContent = `${registryUrl.replace(
/https?:/g,
``
)}/:_authToken="medusa-dev"`
const {
getMonorepoPackageJsonPath,
} = require(`../utils/get-monorepo-package-json-path`)
const { registerCleanupTask } = require(`./cleanup-tasks`)
/**
* Edit package.json to:
* - adjust version to temporary one
* - change version selectors for dependencies that
* will be published, to make sure that yarn
* install them in local site
*/
const adjustPackageJson = ({
monoRepoPackageJsonPath,
packageName,
versionPostFix,
packagesToPublish,
ignorePackageJSONChanges,
packageNameToPath,
}) => {
// we need to check if package depend on any other package to will be published and
// adjust version selector to point to dev version of package so local registry is used
// for dependencies.
const monorepoPKGjsonString = fs.readFileSync(
monoRepoPackageJsonPath,
`utf-8`
)
const monorepoPKGjson = JSON.parse(monorepoPKGjsonString)
monorepoPKGjson.version = `${monorepoPKGjson.version}-dev-${versionPostFix}`
packagesToPublish.forEach((packageThatWillBePublished) => {
if (
monorepoPKGjson.dependencies &&
monorepoPKGjson.dependencies[packageThatWillBePublished]
) {
const currentVersion = JSON.parse(
fs.readFileSync(
getMonorepoPackageJsonPath({
packageName: packageThatWillBePublished,
packageNameToPath,
}),
`utf-8`
)
).version
monorepoPKGjson.dependencies[
packageThatWillBePublished
] = `${currentVersion}-dev-${versionPostFix}`
}
})
const temporaryMonorepoPKGjsonString = JSON.stringify(monorepoPKGjson)
const unignorePackageJSONChanges = ignorePackageJSONChanges(packageName, [
monorepoPKGjsonString,
temporaryMonorepoPKGjsonString,
])
// change version and dependency versions
fs.outputFileSync(monoRepoPackageJsonPath, temporaryMonorepoPKGjsonString)
return {
newPackageVersion: monorepoPKGjson.version,
unadjustPackageJson: registerCleanupTask(() => {
// restore original package.json
fs.outputFileSync(monoRepoPackageJsonPath, monorepoPKGjsonString)
unignorePackageJSONChanges()
}),
}
}
/**
* Anonymous publishing require dummy .npmrc
* See https://github.com/verdaccio/verdaccio/issues/212#issuecomment-308578500
* This is `npm publish` (as in linked comment) and `yarn publish` requirement.
* This is not verdaccio restriction.
*/
const createTemporaryNPMRC = ({ pathToPackage, root }) => {
const NPMRCPathInPackage = path.join(pathToPackage, `.npmrc`)
fs.outputFileSync(NPMRCPathInPackage, NPMRCContent)
const NPMRCPathInRoot = path.join(root, `.npmrc`)
fs.outputFileSync(NPMRCPathInRoot, NPMRCContent)
return registerCleanupTask(() => {
fs.removeSync(NPMRCPathInPackage)
fs.removeSync(NPMRCPathInRoot)
})
}
const publishPackage = async ({
packageName,
packagesToPublish,
versionPostFix,
ignorePackageJSONChanges,
packageNameToPath,
root,
}) => {
const monoRepoPackageJsonPath = getMonorepoPackageJsonPath({
packageName,
packageNameToPath,
})
const { unadjustPackageJson, newPackageVersion } = adjustPackageJson({
monoRepoPackageJsonPath,
packageName,
packageNameToPath,
versionPostFix,
packagesToPublish,
ignorePackageJSONChanges,
})
const pathToPackage = path.dirname(monoRepoPackageJsonPath)
const uncreateTemporaryNPMRC = createTemporaryNPMRC({ pathToPackage, root })
// npm publish
const publishCmd = [
`npm`,
[`publish`, `--tag`, `medusa-dev`, `--registry=${registryUrl}`],
{
cwd: pathToPackage,
},
]
console.log(
`Publishing ${packageName}@${newPackageVersion} to local registry`
)
try {
await promisifiedSpawn(publishCmd)
console.log(
`Published ${packageName}@${newPackageVersion} to local registry`
)
} catch (e) {
console.error(`Failed to publish ${packageName}@${newPackageVersion}`, e)
process.exit(1)
}
uncreateTemporaryNPMRC()
unadjustPackageJson()
return newPackageVersion
}
exports.publishPackage = publishPackage

View File

@@ -0,0 +1,33 @@
const path = require(`path`)
const os = require(`os`)
const verdaccioConfig = {
storage: path.join(os.tmpdir(), `verdaccio`, `storage`),
port: 4873, // default
max_body_size: `1000mb`,
web: {
enable: true,
title: `gatsby-dev`,
},
logs: [{ type: `stdout`, format: `pretty-timestamped`, level: `warn` }],
packages: {
"**": {
access: `$all`,
publish: `$all`,
proxy: `npmjs`,
},
},
uplinks: {
npmjs: {
url: `https://registry.npmjs.org/`,
// default is 2 max_fails - on flaky networks that cause a lot of failed installations
max_fails: 10,
},
},
}
exports.verdaccioConfig = verdaccioConfig
const registryUrl = `http://localhost:${verdaccioConfig.port}`
exports.registryUrl = registryUrl

View File

@@ -0,0 +1,50 @@
const { getDependantPackages } = require(`../get-dependant-packages`)
function createMockPackageNameToPath(packageNames) {
const packageNameToPath = new Map()
for (const packageName of packageNames) {
packageNameToPath.set(packageName, `/test/${packageName}`)
}
return packageNameToPath
}
describe(`getDependantPackages`, () => {
it(`handles deep dependency chains`, () => {
const packagesToPublish = getDependantPackages({
packageName: `package-a-dep1-dep1`,
depTree: {
"package-a-dep1": new Set([`package-a`]),
"package-a-dep1-dep1": new Set([`package-a-dep1`]),
"not-related": new Set([`also-not-related`]),
},
packageNameToPath: createMockPackageNameToPath([
`package-a`,
`package-a-dep1`,
`package-a-dep1-dep1`,
`not-related`,
`also-not-related`,
]),
})
expect(packagesToPublish).toEqual(
new Set([`package-a`, `package-a-dep1`, `package-a-dep1-dep1`])
)
})
it(`doesn't get stuck in circular dependency loops`, () => {
const packagesToPublish = getDependantPackages({
packageName: `package-a`,
depTree: {
"package-a": new Set([`package-b`]),
"package-b": new Set([`package-a`]),
},
packageNameToPath: createMockPackageNameToPath([
`package-a`,
`package-b`,
]),
})
expect(packagesToPublish).toEqual(new Set([`package-a`, `package-b`]))
})
})

View File

@@ -0,0 +1,78 @@
const path = require(`path`)
const { traversePackagesDeps } = require(`../traverse-package-deps`)
jest.doMock(
path.join(...`<monorepo-path>/packages/package-a/package.json`.split(`/`)),
() => {
return {
dependencies: {
"unrelated-package": `*`,
"package-a-dep1": `*`,
},
}
},
{ virtual: true }
)
jest.doMock(
path.join(
...`<monorepo-path>/packages/package-a-dep1/package.json`.split(`/`)
),
() => {
return {
dependencies: {
"package-a-dep1-dep1": `*`,
},
}
},
{ virtual: true }
)
jest.doMock(
path.join(
...`<monorepo-path>/packages/package-a-dep1-dep1/package.json`.split(`/`)
),
() => {
return {
dependencies: {},
}
},
{ virtual: true }
)
describe(`traversePackageDeps`, () => {
it(`handles deep dependency chains`, () => {
const monoRepoPackages = [
`package-a`,
`package-a-dep1`,
`package-a-dep1-dep1`,
`package-not-used`,
]
const packageNameToPath = new Map()
for (const packageName of monoRepoPackages) {
packageNameToPath.set(
packageName,
path.join(...`<monorepo-path>/packages/${packageName}`.split(`/`))
)
}
const { seenPackages, depTree } = traversePackagesDeps({
root: `<monorepo-path>`,
packages: [`package-a`, `doesnt-exist`],
monoRepoPackages,
packageNameToPath,
})
expect(seenPackages).toEqual([
`package-a`,
`package-a-dep1`,
`package-a-dep1-dep1`,
])
expect(depTree).toEqual({
"package-a-dep1": new Set([`package-a`]),
"package-a-dep1-dep1": new Set([`package-a-dep1`]),
})
})
})

View File

@@ -0,0 +1,193 @@
const fs = require(`fs-extra`)
const _ = require(`lodash`)
const {
getMonorepoPackageJsonPath,
} = require(`./get-monorepo-package-json-path`)
const got = require(`got`)
function difference(object, base) {
function changes(object, base) {
return _.transform(object, function (result, value, key) {
if (!_.isEqual(value, base[key])) {
result[key] =
_.isObject(value) && _.isObject(base[key])
? changes(value, base[key])
: value
}
})
}
return changes(object, base)
}
/**
* Compare dependencies of installed packages and monorepo package.
* It will skip dependencies that are removed in monorepo package.
*
* If local package is not installed, it will check unpkg.com.
* This allow gatsby-dev to skip publishing unnecesairly and
* let install packages from public npm repository if nothing changed.
*/
exports.checkDepsChanges = async ({
newPath,
packageName,
monoRepoPackages,
isInitialScan,
ignoredPackageJSON,
packageNameToPath,
}) => {
let localPKGjson
let packageNotInstalled = false
try {
localPKGjson = JSON.parse(fs.readFileSync(newPath, `utf-8`))
} catch {
packageNotInstalled = true
// there is no local package - so we still need to install deps
// this is nice because devs won't need to do initial package installation - we can handle this.
if (!isInitialScan) {
console.log(
`'${packageName}' doesn't seem to be installed. Restart gatsby-dev to publish it`
)
return {
didDepsChanged: false,
packageNotInstalled,
}
}
// if package is not installed, we will do http GET request to
// unkpg to check if dependency in package published in public
// npm repository are different
// this allow us to not publish to local repository
// and save some time/work
try {
const version = getPackageVersion(packageName)
const url = `https://unpkg.com/${packageName}@${version}/package.json`
const response = await got(url)
if (response?.statusCode !== 200) {
throw new Error(`No response or non 200 code for ${url}`)
}
localPKGjson = JSON.parse(response.body)
} catch (e) {
console.log(
`'${packageName}' doesn't seem to be installed and is not published on NPM. Error: ${e.message}`
)
return {
didDepsChanged: true,
packageNotInstalled,
}
}
}
const monoRepoPackageJsonPath = getMonorepoPackageJsonPath({
packageName,
packageNameToPath,
})
const monorepoPKGjsonString = fs.readFileSync(
monoRepoPackageJsonPath,
`utf-8`
)
const monorepoPKGjson = JSON.parse(monorepoPKGjsonString)
if (ignoredPackageJSON.has(packageName)) {
if (ignoredPackageJSON.get(packageName).includes(monorepoPKGjsonString)) {
// we are in middle of publishing and content of package.json is one set during publish process,
// so we need to not cause false positives
return {
didDepsChanged: false,
packageNotInstalled,
}
}
}
if (!monorepoPKGjson.dependencies) monorepoPKGjson.dependencies = {}
if (!localPKGjson.dependencies) localPKGjson.dependencies = {}
const areDepsEqual = _.isEqual(
monorepoPKGjson.dependencies,
localPKGjson.dependencies
)
if (!areDepsEqual) {
const diff = difference(
monorepoPKGjson.dependencies,
localPKGjson.dependencies
)
const diff2 = difference(
localPKGjson.dependencies,
monorepoPKGjson.dependencies
)
let needPublishing = false
let isPublishing = false
const depChangeLog = _.uniq(Object.keys({ ...diff, ...diff2 }))
.reduce((acc, key) => {
if (monorepoPKGjson.dependencies[key] === `gatsby-dev`) {
// if we are in middle of publishing to local repository - ignore
isPublishing = true
return acc
}
if (localPKGjson.dependencies[key] === `gatsby-dev`) {
// monorepo packages will restore version, but after installation
// in local site - it will use `gatsby-dev` dist tag - we need
// to ignore changes that
return acc
}
if (
localPKGjson.dependencies[key] &&
monorepoPKGjson.dependencies[key]
) {
// Check only for version changes in packages
// that are not from gatsby repo.
// Changes in gatsby packages will be copied over
// from monorepo - and if those contain other dependency
// changes - they will be covered
if (!monoRepoPackages.includes(key)) {
acc.push(
` - '${key}' changed version from ${localPKGjson.dependencies[key]} to ${monorepoPKGjson.dependencies[key]}`
)
needPublishing = true
}
} else if (monorepoPKGjson.dependencies[key]) {
acc.push(` - '${key}@${monorepoPKGjson.dependencies[key]}' was added`)
needPublishing = true
} else {
acc.push(` - '${key}@${localPKGjson.dependencies[key]}' was removed`)
// this doesn't need publishing really, so will skip this
}
return acc
}, [])
.join(`\n`)
if (!isPublishing && depChangeLog.length > 0) {
console.log(`Dependencies of '${packageName}' changed:\n${depChangeLog}`)
if (isInitialScan) {
console.log(
`Will ${!needPublishing ? `not ` : ``}publish to local npm registry.`
)
} else {
console.warn(
`Installation of dependencies after initial scan is not implemented`
)
}
return {
didDepsChanged: needPublishing,
packageNotInstalled,
}
}
}
return {
didDepsChanged: false,
packageNotInstalled,
}
}
function getPackageVersion(packageName) {
const projectPackageJson = JSON.parse(
fs.readFileSync(`./package.json`, `utf-8`)
)
const { dependencies = {}, devDependencies = {} } = projectPackageJson
const version = dependencies[packageName] || devDependencies[packageName]
return version || `latest`
}

View File

@@ -0,0 +1,30 @@
/**
* Recursively get set of packages that depend on given package.
* Set also includes passed package.
*/
const getDependantPackages = ({
packageName,
depTree,
packagesToPublish = new Set(),
}) => {
if (packagesToPublish.has(packageName)) {
// bail early if package was already handled
return packagesToPublish
}
packagesToPublish.add(packageName)
const dependants = depTree[packageName]
if (dependants) {
dependants.forEach(dependant =>
getDependantPackages({
packageName: dependant,
depTree,
packagesToPublish,
})
)
}
return packagesToPublish
}
exports.getDependantPackages = getDependantPackages

View File

@@ -0,0 +1,4 @@
const path = require(`path`)
exports.getMonorepoPackageJsonPath = ({ packageName, packageNameToPath }) =>
path.join(packageNameToPath.get(packageName), `package.json`)

View File

@@ -0,0 +1,29 @@
const execa = require(`execa`)
const defaultSpawnArgs = {
cwd: process.cwd(),
stdio: `inherit`,
}
exports.setDefaultSpawnStdio = stdio => {
defaultSpawnArgs.stdio = stdio
}
exports.promisifiedSpawn = async ([cmd, args = [], spawnArgs = {}]) => {
const spawnOptions = {
...defaultSpawnArgs,
...spawnArgs,
}
try {
return await execa(cmd, args, spawnOptions)
} catch (e) {
if (spawnOptions.stdio === `ignore`) {
console.log(
`\nCommand "${cmd} ${args.join(
` `
)}" failed.\nTo see details of failed command, rerun "medusa-dev" without "--quiet" or "-q" switch\n`
)
}
throw e
}
}

View File

@@ -0,0 +1,88 @@
const _ = require(`lodash`)
const path = require(`path`)
/**
* @typedef {Object} TraversePackagesDepsReturn
* @property {Object} depTree Lookup table to check dependants for given package.
* Used to determine which packages need to be published.
* @example
* ```
* {
* "medusa-cli": Set(["medusa"]),
* "medusa-telemetry": Set(["medusa", "medusa-cli"]),
* }
* ```
*/
/**
* Compile final list of packages to watch
* This will include packages explicitly defined packages and all their dependencies
* Also creates dependency graph that is used later to determine which packages
* would need to be published when their dependencies change
* @param {Object} $0
* @param {String} $0.root Path to root of medusa monorepo repository
* @param {String[]} $0.packages Initial array of packages to watch
* This can be extracted from project dependencies or explicitly set by `--packages` flag
* @param {String[]} $0.monoRepoPackages Array of packages in medusa monorepo
* @param {String[]} [$0.seenPackages] Array of packages that were already traversed.
* This makes sure dependencies are extracted one time for each package and avoid any
* infinite loops.
* @param {DepTree} [$0.depTree] Used internally to recursively construct dependency graph.
* @return {TraversePackagesDepsReturn}
*/
const traversePackagesDeps = ({
packages,
monoRepoPackages,
seenPackages = [...packages],
depTree = {},
packageNameToPath,
}) => {
packages.forEach((p) => {
let pkgJson
try {
const packageRoot = packageNameToPath.get(p)
if (packageRoot) {
pkgJson = require(path.join(packageRoot, `package.json`))
} else {
console.error(`"${p}" package doesn't exist in monorepo.`)
// remove from seenPackages
seenPackages = seenPackages.filter((seenPkg) => seenPkg !== p)
return
}
} catch (e) {
console.error(`"${p}" package doesn't exist in monorepo.`, e)
// remove from seenPackages
seenPackages = seenPackages.filter((seenPkg) => seenPkg !== p)
return
}
const fromMonoRepo = _.intersection(
Object.keys({ ...pkgJson.dependencies }),
monoRepoPackages
)
fromMonoRepo.forEach((pkgName) => {
depTree[pkgName] = (depTree[pkgName] || new Set()).add(p)
})
// only traverse not yet seen packages to avoid infinite loops
const newPackages = _.difference(fromMonoRepo, seenPackages)
if (newPackages.length) {
newPackages.forEach((depFromMonorepo) => {
seenPackages.push(depFromMonorepo)
})
traversePackagesDeps({
packages: fromMonoRepo,
monoRepoPackages,
seenPackages,
depTree,
packageNameToPath,
})
}
})
return { seenPackages, depTree }
}
exports.traversePackagesDeps = traversePackagesDeps

View File

@@ -0,0 +1,4 @@
exports.getVersionInfo = () => {
const { version: devCliVersion } = require(`../../package.json`);
return `Medusa Dev CLI version: ${devCliVersion}`;
};

View File

@@ -0,0 +1,372 @@
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

View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"lib": ["es5", "es6", "es2019"],
"target": "es6",
"outDir": "./dist",
"esModuleInterop": true,
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noImplicitReturns": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"allowJs": true,
"skipLibCheck": true,
"downlevelIteration": true // to use ES5 specific tooling
},
"include": ["src"],
"exclude": [
"dist",
"./src/**/__tests__",
"./src/**/__mocks__",
"./src/**/__fixtures__",
"node_modules"
]
}

View File

@@ -0,0 +1,5 @@
{
"extends": "./tsconfig.json",
"include": ["src"],
"exclude": ["node_modules"]
}