{"id":20951380,"url":"https://github.com/lgug2z/triforce","last_synced_at":"2026-04-20T20:03:29.956Z","repository":{"id":104319654,"uuid":"145883861","full_name":"LGUG2Z/triforce","owner":"LGUG2Z","description":"Assemble and link node dependencies across meta and monorepo projects","archived":false,"fork":false,"pushed_at":"2018-08-24T13:55:03.000Z","size":18,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-13T05:29:23.612Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LGUG2Z.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2018-08-23T17:04:34.000Z","updated_at":"2023-07-25T14:19:26.000Z","dependencies_parsed_at":null,"dependency_job_id":"6edd28dd-18b8-47d2-ae6f-c38a8453e274","html_url":"https://github.com/LGUG2Z/triforce","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/LGUG2Z/triforce","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LGUG2Z%2Ftriforce","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LGUG2Z%2Ftriforce/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LGUG2Z%2Ftriforce/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LGUG2Z%2Ftriforce/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LGUG2Z","download_url":"https://codeload.github.com/LGUG2Z/triforce/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LGUG2Z%2Ftriforce/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28038027,"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","status":"online","status_checked_at":"2025-12-25T02:00:05.988Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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-11-19T00:58:38.238Z","updated_at":"2025-12-25T21:01:52.719Z","avatar_url":"https://github.com/LGUG2Z.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# triforce\n[![Go Report Card](https://goreportcard.com/badge/github.com/lgug2z/triforce)](https://goreportcard.com/report/github.com/lgug2z/triforce)\n[![Maintainability](https://api.codeclimate.com/v1/badges/2296d2da4304647c9bcc/maintainability)](https://codeclimate.com/github/LGUG2Z/triforce/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/2296d2da4304647c9bcc/test_coverage)](https://codeclimate.com/github/LGUG2Z/triforce/test_coverage)\n[![Build Status](https://travis-ci.org/LGUG2Z/triforce.svg?branch=master)](https://travis-ci.org/LGUG2Z/triforce)\n\n`triforce` assembles and links `node` dependencies across meta and monorepo projects.\n\n## Installation and Quickstart\n### Binary\n```bash\ngo get -u github.com/LGUG2Z/triforce\n\ncd /your/meta/or/mono/repo\ntriforce assemble --exclude whatever .\n\nnpm install\n\ntriforce link .\n```\n\n### Docker Image\n```\ndocker pull lgug2z/triforce:latest\ndocker run --workdir /tmp --volume ${HOME}/projects:/tmp lgug2z/triforce:latest assemble .\n\nnpm install\n\ndocker run --workdir /tmp --volume ${HOME}/projects:/tmp lgug2z/triforce:latest link .\n```\n\n### Docker-Compose\n```yaml\nversion: '3.6'\n\nservices:\n  assemble:\n    image: lgug2z/triforce\n    volumes:\n      - /your/meta/or/mono/repo:/tmp\n    working_dir: /tmp\n    command: assemble .\n\n  link:\n    image: lgug2z/triforce\n    volumes:\n      - /your/meta/or/mono/repo:/tmp\n    working_dir: /tmp\n    command: link .\n```\n\n### Example Docker CI Image\nIf you want to run a scheduled CI job that can assemble all of your project\ndependencies, and have the required apt dependencies to install them and\ncompile any native extensions they might require:\n```bash\ndocker pull lgug2z/triforce:ci\n```\n\n## When this can be useful\n### Multi projects with their own package.json file\nImagine you have a codebase that consists of multiple different node projects,\nand that each of these projects has its own `package.json` file which requires\ninstalling for the project to be developed.\n\n```bash\nexample-metarepo\n├── api-1\n│   └── package.json\n├── api-2\n│   └── package.json\n├── api-3\n│   └── package.json\n├── app-1\n│   └── package.json\n├── app-2\n│   └── package.json\n├── app-3\n│   └── package.json\n├── lib-1\n│   └── package.json\n├── lib-2\n│   └── package.json\n└── lib-3\n    └── package.json\n```\n\nNow imagine that your libraries are so tighly coupled with your api or app\nprojects that it is never possible to implement a new feature in an api or\nan app without making changes in one or more of the underlying libraries.\n\n\n### Too many projects for an 'npm link'-driven workflow\nAs changes will need to be pushed to multiple projects at the same time to\nensure successful builds and deployments, there is a need for a local\ndevelopment flow that allows for development against changes in those libraries\nthat are still on the local or remote development machine. This is usually where\n`npm link` comes in for smaller scale projects. But what if you have tens of\nlibraries? And apis? And apps? It quickly becomes unwieldy.\n\n### Too many projects for a 'zelda'-driven workflow\nAt this point you may be able to turn to a tool like [zelda](https://github.com/feross/zelda),\nwhich takes advantage of the way that `node` tries to look for dependencies on a filesystem.\nImage one of your projects is checked out at `/Users/ponyo/dev/example-metarepo/lib-1`. `node`\nwill try to resolve the dependencies for this project by looking for a `node_modules` folder\ncontaining the required dependency in every directory up the tree all the way to `/node_modules`:\n\n```bash\n# looking for dependency 'lib-2'\n\n/Users/ponyo/dev/example-metarepo/lib-1/node_modules/lib-2 # doesn't exist, let's try -\u003e |\n#                                                                                        |\n# |--------------------------------------\u003c--------------------------------------------\u003c- |\n# v\n/Users/ponyo/dev/example-metarepo/node_modules/lib-2 # doesn't exist, let's try -\u003e |\n#                                                                                  |\n# |-----------------------------------\u003c------------------------------------------\u003c-|\n# v\n/Users/ponyo/dev/node_modules/lib-2 # doesn't exist, let's try -\u003e |\n#                                                                 |\n# |------------------------------\u003c------------------------------\u003c-|\n# v\n/Users/ponyo/node_modules/lib-2 # exists! let's use it!\n```\n\nThe problem, however, is that `zelda` is not very efficient; it will install dependencies\nof a project in its `package.json` files, including any private dependencies referenced with VCS\nURLs, then look one level up to see if it has installed any dependencies with names matching projects\nthat live in the parent folder. If any are found, the dependency as installed in the project\n`node_modules` folder will be removed, and then a separate `npm install` will be run in the checked out\ncopy of the dependent project. And this process will keep happening recursively until there is nothing\nelse left to clean up and reinstall in a checked out project in the parent folder. Finally, a symlink\n`node_modules` is created in the parent folder that points to itself, leaving you with this:\n\n```bash\nlrwxr-xr-x    1 ponyo  staff     1 11 Jul 16:07 node_modules -\u003e .\n```\n\nThis is great, because any project can use a local version of a private dependency for development\npurposes and they can all be checked in together when the time is right. However, running `zelda`\nindividually on tens of repositories becomes incredibly slow and can take many hours just to do\nan initial installation to get a codebase up and running for the first time. Not to mention, every\nsubsequent time `zelda` is run, every public dependency that was previously downloaded gets blown\naway.\n\n### triforce workflows\n\n`triforce` ultimately has two tasks:\n* assemble all of the `dependencies` and `devDependencies` across a collection of projects into a single \n`package.json` file\n* create symlinks of any checked out private dependencies within the top-level `node_dependencies` folder \nwhich contains dependencies from the assembled `package.json` file\n\nThese tasks are completed with two commands:\n```bash\ntriforce assemble ~/my/meta/or/mono/repo\n```\n\n```bash\n# after the assembled package.json file has been installed\ntriforce link ~/my/meta/or/mono/repo\n```\n\nThey leave you with something resembling:\n\n```bash\nexample-metarepo\n├── api-1\n├── app-1\n├── lib-1\n└── node_modules\n    ├── acl \n    ├── api-1 -\u003e ../api-1\n    ├── app-1 -\u003e ../app-1\n    ├── lib-1 -\u003e ../lib-1\n    ├── lodash\n    ├── node-sass\n    └── react\n```\n\n#### Dependency versions\nWhen dealing with a codebase comprised of a large number of `node` projects, it will almost always be the\ncase that different projects will require ever so slightly different versions of the same dependency, or\nthat the same dependency will be a listed as a `dependency` in one project and a `devDependency` in another.\nFor now, `triforce` deals with this in a simple way:\n* If the same dependency is listed with different versions across projects, pick the highest version\n* If the same dependency is listed as a `dependency` and a `devDependency` across projects, promote it to \na `dependency` with the higher version\n\n\n### Excluding private dependencies\n`triforce` by default excludes any dependencies where the version contains `bitbucket`, `github` or `gitlab`.\nAdditional exclusions can be specified by using the `--exclude` flag when running the `assemble` command:\n\n```bash\ntriforce assemble --exclude MySecretGithubOrgName ~/path/to/my/meta/or/mono/repo\n```\n\n### Making developer onboarding even faster\n`triforce` can be used to take a `zelda` workflow that takes ~5 hours for an initial install across an\nentire codebase down to 20 minutes. Not bad, but still not great. If a team develops in a Dockerised\ndevelopment environment, then it is already a given that everyone is running against the same version\nof `node` on the same operating system and distro.\n\nWith this in mind, a task can be scheduled to run on a CI platform that checks out the latest version\nof a meta or monorepo, runs `triforce assemble` and then compresses and uploads the resulting `node_modules`\nfolder to something like S3 or GCS, where developers can fetch the latest dump of installed dependencies\nfrom every morning.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flgug2z%2Ftriforce","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flgug2z%2Ftriforce","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flgug2z%2Ftriforce/lists"}