newmoでは、フロントエンド、バックエンド、iOSやAndroidなどのモバイルアプリをすべて同じリポジトリで管理するmonorepoを採用しています。
monorepoを採用することで、アプリケーション間で共通のコードを共有することができたり、CIの管理が楽になったり、他のチームのコードを見るのにわざわざリポジトリをcloneする必要がなくなります。
また、monorepoを採用することで、アプリケーションが利用しているパッケージ(ライブラリやツール)のバージョンを1つだけにするOne Version Ruleが実装できます。
One Version Rule
One Version Ruleは、monorepo内のパッケージのパッケージのバージョンを1つだけにするルールです。
One Version Ruleでは、monorepo内のアプリケーションが依存するパッケージは1つのバージョンだけにします。
たとえば、アプリケーションAがpackageX
のバージョン 1.0.0
を使っているとき、アプリケーションBもpackageX
のバージョン 1.0.0
を使うように統一します。
これによって、あるパッケージが1つのバージョンだけに集約されるので、メンテナンス性がよくなったり、パッケージのアップデートに関するセキュリティ的な問題に対処しやすくなったり、Diamond dependencyのようなパッケージ同士の依存におけるバージョンのConflictが起きにくくなります。
また、副作用として新しいパッケージを入れることに慎重となるため、同じ機能を持つ異なるパッケージが入りにくい傾向があります。
雑にパッケージの依存を増やすコストが高くなるため、newmoではDesign Docを書いて議論してからパッケージを追加することが多いです。
一方で、One Version Ruleを実践するには、当たり前ですがパッケージのバージョンを1つに統一する必要があります。そのため、依存するパッケージのバージョンを1つに統一するための方法を考える必要があります。
このパッケージのバージョン管理方法は言語により異なるため、言語ごとに適切な方法を選択する必要があります。
この記事では、フロントエンドで利用するnpm Registryのパッケージのバージョンを1つに統一するOne Version Ruleを実装する方法について紹介します。
pnpm catalogを使ったOne Version Ruleの実装
newmoでは、npmのパッケージ管理ツールとしてpnpmを利用しています。
そして、pnpmのCatalogs機能を使ってOne Version Ruleを実装しています。
pnpm catalogとは
pnpm catalogは、pnpm 9.5で追加された機能で、依存する複数のパッケージに名前をつけて管理したり、パッケージの依存をカタログ的に一箇所で管理できる仕組みです。
pnpmのworkspace(複数のパッケージ = ここではアプリケーションを統合的に扱う仕組み)を定義するpnpm-workspace.yaml
に、catalog
を定義できるようになっています。
pnpm-workspace.yaml
のcatalog:
には、パッケージ名とそのバージョンを定義できます。
pnpm-workspace.yaml
:
packages:
- packages/*
catalog:
react: ^18.3.1
redux: ^5.0.1
catalog:
直下のパッケージはデフォルトのカタログとして扱われ、アプリケーションのpackage.json
からは catalog:
(名前がない場合はデフォルトカタログという意味となる)で参照できるようになります。
アプリケーションのpackage.json
:
{
"name": "@example/app",
"dependencies": {
"react": "catalog:",
"redux": "catalog:"
}
}
さらにcatalogにはNamed Catalogsという機能もあり、複数のパッケージとバージョンをまとめたものに対して名前をつけて管理することができます。
次の例では、react17
とreact18
という名前のcatalogを定義しています。
catalogs:
react17:
react: ^17.0.2
react-dom: ^17.0.2
react18:
react: ^18.2.0
react-dom: ^18.2.0
同じようにアプリケーションからは catalog:react17
や catalog:react18
で参照できます。
{
"name": "@example/components",
"dependencies": {
"react": "catalog:react18",
}
}
この機能を使うことで、monorepoにある全てのパッケージの名前とバージョンがpnpm-workspace.yaml
という一つのファイルで管理できるようになります。
One Version Ruleでは、monorepoでは1つのバージョンを扱うので基本的にデフォルトカタログ(catalog:
)にほとんどのパッケージを記述することになります。
パッケージをアップデートするときに、アプリケーションのコードも必要な場合があります。
複数のアプリケーションが依存しているパッケージの場合、同時にアプリケーションを修正することが難しいケースもあります。
その場合は、例外的にNamed Catalogsを使ってバージョンを分けることで、段階的にアップデートを進めることも可能です。
ここまでは、pnpmのcatalog機能の紹介でしたが、実際にOne Version Ruleをやるにはこのルールに強制力が必要です。
newmoのmonorepoでは、pnpmのHooks機能を使ってこのルールに強制力を持たせています。
pnpmのHooksを使ったOne Version Ruleの実装
pnpmのHooks機能を使うことで、依存するパッケージのバージョンを1つに統一するOne Version Ruleを実装することができます。
pnpmのHooks機能は、package.json
の依存を解析したタイミング(readPackage
)と依存関係が全て解決したタイミング(afterAllResolved
)にフックする処理を.pnpmfile.cjs
ファイルに記述できます。
実現したいOne Version Ruleをpnpmのレベルまで落とすと、次のようなチェックをすれば良いことがわかります。
- アプリケーションが依存するパッケージのバージョンは
workspace:*
または catalog:
で指定する
- アプリケーション側には直接バージョンを指定できなくします
{ "<name>": "catalog:" }
とアプリケーションで指定されたパッケージの実際のバージョンが pnpm-workspace.yaml
に記載されている
pnpm-workspace.yaml
で全てのパッケージのバージョンを管理するようにします
- 基本的にはデフォルトカタログ(
catalog:
)にパッケージとバージョンを定義して、1つのバージョンを使うようにします
- 例外としてnamed catalogを使うことで、複数のバージョンが存在することは許容します
- monorepo内のパッケージとnode_modulesのパッケージを区別できるような名前をつける
- これは
readPackage
がmonorepo内外の両方package.json
の解析のタイミングで呼ばれるため、区別するのに必要です
- newmoでは
@newmo-app/
で始まるパッケージをmonorepo内のパッケージとして扱っています
- バージョンは必ずPinされたバージョンを使うようにする
- lockファイルでバージョンは固定はされますが、
pnpm-workspace.yaml
を見たときにバージョンがわかるようにPinされたバージョンを使うようにしています
- このルールを満たさない時は、自動的に修正コマンドをエラーに表示する
- このルールを満たさないときに手動で修正することは可能ですが、自動的に修正コマンドを表示することで、修正の手間を減らすことができます
- 一般的とは言えないルールなので、普通のパッケージ管理ツールを使うのと同じぐらい簡単に扱えるようにする必要があります
このルールを実装した.pnpmfile.cjs
は、次のようになっています。
MONOREPO_PREFIX
を変更すれば、大体そのまま利用できるようになっています。
.pnpmfile.cjs
(クリックで開く)
const rootPkg = require("./package.json");
const fs = require("node:fs");
const path = require("node:path");
const rootDir = __dirname;
const MONOREPO_PREFIX = "@newmo-app/";
const pnpmCatalogYamlFile = path.join(rootDir, "pnpm-workspace.yaml");
const pnpmCatalogYaml = fs.readFileSync(pnpmCatalogYamlFile, "utf-8");
const isMonorepoPackage = (pkgName) => {
return pkgName.startsWith(MONOREPO_PREFIX);
};
const pinVersion = (version) => {
if (version.startsWith("^") || version.startsWith("~")) {
return version.slice(1);
}
return version;
};
const isPackageIncludedInCatalog = (pkgName) => {
const pkgAndVersionPattern = new RegExp(String.raw`"?${pkgName}"?: \d+\.\d+\.\d+`);
if (pkgAndVersionPattern.test(pnpmCatalogYaml)) {
return true;
}
const pkgAndVersionPatternWithNpm = new RegExp(String.raw`"?${pkgName}"?: npm:[\w-]{1,32}@`);
return pkgAndVersionPatternWithNpm.test(pnpmCatalogYaml);
};
const assertMonorepoPackageNameRule = (lockfile) => {
const pkgPathList = Object.keys(lockfile.importers).map(pathFromRoot => {
return path.join(path.resolve(rootDir, pathFromRoot), "package.json");
});
for (const pkgPath of pkgPathList) {
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
if (!isMonorepoPackage(pkg.name)) {
throw new Error(`Found invalid package name: ${pkg.name}
Please make sure that all internal packages are prefixed with ${MONOREPO_PREFIX}
詳細は **社内Notionへのリンク** を参照してください。
`);
}
}
};
const assertRootPackage = (pkg) => {
if (pkg.name !== rootPkg.name) {
throw new Error(`Invalid argument: ${pkg.name}. This function only accepts root package.json`);
}
const errorPackageSet = new Set();
["dependencies", "devDependencies", "peerDependencies"].forEach((depType) => {
const deps = pkg[depType];
if (deps) {
Object.entries(deps).forEach(([depName, depVersion]) => {
if (depVersion !== pinVersion(depVersion)) {
errorPackageSet.add({
depName, depVersion, depType, errorType: "invalid-semver-version"
});
}
});
}
});
if (pkg?.pnpm?.overrides) {
Object.entries(pkg.pnpm.overrides).forEach(([depName, depVersion]) => {
if (depVersion !== pinVersion(depVersion)) {
errorPackageSet.add({
depName, depVersion, depType: "pnpm.overrides", errorType: "invalid-semver-version"
});
}
});
}
if (errorPackageSet.size !== 0) {
const commands = Array.from(errorPackageSet).map((errorPackage) => {
if (errorPackage.errorType === "invalid-semver-version") {
if (errorPackage.depType === "pnpm.overrides") {
return `npm pkg set 'pnpm.overrides.${errorPackage.depName}'='${pinVersion(errorPackage.depVersion)}'`;
}
return `npm pkg set '${errorPackage.depType}.${errorPackage.depName}'='${pinVersion(errorPackage.depVersion)}'`;
}
}).join("\n");
throw new Error(`Found invalid dependency versions in root package.json
This monorepo enforces all dependencies to be pinned version in root "package.json".
Please run the following commands to fix the issue:
\`\`\`
cd ${rootDir}
${commands}
pnpm install
\`\`\`
詳細は **社内Notionへのリンク** を参照してください。
`);
}
};
const assertWorkspacePackage = (pkg) => {
const errorPackageSet = new Set();
["dependencies", "devDependencies", "peerDependencies"].forEach((depType) => {
const deps = pkg[depType];
if (deps) {
Object.entries(deps).forEach(([depName, depVersion]) => {
if (isMonorepoPackage(depName)) {
return;
}
if (!isPackageIncludedInCatalog(depName)) {
errorPackageSet.add({
depName, depVersion, depType, packageName: pkg.name, errorType: "non-catalog"
});
}
if (!depVersion.startsWith("workspace:") && !depVersion.startsWith("catalog:")) {
errorPackageSet.add({
depName, depVersion, depType, packageName: pkg.name, errorType: "invalid-version"
});
}
});
}
});
if (errorPackageSet.size !== 0) {
const fixCommand = ({ depName, depVersion, depType, packageName, errorType }) => {
if (errorType === "non-catalog") {
if (depVersion.startsWith("workspace:") || depVersion.startsWith("catalog:")) {
return `yq -i '.catalog += {"${depName}": "$(npm info ${depName} version)"}' "${pnpmCatalogYamlFile}" # ${packageName}`;
}
return `yq -i '.catalog += {"${depName}": "${pinVersion(depVersion)}"}' "${pnpmCatalogYamlFile}" # ${packageName}`;
}
if (errorType === "invalid-version") {
return `npm --prefix=$(find . -name package.json | xargs grep -l '"name": "${packageName}"' | xargs dirname) pkg set '${depType}.${depName}'='catalog:'`;
}
};
const commands = Array.from(errorPackageSet).map((errorPackage) => {
return fixCommand(errorPackage);
}).join("\n");
const isAddNewCatalog = Array.from(errorPackageSet).some((errorPackage) => {
return errorPackage.errorType === "non-catalog";
})
throw new Error(`Found packages that violate the rule
This monorepo enforces all dependencies to be defined in root "package.json" and pnpm catalogs.
Please run the following commands to fix the issue:
yq コマンドに依存しているので、 "brew install yq" で事前にインストールしてください
\`\`\`
cd ${rootDir}
${commands}
pnpm install
\`\`\`
${isAddNewCatalog ? "新しいパッケージがpnpm-workspace.yamlのcatalogに追加されます。コマンド実行後に、pnpm-workspace.yamlに追加された依存を適切なグループに移動してください" : ""}
詳細は **社内Notionへのリンク** を参照してください。
`);
}
};
function afterAllResolved(lockfile, context) {
assertMonorepoPackageNameRule(lockfile);
return lockfile;
}
function readPackage(pkg, context) {
const isPnpmCreate = pkg.name === undefined;
if (isPnpmCreate) {
return pkg;
}
if (!isMonorepoPackage(pkg.name)) {
return pkg;
}
if (pkg.name === rootPkg.name) {
assertRootPackage(pkg);
return pkg;
}
assertWorkspacePackage(pkg);
return pkg;
}
module.exports = {
hooks: {
readPackage,
afterAllResolved
}
};
この.pnpmfile.cjs
は、pnpm install
時に実行され、依存するパッケージのバージョンを1つに統一するOne Version Ruleを実装しています。
たとえば、次のようにアプリケーションに直接バージョンを指定した状態で pnpm install
を行うとエラーが発生します
"devDependencies": {
"@playwright/test": "catalog:",
"@types/node": "catalog:",
"typescript": "catalog:",
"jquery": "^3.7.1"
},
実行時のエラーメッセージには、このエラーを修正するコマンドも表示されるので、コマンドを実行することでエラーを修正することができます。
pnpm: Found packages that violate the rule
This monorepo enforces all dependencies to be defined in root "package.json" and force the version via pnpm catalogs
Please run the following commands to fix the issue:
yq コマンドに依存しているので、"brew install yq" で事前にインストールしてください
```
cd /path/tp/newmohq/newmo-app
yq -i '.catalog += {"jquery": "3.7.1"}' "/path/tp/newmohq/newmo-app/pnpm-workspace.yaml" # @newmo-app/application-x
npm --prefix=$(find . -name package.json | xargs grep -l '"name": "@newmo-app/application-x' | xargs dirname) pkg set 'devDependencies.jquery'='catalog:'
pnpm install
```
詳細は **社内Notionへのリンク** を参照してください。
pnpm install
でOne Version Ruleのチェックが行われるので、自動的にCIでも落ち、また開発者のローカルでもすぐエラーがわかるようになっています。
これによってnewmoでは、monorepo内の全てのパッケージのバージョンを1つに統一するOne Version Ruleを実装しています。
Note: Sherifのようなmonorepoに特化したLinterなどもありますが、.pnpmfile.cjs
で実装するメリットは他のツールを増やす必要がない点にあります。
参考: newmoのpnpm catalog
参考までに、newmoのpnpm-workspace.yaml
に記載されているパッケージのカタログを紹介します。
ここに書かれているパッケージが、現在時点(2024-08-30)でのフロントエンドで利用しているパッケージの一覧です。
packages:
- ... 色々な内部パッケージ ...
catalog:
"@line/liff": 2.24.0
"@rive-app/react-canvas": 4.12.0
react: 19.0.0-rc-935180c7e0-20240524
react-dom: 19.0.0-rc-935180c7e0-20240524
"@types/react": npm:types-react@19.0.0-rc.0
"@types/react-dom": npm:types-react-dom@19.0.0-rc.0
react-aria-components: 1.2.1
next: 15.0.0-rc.0
"@next/third-parties": 14.2.5
"@pandacss/dev": 0.44.0
postcss: 8.4.41
storybook: 8.2.9
"@storybook/addon-essentials": 8.2.9
"@storybook/addon-interactions": 8.2.9
"@storybook/addon-links": 8.2.9
"@storybook/addon-storysource": 8.2.9
"@storybook/blocks": 8.2.9
"@storybook/react": 8.2.9
chromatic: 11.7.1
"@chromatic-com/storybook": 1.6.1
"@storybook/react-vite": 8.2.9
"@vitejs/plugin-react": 4.3.1
vite: 5.4.2
vitest: 2.0.3
"@playwright/test": 1.46.0
serve: 14.2.3
graphql: 16.8.1
"@graphql-typed-document-node/core": 3.2.0
"@apollo/client": 3.10.6
"@graphql-codegen/cli": 5.0.2
"@graphql-codegen/client-preset": 4.2.5
"@newmo/graphql-codegen-plugin-typescript-react-apollo": 1.2.2
"@newmo/graphql-fake-server": 0.11.0
"@newmo/graphql-codegen-fake-server-client": 0.11.0
eslint: 8.57.0
typescript-eslint: 7.13.1
eslint-config-next: 15.0.0-rc.0
eslint-plugin-playwright: 1.6.2
eslint-plugin-prettier: 5.1.3
eslint-plugin-react: 7.34.1
eslint-plugin-react-hooks: 4.6.2
eslint-plugin-storybook: 0.8.0
"@vitest/eslint-plugin": 1.0.4
"@pandacss/eslint-plugin": 0.1.9
"@next/eslint-plugin-next": 14.2.4
"@graphql-eslint/eslint-plugin": 3.20.1
secretlint: 8.2.4
"@secretlint/secretlint-rule-preset-recommend": 8.2.4
typescript: 5.5.2
prettier: 3.2.5
glob: 10.3.12TODO
wrangler: 3.57.1
"@types/node": 20.12.7
すでに複数のウェブアプリケーションが稼働していますが、依存はかなり最小限にしていて、また新しいパッケージを追加する際にはDesign Docを書いて議論することが多いです。
Design Docでは、導入する目的/目的外、他の選択肢との比較、メリット/デメリット、Tier(フロントエンドの移り変わりは早すぎるのかを参考に)などを議論しています。
いわゆるArchitecture Decision Record(ADR)のようなものですが、新しいパッケージを追加する際には、このような議論を行うことでなぜそのパッケージを導入するのかを明確にしています。
カタログにDesign Docへのリンクを入れることで、後から入った人もなぜそのパッケージを使っているかをわかるようにしています。
こうした議論をするのは、一度入れたパッケージを削除するのが難しいからです。
パッケージの扱い(Tier)について話すのも、どれぐらい該当のパッケージに依存してアプリケーションを作るかを関係者で認識を揃えるためです。
まとめ
newmoのフロントエンドでは、pnpmのcatalog機能を使ってOne Version Ruleを実装しています。
One Version Ruleは、monorepo内の全てのパッケージのバージョンを1つに統一するルールです。
これによって、パッケージの一覧性がよくなり、パッケージのアップデートといったメンテナンスがしやすくなります。
一方で、基本的には1つのバージョンだけを扱うことになるので、アップデート時には自動テストが重要になります。
newmoのフロントエンドでは、PlaywrightとFake Serverを使ったIntegration TestsやChromaticを使ったVRT(Visual Regression Test)など自動テストを充実させています。
ライブラリを扱うコードに対しては、Unit Testが書きにくいことも多いため、Integration Testsなどユーザーが見るものに対するテストに比重を置いています。
(主なロジックはGoで書かれたバックエンドにあるため、バックエンド側もE2Eテストなどを充実させてカバーしています)
副作用として、新しいパッケージを追加する際には、通常のバラバラのバージョンで管理するよりは心理的な障壁が高くなります。
良い面としてはちゃんと議論してからパッケージを追加することができるということですが、逆を言えばパッケージを簡単には追加できないということでもあります。
これは、newmoではちゃんと検討してからパッケージを追加するような意思決定をしたということなので、全ての開発でこの方法が適切というわけではありません。
One Version Ruleから複数バージョンへ移行するのは簡単ですが、その逆は難しいです。
このOne Version Ruleで破綻するところまでは、この方法でやってみようということで、newmoではフロントエンドを含め、Go言語のバックエンドやSwiftのiOSなども同様の方法でパッケージ管理を行っています。
PR: newmoではフロントエンドエンジニアを募集しています!
興味がある方は、次の採用情報をご覧ください。
Appendix: pnpm 9.5未満でのOne Version Ruleの実装
pnpm catalogはpnpm 9.5で追加された機能ですが、pnpm 9.5未満でも同様のOne Version Ruleを実装することができます。
pnpm.overrides
と workspace:*
を使うことで擬似的にpnpm catalogと同様の機能を実現することができます。
pnpm catalogが出る前にこの仕組みを実装して使っていて、pnpm catalogがリリースされたときには、pnpm catalogを使うように移行しました。
こちらの方式は pnpm overridesを使うのでrenovatebotなども対応しています。
pnpm catalogはまだリリースされたばかりなので、renomatebotやdependabotなどのツールはまだ対応していません。
pnpmfile.js
(クリックで開く)
const rootPkg = require("./package.json");
const fs = require("node:fs");
const path = require("node:path");
const rootDir = __dirname;
const MONOREPO_PREFIX = "@newmo-app/";
const isMonorepoPackage = (pkgName) => {
return pkgName.startsWith(MONOREPO_PREFIX);
};
const pinVersion = (version) => {
if (version.startsWith("^") || version.startsWith("~")) {
return version.slice(1);
}
return version;
};
const assertMonorepoPackageNameRule = (lockfile) => {
const pkgPathList = Object.keys(lockfile.importers).map((pathFromRoot) => {
return path.join(path.resolve(rootDir, pathFromRoot), "package.json");
});
for (const pkgPath of pkgPathList) {
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
if (!isMonorepoPackage(pkg.name)) {
throw new Error(`Found invalid package name: ${pkg.name}
Please make sure that all packages are prefixed with ${MONOREPO_PREFIX}
`);
}
}
};
const assertRootPackage = (pkg) => {
if (pkg.name !== rootPkg.name) {
throw new Error(
`Invalid argument: ${pkg.name}. This function only accepts root package.json`
);
}
const errorPackageSet = new Set();
["dependencies", "devDependencies", "peerDependencies"].forEach((depType) => {
const deps = pkg[depType];
if (deps) {
Object.entries(deps).forEach(([depName, depVersion]) => {
if (depVersion !== pinVersion(depVersion)) {
errorPackageSet.add({
depName,
depVersion,
depType,
errorType: "invalid-semver-version",
});
}
});
}
});
if (pkg?.pnpm?.overrides) {
Object.entries(pkg.pnpm.overrides).forEach(([depName, depVersion]) => {
if (depVersion !== pinVersion(depVersion)) {
errorPackageSet.add({
depName,
depVersion,
depType: "pnpm.overrides",
errorType: "invalid-semver-version",
});
}
});
}
if (errorPackageSet.size !== 0) {
const commands = Array.from(errorPackageSet)
.map((errorPackage) => {
if (errorPackage.errorType === "invalid-semver-version") {
if (errorPackage.depType === "pnpm.overrides") {
return `npm pkg set 'pnpm.overrides.${
errorPackage.depName
}'='${pinVersion(errorPackage.depVersion)}'`;
}
return `npm pkg set '${errorPackage.depType}.${
errorPackage.depName
}'='${pinVersion(errorPackage.depVersion)}'`;
}
})
.join("\n");
throw new Error(`Found invalid dependency versions in root package.json
This monorepo enforces all dependencies to be pinned version in root "package.json".
Please run the following commands to fix the issue:
\`\`\`
cd ${rootDir}
${commands}
pnpm install
\`\`\`
`);
}
};
const assertWorkspacePackage = (pkg) => {
const errorPackageSet = new Set();
["dependencies", "devDependencies", "peerDependencies"].forEach((depType) => {
const deps = pkg[depType];
if (deps) {
Object.entries(deps).forEach(([depName, depVersion]) => {
if (isMonorepoPackage(depName)) {
return;
}
if (!rootPkg.pnpm.overrides[depName]) {
errorPackageSet.add({
depName,
depVersion,
depType,
packageName: pkg.name,
errorType: "non-overrides",
});
}
if (!depVersion.startsWith("workspace:")) {
errorPackageSet.add({
depName,
depVersion,
depType,
packageName: pkg.name,
errorType: "invalid-version",
});
}
});
}
});
if (errorPackageSet.size !== 0) {
const fixCommand = ({
depName,
depVersion,
depType,
packageName,
errorType,
}) => {
if (errorType === "non-overrides") {
if (depVersion.startsWith("workspace:")) {
return `npm pkg set 'pnpm.overrides.${depName}'="$(npm info ${depName} version)"`;
}
return `npm pkg set 'pnpm.overrides.${depName}'='${pinVersion(
depVersion
)}'`;
}
if (errorType === "invalid-version") {
return `npm --prefix=$(find . -name package.json | xargs grep -l '"name": "${packageName}"' | xargs dirname) pkg set '${depType}.${depName}'='workspace:*'`;
}
};
const commands = Array.from(errorPackageSet)
.map((errorPackage) => {
return fixCommand(errorPackage);
})
.join("\n");
throw new Error(`Found packages that violate the rule
This monorepo enforces all dependencies to be defined in root "package.json" and force the version via "pnpm.overrides".
Please run the following commands to fix the issue:
\`\`\`
cd ${rootDir}
${commands}
pnpm install
\`\`\`
詳細は **社内Notionへのリンク** を参照してください。
`);
}
};
function afterAllResolved(lockfile, context) {
assertMonorepoPackageNameRule(lockfile);
return lockfile;
}
function readPackage(pkg, context) {
const isPnpmCreate = pkg.name === undefined;
if (isPnpmCreate) {
return pkg;
}
if (!isMonorepoPackage(pkg.name)) {
return pkg;
}
if (pkg.name === rootPkg.name) {
assertRootPackage(pkg);
return pkg;
}
assertWorkspacePackage(pkg);
return pkg;
}
module.exports = {
hooks: {
readPackage,
afterAllResolved,
},
};
Reviewed by ito.