{"id":20022275,"url":"https://github.com/lucifier129/monodic","last_synced_at":"2025-05-05T01:31:26.917Z","repository":{"id":77718090,"uuid":"335165365","full_name":"Lucifier129/monodic","owner":"Lucifier129","description":"Monorepo: 多项目单仓库工程管理方案","archived":false,"fork":false,"pushed_at":"2021-02-02T06:34:41.000Z","size":212,"stargazers_count":15,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-08T14:52:31.369Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/Lucifier129.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2021-02-02T04:12:24.000Z","updated_at":"2025-03-26T07:48:25.000Z","dependencies_parsed_at":null,"dependency_job_id":"edad0cbb-294f-4ce2-90ce-8897f12afce6","html_url":"https://github.com/Lucifier129/monodic","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lucifier129%2Fmonodic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lucifier129%2Fmonodic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lucifier129%2Fmonodic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lucifier129%2Fmonodic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Lucifier129","download_url":"https://codeload.github.com/Lucifier129/monodic/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252423165,"owners_count":21745553,"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-11-13T08:39:47.047Z","updated_at":"2025-05-05T01:31:26.906Z","avatar_url":"https://github.com/Lucifier129.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# monodic\n\n多项目单仓库工程管理方案\n\nmonodic 是一个低侵入性的多项目单仓库（Monorepo）工程化管理工具。\n\n通过这种文件夹之间的切换处理，实现对项目所依赖的框架、工程化设施解耦，不管是什么框架的项目，开发方式都跟原来的一样。\n\n仅仅改变了启动命令，从项目文件夹里 `npm run xxx` 转变成 `monodic start`.\n\n[点击这里进行问题排查](#faq)\n\n## 使用\n\n### 第一步：配置 package.json 的 scripts 命令\n\n**支持的 node.js 版本为 10.15.00 以上，如低于该版本，请先升级一下 node.js**\n\n在项目根目录下新建 package.json 或者执行 `npm init -y` 自动生成。\n\n执行一下命令安装 monodic\n\n```shell\nnpm install --save-dev monodic \n```\n\n添加 `monodic` 对应的命令\n\n- `monodic start`：运行 `monodic` 项目任务管理，选择项目，选择命令即可。\n  - 这个命令实际上是帮开发者去运行：`npm run start`, `npm run build`, `npm run test`\n- `monodic release`：运行 `monodic` 项目发布，选择要发布的项目即可\n  - 这个命令实际上是帮开发者将指定的文件夹，发布到 git 的指定分支\n- `monodic reset`：将所有 links 的软链接关系重置成初始状态\n  - 这个命令是在文件夹关联关系凌乱时用以恢复，它会将真实文件夹放到 src 字段的位置。\n- `monodic release-all`：将 `config.release` 里的全部项目发布到它们指定的分支\n  - 相比 `monodic release` 需要选择一个项目，该命令发布所有项目\n- `monodic command`：在链接关系重置状态下输入指令，方便进行 git add/commit 等操作\n\n```json\n{\n  \"scripts\": {\n    \"start\": \"monodic start --mode=copy\",\n    \"release\": \"monodic release --mode=copy\",\n    \"release-all\": \"monodic release-all --mode=copy\",\n    \"reset\": \"monodic reset\",\n    \"command\": \"monodic command\"\n  }\n}\n```\n\n然后\n\n- `npm run start` 运行某个项目，monodic 启动时，会注入 `IS_MONODIC=YES` 环境变量，可以根据该变量，调整配置属性等\n- `npm run release` 发布某个项目\n- `npm run reset` 重置文件夹的关联关系\n- `npm run release-all` 发布所有项目\n- `npm run command` 在重置状态下运行指令\n\n#### 快速启动\n\n```json\n{\n  \"scripts\": {\n    \"start:h5\": \"monodic start --project=projects/h5 --script=start\",\n    \"release:h5\": \"monodic release --project=h5\"\n  }\n}\n```\n\n从 `monodic v1.3.0` 开始，支持命令行参数，可以跳过选择，直接启动目标项目和脚本任务。\n\n这个功能只对 `monodic start` 和 `monodic release` 生效。\n\n- `monodic start --mode={copy/exchange} --project={your project path} --script={your script name}`\n\n  - `mode` 选择启动模式，默认是`exchange`模式，即交换真实文件夹和软链接的位置。\n    设置 `copy` 模式时，将在项目目录下新建 `.monodic`，将源码文件拷贝进该目录，后续所有命令都将在 `.monodic` 目录里执行\n  - `project` 参数选择项目，参数值跟 cli 里列出的路径保持一致\n  - `script` 参数选择 `package.json` 里的 `npm scripts` 的 key，比如 `start`、`test`、`build` 等（不需要写全 `npm run start`）\n\n- `monodic release --mode={copy/exchange} --project={your project key}`\n  - `mode` 选择启动模式，默认是`exchange`模式，即交换真实文件夹和软链接的位置。\n    设置 `copy` 模式时，将在项目目录下新建 `.monodic`，将源码文件拷贝进该目录，后续所有命令都将在 `.monodic` 目录里执行\n  - `project` 参数选择项目，它跟 `monodic start` 里不同，它不是路径，而是 `monodic.config.js` 的 `release` 配置里的 key 值，即跟 cli 里列出来的保持一致。\n\n### 第二步：新建 monodic.config.js\n\n- `config.links` 配置共享文件夹，数组结构，每个 item 包含 { src, dest } 两个字段\n  - `src` 字段为字符串类型，表示源文件夹位置\n  - `dest` 为数组类型，表示需要跟 src 进行软链接关联起来的文件夹地址\n- `config.release` 配置项目的发布目录和分支，对象结构，对象的 key 为项目名，对象的 value 包含 { src, branch } 两个必选字段\n  - `src` 必选：项目的发布内容所在的目录地址\n  - `dest` 可选：项目在当前分支的发布目录\n  - `branch` 可选：项目发布的目标分支\n  - `message` 可选：提交达到发布分支时的 commit message，支持函数和异步函数，`async ({ name: string, version: string }) =\u003e string` 返回 message 字符串。\n  - `include` 可选字段，匹配要发布的文件，匹配规则见[gh-pages 的 options.src 文档](https://github.com/tschaub/gh-pages#optionssrc)\n  - `prerelease` 可选：项目发布前需要执行的命令（cwd 会切换到 src 目录最近的 pakcage.json 所在的目录），可以在这里填写 `npm run build` 等构建命令\n  - `postrelease` 可选：项目发布后需要执行的命令（cwd 会切换到 src 目录最近的 package.json 所在的目录），可以在这里填写 `git add -A \u0026\u0026 git commit -m \"PKG:xxx\"` 提交 git 的命令\n  - `ignoreSrcPackage` 可选：`release.src` 发布目录也可能出现 `package.json`，设置 `ignoreSrcPackage` 为 `true` 可以跳过 `release.src` 目录的 `package.json`，在 `release.src` 父级目录里寻找 `package.json`，去执行 `prerelease` 和 `postrelease`\n- `config.ignorePackages` 配置需要忽略扫描的 `package.json` 目录，数组类型\n\n```javascript\nconst { createConfig } = require(\"monodic\");\n\nmodule.exports = createConfig({\n  // 忽略扫描 package.json 的目录\n  ignorePackages: [\n    \"publish\",\n    \"packages/pure-model/dist\",\n    \"projects/react-imvc/publish\",\n  ],\n  // 构建共享目录的分配方式\n  links: [\n    {\n      // 源代码目录\n      src: \"./projects/isomorphic/src\",\n      // 需要软链接过去的目录\n      dest: [\n        \"./projects/react-imvc/src/isomorphic\",\n        \"./projects/react-app/src/isomorphic\",\n      ],\n    },\n    {\n      src: \"./packages/pure-model/src\",\n      dest: [\"./projects/isomorphic/src/pure-model\"],\n    },\n  ],\n  // 发布配置\n  release: {\n    \"react-imvc\": {\n      src: \"./projects/react-imvc\",\n      dest: \"./publish/react-imvc\",\n      prerelease: \"npm run build:imvc\",\n      postrelease: `git add -A \u0026\u0026 git commit -m \"PKG:react-imvc\"`,\n      branch: \"react-imvc-release\",\n    },\n    \"react-app\": {\n      src: \"./projects/react-app/build\",\n      branch: \"react-app-release\",\n      message: \"测试自定义发布的 git commit message\",\n    },\n    monodic: {\n      src: \"./packages/monodic\",\n      branch: \"monodic-release\",\n    },\n    \"pure-model\": {\n      src: \"./packages/pure-model/dist\",\n      branch: \"pure-model-release\",\n    },\n  },\n});\n```\n\n### 异步配置\n\n自 `v1.6.0` 版本开始。`monodic` 支持异步配置，使用方式如下：\n\n```javascript\nconst { createConfig } = require(\"monodic\");\n\nmodule.exports = async () =\u003e {\n  // 使用异步函数获取动态配置\n  return createConfig({\n\n  })\n}\n```\n\n## Copy VS Exchange\n\n自 `v1.5.1` 版本开始，`monodic` 支持两种启动模式：`Copy` 和 `Exchange`。默认采取 `Exchange` 模式。\n\n### Exchange Mode\n\n它的工作原理是：\n\n- 配置文件夹之间的关联关系，保留唯一的真实文件夹，其余地址都设置为软链接，指向该真实文件夹。实现文件内容的唯一性和位置的多样性。\n- 通过 cli 接管多个项目的运行脚本，间接启动不同的 `projects/{name}` 的项目：`monodic start`\n  - 在命令行里选择项目文件夹，然后选择所要执行的 `npm scripts` 命令\n  - `monodic` 会在执行命令前，进行文件夹切换\n    - step1: 将真实的文件夹移动到待启动的项目所在的目录，将原来的位置设置为软链接\n    - step2: 运行开发者选中的命令\n    - step3: 运行结束后，重置文件夹之间的软链接关系\n- 通过 cli 将不同的项目的 build 产物，发布到不同的分支，实现根目录下只有一个项目的功能，可适配对仓库目录结构有要求的发布系统。\n\n### Copy Mode\n\n`Copy` 模式下，`monodic` 会将项目里除 `node_modules|.git` 以外的文件，拷贝进 `.monodic` 目录，并在 `.monodic` 目录里启动应用。\n\n`monodic` 会监听项目里的文件变动，实时同步到 `.monodic` 目录，对开发者保持透明。开发者无须编辑`.monodic`目录里的代码。\n\nCopy 模式启动时有 copy 文件的动作，如果项目文件里存在大文件，可能拖慢启动速度。如果这些大文件跟运行无关，可以在 `monodic.config.js` 中设置 `ignoreFiles` 将它们忽略。\n\n注：建议在根目录的 `.gitignore` 里添加 `.monodic`，让 `git` 忽略它们。\n\n### Copy Or Exchange\n\n`monodic` 默认采用 `Exchange` 模式，但只是处于历史原因，`Exchange` 比 `Copy` 更早开发。推荐使用 `Copy` 模式。\n\n`Copy` 模式解决了 `Exchange` 的以下问题：\n\n- `Exchange` 模式一次只能运行一个项目，`Copy` 模式可以同时运行多个项目\n- `Exchange` 模式运行时，对 git 来说，有文件删除和移动的变化，`Copy` 模式对 git commit 更友好。不必运行 `monodic command`，直接使用 `git` 命令提交代码。\n- `Exchange` 模式包含一定失败风险，失败后，可能删除了部分代码，需要通过 git checkout 等方式恢复。`Copy` 模式几乎不会改动源文件，更加安全可靠。\n\n### 从 `Exchange` 模式迁移到 `Copy` 模式步骤\n\n- 在根目录的 `.gitignore` 文件中添加 `.monodic`\n- 在根目录的 `package.json` 添加命令行参数\n  - `monodic start --mode=copy`\n  - `monodic release --mode=copy`\n  - `monodic release-all --mode=copy`\n\n\n### React-Native 项目 Copy 模式配置方式\n\nReact-Native 项目使用 Metro 配置的，除以上配置以外，还需按照一下方式配置 `metro.config.js`，以避免启动时报 node_modules 里的模块引用错误。\n\n注意：如果使用 `crn-cli` 启动项目，需要再新增 `metro.bu.config.js` 配置文件，内容跟 `metro.config.js` 一致。\n\n```javascript\n/**\n * Metro configuration for React Native\n * https://github.com/facebook/react-native\n */\n\nconst path = require(\"path\");\n\nconst blacklist = require(\"metro-config/src/defaults/blacklist\");\nconst escapeRegexString = require(\"escape-regex-string\");\n\nlet monodicWatchFolders = [];\nlet monodicBlackList = [];\n\nif (process.env.IS_MONODIC === \"YES\") {\n  // monodic copy 模式启动时，代码被复制到 .monodic 目录，真实的 node_module 文件夹在父级目录\n  monodicWatchFolders = [\n    path.resolve(__dirname, \"../node_modules\"),\n  ];\n  // monodic copy 模式启动时，代码被复制到 .monodic 目录，忽略 .monodic 目录下的 node_modules 软链\n  monodicBlackList = [\n    new RegExp(\n      `^${escapeRegexString(path.resolve(__dirname, \"node_modules\"))}\\\\/.*$`\n    ),\n  ];\n} else {\n  // 非 monodic copy 模式启动时，忽略 .monodic 目录，避免检索出多个 package.json\n  monodicBlackList = [\n    new RegExp(\n      `^${escapeRegexString(path.resolve(__dirname, \".monodic\"))}\\\\/.*$`\n    ),\n  ];\n}\n\nmodule.exports = {\n  watchFolders: [...monodicWatchFolders],\n  resolver: {\n    blacklistRE: blacklist([...monodicBlackList]),\n  },\n  transformer: {\n    getTransformOptions: async () =\u003e ({\n      transform: {\n        experimentalImportSupport: true,\n        inlineRequires: true,\n      },\n    }),\n  },\n};\n```\n\n\n## FAQ\n\n问题排查\n\n### 为什么运行时报错 Error: EPERM: operation not permitted ？\n\n可能原因是 VSCode 编辑器跟命令行对于同一个文件的操作权限产生了冲突。可以先关闭 VSCode，再运行命令，然后打开 VSCode 即可。\n\n报此错误时，文件夹可能已经被 monodic 操作，但未完成正确的衔接，可能产生冗余文件夹等。\n\n可运行 `npm run reset` 启动重置命令，恢复文件夹。如继续报错提示，请按照提示进行操作。\n\n注：在执行有风险的命令之前，可通过 `git add -A` 先将文件保存在 git 的工作区。而后在运行命令不达预期后，可通过 `git checkout .` 撤销修改。git 会将文件恢复到跟工作区的版本保持一致的状态。\n\n### 为什么运行项目时报错 `xxx is not install`，模块依赖没有安装 ？\n\n这是由于每个项目都是独立的，需要各自安装 `npm install --save {name}` 依赖。\n\n即，如果一个共享模块依赖的 `redux`，所有使用该模块的项目，都要自行 `npm install --save redux`。\n\n`monodic` 只是一个文件夹切换和项目命令管理工具，并不知晓模块之间的依赖关系。\n\n### 为什么 windows 里软链接创建失败？\n\n在 windows 里通过脚本创建软链接需要管理员权限，可以设置 windows 的 `本地策略/Local Policies` 开启。\n\n可以点击 Ember 的文档：[Enabling symlinks](https://cli.emberjs.com/release/appendix/windows/#enablingsymlinks) 按步骤设置\n\n### 为什么运行 `monodic release` 时报错 “branch already exists”？\n\n这是 `monodic` 依赖的发布工具 `gh-pages` 的已知问题，可以通过删除 `node_modules/gh-pages/.cache` 缓存文件来解决。\n\n见 `gh-pages` 文档[查看更多](https://github.com/tschaub/gh-pages#when-get-error-branch-already-exists)\n\n### 为什么报错“配置中的路径所对应的本地文件夹都不存在，请添加一个文件夹”？\n\n这是因为 `monodic.config.js` 里的 links 配置里，`src` 和 `dest` 都不存在，无法进行软链接关联。\n\n请先添加一个源文件夹，之后再重新启动。\n\n### 为什么报错“预期只有一个真实的文件夹，其余为软链接。目前找到的文件夹数量为 n”？\n\n这是因为 `monodic` 的工作原理是，保留唯一的真实文件夹，其余为软链接。\n\n有可能因为一些不可预料的原因，软链接变成了真实文件夹。\n\n解决方案是，将多余的文件夹删除，只保留一份真实文件夹。再重新启动。\n\n### 为什么报错“预期只有一个真实的文件夹，其余为软链接。目前找到 n 个真实文件。请删除多余的”？\n\n原因同上。\n\ngit 对软链接的处理方式是，将它变成一个文件，内部是它软链接到的真实地址。\n\ngit 有可能在 git pull 时没有正确的将上述包含真实地址的文件，转换成软链接。它成了真实的文件。\n\n将这些冗余文件删除，只保留一份真实文件夹。再启动即可。\n\n另外，windows 里安装 git 时，如果没有勾选 `Enable symbolic links`，git 不会在 git pull 后转换成软链接。\n\n可以通过卸载 git，重新下载/安装时勾选该选项。\n\n![git](./files/git-symlink.png)\n\n### 为什么运行 `monodic release` 发布到 git 分支时，总是提示输入 username 和密码？\n\ngit 需要设置保存用户信息后，才可以不输入用户名和密码。\n\n可以通过 git 文档设置保存用户信息: [](https://git-scm.com/docs/git-credential-store#_examples)\n\n或参考下面的命令：\n\n```shell\n$ git config credential.helper store\n$ git push http://example.com/repo.git\nUsername: \u003ctype your username\u003e\nPassword: \u003ctype your password\u003e\n\n[several days later]\n$ git push http://example.com/repo.git\n[your credentials are used automatically]\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucifier129%2Fmonodic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flucifier129%2Fmonodic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucifier129%2Fmonodic/lists"}