{"id":16962204,"url":"https://github.com/trydofor/pnpm-hoist-layer","last_synced_at":"2025-04-11T22:12:22.309Z","repository":{"id":257784646,"uuid":"860312339","full_name":"trydofor/pnpm-hoist-layer","owner":"trydofor","description":"use `.pnpmfile.cjs` to hoist deps to monorepo like nuxt layer","archived":false,"fork":false,"pushed_at":"2025-02-06T06:37:02.000Z","size":63,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-11T22:12:13.064Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/pnpm-hoist-layer","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/trydofor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-20T07:51:28.000Z","updated_at":"2025-02-06T06:37:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"ac7390eb-c467-4421-b6b9-18c28cfc076d","html_url":"https://github.com/trydofor/pnpm-hoist-layer","commit_stats":null,"previous_names":["trydofor/pnpm-hoist-layer"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trydofor%2Fpnpm-hoist-layer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trydofor%2Fpnpm-hoist-layer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trydofor%2Fpnpm-hoist-layer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trydofor%2Fpnpm-hoist-layer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/trydofor","download_url":"https://codeload.github.com/trydofor/pnpm-hoist-layer/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248487682,"owners_count":21112190,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T23:05:41.533Z","updated_at":"2025-04-11T22:12:22.282Z","avatar_url":"https://github.com/trydofor.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🪝 pnpm-hoist-layer\n\nuse `.pnpmfile.cjs` to hoist deps/devDeps to project like\n[Nuxt Layer](https://nuxt.com/docs/getting-started/layers)\n\n✅ 10.x, 9.12+ Downloads(7-Days \u003e 500K)\n\n## Purpose\n\npnpm [public-hoist-pattern](https://pnpm.io/npmrc#public-hoist-pattern)\nonly affects to the top-project (virtual store), not the sub-projects, therefore,\n\n* relative path issues may occur\n* copy same deps/devDeps from here to there\n* config too many hoist-pattern\n\nhow to auto add the dep's deps/devDeps to my project?\ni.e. give your deps/devDeps to me when i deps on you.\n\ne.g. `mobile-\u003ecommon`, `common-\u003e@nuxt`, after hoist layer,\n\n```diff\n├── common\n│   ├── node_modules\n│   │   ├── @nuxt -\u003e ../.pnpm/...\n└── mobile\n    ├── node_modules\n    │   ├── @fessional\n    │   │   └── razor-common -\u003e ../.pnpm/...\n+   │   ├── @nuxt -\u003e ../.pnpm/... // ✅ hoist as layer\n```\n\n## How it Works\n\nWhen using `catalog` in `pnpm-workspace.yaml`, it is not easy to manage\nthe dependencies via `pnpm add`, the recommended practice is to manually\nedit the `catalog` and the `package.json`, and then run `pnpm i` to\ninstall the update, at this point, the following happens to pnpm-hoist-layer.\n\n* make a temporary directory(tmpDir), and write the `.pnpmfile.cjs` hook.\n* start the sub-process, `pnpm -r i --resolution-only --lockfile-dir=tmpDir`\n* sub-process quickly resolves packages related to hoistLayer\n* top-process parse stdout of sub-process as hoistLayer metadata\n* top-process merges hoistLayer metadata via hooks\n\nthe hoistLayer metadata is `📝 hoist-layer.json` in the console,\n\n```json\n[\n  {\n    \"name\": \"hoist1\",\n    \"dependencies\": {\n      \"date-fns\": \"catalog:h1\",\n      \"lodash-es\": \"catalog:h1\"\n    },\n    \"devDependencies\": {}\n  },\n  {\n    \"name\": \"hoist2\",\n    \"dependencies\": {\n      \"date-fns\": \"catalog:h2\",\n      \"hoist1\": \"workspace:*\",\n      \"lodash-es\": \"catalog:h1\"\n    },\n    \"devDependencies\": {},\n    \"hoistLayer\": [\n      \"hoist1\"\n    ]\n  }\n]\n```\n\n## Usage\n\n(1) add `layer` to the package.json\n\n* `hoistLayer` - to define which is the layer\n* `*dependencies` - for package resolution\n\n```diff\n  \"devDependencies\": {\n+   \"@fessional/razor-common\": \"file:../common\",\n  },\n+ \"hoistLayer\": [\n+   \"@fessional/razor-common\",\n+ ]\n```\n\n(2) write `.pnpmfile.cjs` to hook\n\n```bash\n## 💾 opt-1: project install and require\npnpm add -D pnpm-hoist-layer\ncat \u003e .pnpmfile.cjs \u003c\u003c 'EOF'\nconst pnpmfile = {};\ntry {\n  pnpmfile.hooks = require('pnpm-hoist-layer').hooks;\n} catch {\n  console.warn('⚠️ \"pnpm-hoist-layer\" not found, retry after installing.');\n}\nmodule.exports = pnpmfile;\nEOF\n\n## 📦 opt-2: write the content to .pnpmfile.cjs\ncurl -o .pnpmfile.cjs https://raw.githubusercontent.com/trydofor\\\n/pnpm-hoist-layer/main/index.js\n\n## Known Issues\n\nthe deps tree are resolved from top to bottom, and hoist from bottom to top, it's a reverse process.\n\n* ✅ shared-workspace-lockfile=false, may 🐞 [peers](https://github.com/pnpm/pnpm/issues/8538)\n* ✅ monorepo + shared-workspace-lockfile=false, but 🐞 [default=true](https://github.com/vuejs/language-tools/issues/4860)\n* ✅ pnpm cli at top-dir, but 🐞 sub-dir (`packages/*`)\n* ✅ `--resolution-only` resolve `devDependencies`, but ❗ `pnpm i` NOT.\n* ❗ do NOT use [`link:`](https://pnpm.io/cli/link), it do NOT hook\n* ❗ do NOT deps indirectly , 2+ level deps NOT resolved\n* ❗ this is a [bad practice](https://github.com/pnpm/pnpm/issues/8588)\n\n## Node and Pnpm\n\nmanages `nodejs` version by [asdf](https://asdf-vm.com)\nand `pnpm` by [corepack](https://nodejs.org/api/corepack.html)\n\n```bash\ngit clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.15.0\n## config zsh\ncat \u003e\u003e ~/.zshrc \u003c\u003c 'EOF'\nexport ASDF_NODEJS_AUTO_ENABLE_COREPACK=true\nexport ASDF_NODEJS_LEGACY_FILE_DYNAMIC_STRATEGY=latest_installed\nsource \"$HOME/.asdf/asdf.sh\"\nEOF\n## support .nvmrc or .node-version\ncat \u003e\u003e ~/.asdfrc \u003c\u003c 'EOF'\nlegacy_version_file=yes\nEOF\n\n## install nodejs plugin\nasdf plugin add nodejs\n## install nodejs and corepack enable\nasdf install nodejs\n## by package.json and corepack\npnpm -v\n## Corepack is about to download\n\n## init workspace top-project first\npnpm -w i --ignore-pnpmfile\n## init workspace sub-project\npnpm -r i\n## to debug with env DEBUG != null\nDEBUG=1 pnpm i\n## ignore if error\npnpm i --ignore-pnpmfile --ignore-scripts\n```\n\n## Test and Diff\n\n```tree\nGlossary\n├── multi pkg, one git (often called \"monorepo\")\n│   ├── with workspace (termed as \"mono\")\n│   └── without workspace (termed as \"poly\")\n└── one pkg, one git (termed as \"solo\")\n```\n\n```bash\nnode -v #v20.16.0\npnpm -v #9.12.1\n\npnpm test\n# ✅ Success mono1, npmrc={}\n# ✅ Success mono1, npmrc={\"shared-workspace-lockfile\":false}\n# ✅ Success mono2, npmrc={}\n# ✅ Success mono2, npmrc={\"shared-workspace-lockfile\":false}\n# ✅ Success poly1, npmrc={}\n# ✅ Success poly2, npmrc={}\n# ✅ Success hoist, npmrc={}\n```\n\n* hoist - hoist auto/manual testing\n* mono1 - multi-pkg + workspace, sub `hoistLayer`\n* mono2 - multi-pkg + workspace, top `hoistLayer`\n* poly1 - multi-pkg, sub `hoistLayer`\n* poly2 - multi-pkg, top `hoistLayer`\n* solo - single pkg as deps for test\n\n### Mono before and after\n\ndiff `mono` from `pnpm -r i --ignore-pnpmfile` to `pnpm -r i` like this,\n\n```diff\n## pnpm -r list\nLegend: production dependency, optional only, dev only\nmono-test-0@1.0.0 pnpm-hoist-layer/test/mono/packages/pkg0\n+ dependencies:\n+   solo-prd-dep link:../../solo/prd\n= devDependencies:\n=   mono-test-1 link:../pkg1\n+   mono-test-2 link:../pkg2\n+   solo-dev-dep link:../../solo/dev\nmono-test-1@1.0.0 pnpm-hoist-layer/test/mono/packages/pkg1\n+ dependencies:\n+   solo-prd-dep link:../../solo/prd\n= devDependencies:\n=   mono-test-2 link:../pkg2\n+   solo-dev-dep link:../../solo/dev\nmono-test-2@1.0.0 pnpm-hoist-layer/test/mono/packages/pkg2\n= dependencies:\n=   solo-prd-dep link:../../solo/prd\n= devDependencies:\n=   solo-dev-dep link:../../solo/dev\n\n## tree -L 4\n✅ mono\n= ├── node_modules\n= ├── package.json\n= ├── packages\n= │   ├── pkg0\n= │   │   ├── node_modules\n= │   │   │   ├── mono-test-1 -\u003e ../../../node_modules/.pnpm/\n+ │   │   │   └── mono-test-2 -\u003e ../../pkg2\n+ │   │   │   ├── solo-dev-dep -\u003e ../../../solo/dev\n+ │   │   │   ├── solo-prd-dep -\u003e ../../../solo/prd\n= │   │   └── package.json\n= │   ├── pkg1\n= │   │   ├── node_modules\n= │   │   │   └── mono-test-2 -\u003e ../../pkg2\n+ │   │   │   ├── solo-dev-dep -\u003e ../../../solo/dev\n+ │   │   │   ├── solo-prd-dep -\u003e ../../../solo/prd\n= │   │   └── package.json\n= │   └── pkg2\n= │       ├── node_modules\n= │       │   └── solo-dev-dep -\u003e ../../../solo/dev\n= │       │   ├── solo-prd-dep -\u003e ../../../solo/prd\n= │       └── package.json\n= ├── pnpm-lock.yaml\n= └── pnpm-workspace.yaml\n```\n\n### Poly before and after\n\ndiff `poly` from `pnpm -r i --ignore-pnpmfile` to `pnpm -r i` like this,\n\n```diff\n## pnpm -r list\nLegend: production dependency, optional only, dev only\npoly-test-0@1.0.0 pnpm-hoist-layer/test/poly/packages/pkg0\n+ dependencies:\n+   solo-prd-dep link:../../solo/prd\n= devDependencies:\n=   poly-test-1 file:../pkg1\n+   poly-test-2 file:../pkg2\n+   solo-dev-dep link:../../solo/dev\npoly-test-1@1.0.0 pnpm-hoist-layer/test/poly/packages/pkg1\n+ dependencies:\n+   solo-prd-dep link:../../solo/prd\n= devDependencies:\n=   poly-test-2 file:../pkg2\n+   solo-dev-dep link:../../solo/dev\npoly-test-2@1.0.0 pnpm-hoist-layer/test/poly/packages/pkg2\n= dependencies:\n=   solo-prd-dep link:../../solo/prd\n= devDependencies:\n=   solo-dev-dep link:../../solo/dev\n\n## tree -L 4\n✅ poly\n= ├── package.json\n= ├── packages\n= │   ├── pkg0\n= │   │   ├── node_modules\n= │   │   │   ├── poly-test-1 -\u003e .pnpm/\n+ │   │   │   └── poly-test-2 -\u003e .pnpm/\n+ │   │   │   ├── solo-dev-dep -\u003e ../../../solo/dev\n+ │   │   │   ├── solo-prd-dep -\u003e ../../../solo/prd\n= │   │   ├── package.json\n= │   │   └── pnpm-lock.yaml\n= │   ├── pkg1\n= │   │   ├── node_modules\n= │   │   │   └── poly-test-2 -\u003e .pnpm/\n+ │   │   │   ├── solo-dev-dep -\u003e ../../../solo/dev\n+ │   │   │   ├── solo-prd-dep -\u003e ../../../solo/prd\n= │   │   ├── package.json\n= │   │   └── pnpm-lock.yaml\n= │   └── pkg2\n= │       ├── node_modules\n= │       │   ├── solo-dev-dep -\u003e ../../../solo/dev\n= │       │   └── solo-prd-dep -\u003e ../../../solo/prd\n= │       ├── package.json\n= │       └── pnpm-lock.yaml\n= └── pnpm-lock.yaml\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrydofor%2Fpnpm-hoist-layer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrydofor%2Fpnpm-hoist-layer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrydofor%2Fpnpm-hoist-layer/lists"}