create-bubbles 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/dist/index.js +21 -0
  2. package/index.js +1 -1
  3. package/package.json +24 -24
  4. package/template-taro-react-oxc/.env.development +1 -1
  5. package/template-taro-react-oxc/.env.production +1 -1
  6. package/template-taro-react-oxc/project.config.json +1 -1
  7. package/template-taro-vue-eslint/project.config.json +2 -2
  8. package/{template-vp-monorepo-react-nestjs → template-vp-monorepo-react-hono}/.spaces/server//347/231/273/345/275/225/JWT/344/270/216RefreshToken/350/275/256/346/215/242/351/211/264/346/235/203/346/236/266/346/236/204/345/256/236/347/216/260/346/214/207/345/215/227.md +588 -1133
  9. package/template-vp-monorepo-react-hono/.vite-hooks/commit-msg +2 -0
  10. package/template-vp-monorepo-react-hono/.vite-hooks/pre-commit +2 -0
  11. package/template-vp-monorepo-react-hono/.vscode/extensions.json +3 -0
  12. package/template-vp-monorepo-react-hono/.vscode/settings.json +23 -0
  13. package/template-vp-monorepo-react-hono/AGENTS.md +16 -0
  14. package/template-vp-monorepo-react-hono/README.md +54 -0
  15. package/template-vp-monorepo-react-hono/apps/server/.env.example +13 -0
  16. package/template-vp-monorepo-react-hono/apps/server/README.md +75 -0
  17. package/template-vp-monorepo-react-hono/apps/server/drizzle/0000_flaky_madame_masque.sql +18 -0
  18. package/template-vp-monorepo-react-hono/apps/server/drizzle/meta/0000_snapshot.json +153 -0
  19. package/template-vp-monorepo-react-hono/apps/server/drizzle/meta/_journal.json +13 -0
  20. package/template-vp-monorepo-react-hono/apps/server/drizzle.config.ts +19 -0
  21. package/template-vp-monorepo-react-hono/apps/server/package.json +46 -0
  22. package/template-vp-monorepo-react-hono/apps/server/src/app.test.ts +57 -0
  23. package/template-vp-monorepo-react-hono/apps/server/src/app.ts +68 -0
  24. package/template-vp-monorepo-react-hono/apps/server/src/common/errors/app-error.ts +23 -0
  25. package/template-vp-monorepo-react-hono/apps/server/src/common/http/response.ts +21 -0
  26. package/template-vp-monorepo-react-hono/apps/server/src/common/logger.ts +30 -0
  27. package/template-vp-monorepo-react-hono/apps/server/src/config/env.ts +54 -0
  28. package/template-vp-monorepo-react-hono/apps/server/src/index.bun.ts +60 -0
  29. package/template-vp-monorepo-react-hono/apps/server/src/index.ts +73 -0
  30. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/client.ts +46 -0
  31. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/schema/index.ts +1 -0
  32. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/schema/job-runs.ts +42 -0
  33. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/queue/connection.ts +6 -0
  34. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/redis/client.ts +39 -0
  35. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/redis/options.ts +28 -0
  36. package/template-vp-monorepo-react-hono/apps/server/src/modules/health/health.routes.ts +17 -0
  37. package/template-vp-monorepo-react-hono/apps/server/src/modules/health/health.service.ts +59 -0
  38. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.queue.ts +46 -0
  39. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.repository.ts +101 -0
  40. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.routes.ts +46 -0
  41. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.schemas.ts +9 -0
  42. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.service.ts +57 -0
  43. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.types.ts +11 -0
  44. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.worker.ts +108 -0
  45. package/template-vp-monorepo-react-hono/apps/server/src/runtime/shutdown.ts +38 -0
  46. package/template-vp-monorepo-react-hono/apps/server/src/worker.ts +48 -0
  47. package/template-vp-monorepo-react-hono/apps/server/tsconfig.json +15 -0
  48. package/template-vp-monorepo-react-hono/apps/server/vite.config.ts +15 -0
  49. package/template-vp-monorepo-react-hono/apps/web/.env +2 -0
  50. package/template-vp-monorepo-react-hono/apps/web/.env.dev +1 -0
  51. package/template-vp-monorepo-react-hono/apps/web/.env.prod +1 -0
  52. package/template-vp-monorepo-react-hono/apps/web/index.html +13 -0
  53. package/template-vp-monorepo-react-hono/apps/web/package.json +35 -0
  54. package/template-vp-monorepo-react-hono/apps/web/public/favicon.svg +1 -0
  55. package/template-vp-monorepo-react-hono/apps/web/public/icons.svg +24 -0
  56. package/template-vp-monorepo-react-hono/apps/web/src/App.tsx +25 -0
  57. package/template-vp-monorepo-react-hono/apps/web/src/assets/hero.png +0 -0
  58. package/template-vp-monorepo-react-hono/apps/web/src/assets/react.svg +1 -0
  59. package/template-vp-monorepo-react-hono/apps/web/src/assets/vite.svg +1 -0
  60. package/template-vp-monorepo-react-hono/apps/web/src/components/Loading/PageLoading.tsx +11 -0
  61. package/template-vp-monorepo-react-hono/apps/web/src/locales/en_US.json +4 -0
  62. package/template-vp-monorepo-react-hono/apps/web/src/locales/zh_CN.json +4 -0
  63. package/template-vp-monorepo-react-hono/apps/web/src/main.tsx +5 -0
  64. package/template-vp-monorepo-react-hono/apps/web/src/pages/home/index.tsx +47 -0
  65. package/template-vp-monorepo-react-hono/apps/web/src/router/index.tsx +25 -0
  66. package/template-vp-monorepo-react-hono/apps/web/src/router/modules/.gitkeep +0 -0
  67. package/template-vp-monorepo-react-hono/apps/web/src/styles/index.css +7 -0
  68. package/template-vp-monorepo-react-hono/apps/web/src/types/auto-imports.d.ts +58 -0
  69. package/template-vp-monorepo-react-hono/apps/web/src/utils/env/index.ts +6 -0
  70. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/core/index.ts +420 -0
  71. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/core/utils.ts +39 -0
  72. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/index.ts +61 -0
  73. package/template-vp-monorepo-react-hono/apps/web/tsconfig.json +18 -0
  74. package/template-vp-monorepo-react-hono/apps/web/vite.config.ts +45 -0
  75. package/template-vp-monorepo-react-hono/commitlint.config.js +1 -0
  76. package/template-vp-monorepo-react-hono/docker-compose.yml +41 -0
  77. package/template-vp-monorepo-react-hono/mise.toml +4 -0
  78. package/template-vp-monorepo-react-hono/package.json +37 -0
  79. package/template-vp-monorepo-react-hono/packages/i18n/core/package.json +20 -0
  80. package/template-vp-monorepo-react-hono/packages/i18n/core/src/format.ts +9 -0
  81. package/template-vp-monorepo-react-hono/packages/i18n/core/src/i18n.ts +73 -0
  82. package/template-vp-monorepo-react-hono/packages/i18n/core/src/index.ts +24 -0
  83. package/template-vp-monorepo-react-hono/packages/i18n/core/src/storage.ts +47 -0
  84. package/template-vp-monorepo-react-hono/packages/i18n/core/src/store.ts +48 -0
  85. package/template-vp-monorepo-react-hono/packages/i18n/core/src/types.ts +36 -0
  86. package/template-vp-monorepo-react-hono/packages/i18n/core/tsconfig.json +4 -0
  87. package/template-vp-monorepo-react-hono/packages/i18n/core/vite.config.ts +13 -0
  88. package/template-vp-monorepo-react-hono/packages/i18n/react/package.json +28 -0
  89. package/template-vp-monorepo-react-hono/packages/i18n/react/src/index.tsx +26 -0
  90. package/template-vp-monorepo-react-hono/packages/i18n/react/src/use-store.ts +14 -0
  91. package/template-vp-monorepo-react-hono/packages/i18n/react/tsconfig.json +9 -0
  92. package/template-vp-monorepo-react-hono/packages/i18n/react/vite.config.ts +16 -0
  93. package/template-vp-monorepo-react-hono/packages/i18n/vue/package.json +27 -0
  94. package/template-vp-monorepo-react-hono/packages/i18n/vue/src/index.ts +52 -0
  95. package/template-vp-monorepo-react-hono/packages/i18n/vue/src/use-store.ts +19 -0
  96. package/template-vp-monorepo-react-hono/packages/i18n/vue/tsconfig.json +8 -0
  97. package/template-vp-monorepo-react-hono/packages/i18n/vue/vite.config.ts +16 -0
  98. package/template-vp-monorepo-react-hono/pnpm-workspace.yaml +57 -0
  99. package/template-vp-monorepo-react-hono/scripts/clean.mjs +48 -0
  100. package/template-vp-monorepo-react-hono/tsconfig.base.json +10 -0
  101. package/template-vp-monorepo-react-hono/tsconfig.bundler.json +12 -0
  102. package/template-vp-monorepo-react-hono/tsconfig.json +11 -0
  103. package/template-vp-monorepo-react-hono/tsconfig.lib.json +10 -0
  104. package/template-vp-monorepo-react-hono/vite.config.ts +20 -0
  105. package/template-vp-monorepo-react-nestjs/.spaces/server/todo.md +991 -0
  106. package/template-vp-monorepo-react-nestjs/.vscode/launch.json +16 -0
  107. package/template-vp-monorepo-react-nestjs/.vscode/settings.json +21 -18
  108. package/template-vp-monorepo-react-nestjs/apps/server/.env.development +3 -39
  109. package/template-vp-monorepo-react-nestjs/apps/server/.swcrc +8 -0
  110. package/template-vp-monorepo-react-nestjs/apps/server/drizzle.config.ts +8 -3
  111. package/template-vp-monorepo-react-nestjs/apps/server/nest-cli.json +8 -1
  112. package/template-vp-monorepo-react-nestjs/apps/server/package.json +11 -6
  113. package/template-vp-monorepo-react-nestjs/apps/server/src/app.controller.ts +2 -0
  114. package/template-vp-monorepo-react-nestjs/apps/server/src/app.module.ts +20 -15
  115. package/template-vp-monorepo-react-nestjs/apps/server/src/common/adapters/fastify.adapter.ts +16 -0
  116. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/auth.ts +2 -0
  117. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/response.ts +0 -4
  118. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/session.constants.ts +28 -0
  119. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/current-auth.decorator.ts +16 -0
  120. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/public.decorator.ts +4 -0
  121. package/template-vp-monorepo-react-nestjs/apps/server/src/common/dto/api-failure.dto.ts +25 -0
  122. package/template-vp-monorepo-react-nestjs/apps/server/src/common/error/common.error.ts +40 -0
  123. package/template-vp-monorepo-react-nestjs/apps/server/src/common/exceptions/app.exception.ts +55 -0
  124. package/template-vp-monorepo-react-nestjs/apps/server/src/common/filters/global-exception.filter.ts +384 -0
  125. package/template-vp-monorepo-react-nestjs/apps/server/src/common/openapi/response-headers.ts +9 -0
  126. package/template-vp-monorepo-react-nestjs/apps/server/src/config/index.ts +1 -1
  127. package/template-vp-monorepo-react-nestjs/apps/server/src/config/session.config.ts +36 -0
  128. package/template-vp-monorepo-react-nestjs/apps/server/src/database/schema.ts +4 -64
  129. package/template-vp-monorepo-react-nestjs/apps/server/src/main.ts +29 -8
  130. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.controller.ts +55 -0
  131. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.errors.ts +42 -0
  132. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.module.ts +26 -0
  133. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.repository.ts +40 -0
  134. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.service.ts +129 -0
  135. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/auth-repsponse.dto.ts +31 -0
  136. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/login.dto.ts +11 -0
  137. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/register.dto.ts +17 -0
  138. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/guards/session-auth.guard.ts +50 -0
  139. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/password.service.ts +24 -0
  140. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session-store.service.ts +198 -0
  141. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session-token.service.ts +29 -0
  142. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session.script.ts +221 -0
  143. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session.types.ts +24 -0
  144. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/db/db.controller.ts +1 -1
  145. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/db/dto/create-user.dto.ts +3 -5
  146. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/redis/redis.service.ts +1 -1
  147. package/template-vp-monorepo-react-nestjs/apps/server/src/utils/env-arr.ts +3 -0
  148. package/template-vp-monorepo-react-nestjs/apps/server/test/error-catalog.spec.ts +30 -0
  149. package/template-vp-monorepo-react-nestjs/apps/server/tsconfig.build.json +11 -1
  150. package/template-vp-monorepo-react-nestjs/apps/server/tsconfig.json +12 -20
  151. package/template-vp-monorepo-react-nestjs/apps/server/vite.config.ts +15 -0
  152. package/template-vp-monorepo-react-nestjs/apps/web/package.json +1 -0
  153. package/template-vp-monorepo-react-nestjs/apps/web/tsconfig.json +5 -15
  154. package/template-vp-monorepo-react-nestjs/apps/web-vue/commitlint.config.js +1 -1
  155. package/template-vp-monorepo-react-nestjs/apps/web-vue/lefthook.yml +1 -1
  156. package/template-vp-monorepo-react-nestjs/apps/web-vue/package.json +1 -3
  157. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/api/index.ts +5 -5
  158. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/components/Icon/svg-icon.vue +3 -3
  159. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/hooks/chart/lib.ts +6 -6
  160. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/hooks/chart/useEcharts.ts +34 -29
  161. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/i18n/instance.ts +2 -0
  162. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/layout/default/index.vue +17 -32
  163. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/locales/en_US.json +4 -1
  164. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/locales/zh_CN.json +4 -1
  165. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/guard/index.ts +3 -3
  166. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/guard/permissionGuard.ts +7 -7
  167. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/index.ts +7 -7
  168. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/interface.ts +6 -6
  169. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/store/index.ts +6 -6
  170. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/store/modules/user.ts +9 -9
  171. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/styles/index.scss +3 -3
  172. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/types/antdv-next.d.ts +1 -0
  173. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/env.ts +1 -1
  174. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/core/index.ts +63 -45
  175. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/core/utils.ts +12 -6
  176. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/index.ts +1 -1
  177. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/echart/config.ts +687 -687
  178. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/echart/index.vue +7 -7
  179. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/tree-chart.vue +2 -7
  180. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/home/index.vue +13 -3
  181. package/template-vp-monorepo-react-nestjs/apps/web-vue/tsconfig.json +5 -17
  182. package/template-vp-monorepo-react-nestjs/apps/web-vue/uno.config.ts +5 -5
  183. package/template-vp-monorepo-react-nestjs/i18n.config.ts +22 -0
  184. package/template-vp-monorepo-react-nestjs/package.json +12 -1
  185. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/README.md +113 -0
  186. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/docs/excel-sync-plan.md +618 -0
  187. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/package.json +31 -0
  188. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/cli.ts +5 -0
  189. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/command.ts +512 -0
  190. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/config.ts +15 -0
  191. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/files.ts +67 -0
  192. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/index.ts +19 -0
  193. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/load-config.ts +228 -0
  194. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/report.ts +57 -0
  195. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/scanner.test.ts +81 -0
  196. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/scanner.ts +290 -0
  197. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/sync.ts +274 -0
  198. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/command.test.ts +209 -0
  199. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/config.test.ts +19 -0
  200. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/load-config.test.ts +194 -0
  201. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/report.test.ts +63 -0
  202. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/sync.test.ts +203 -0
  203. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/temporary-directory.ts +25 -0
  204. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/tsconfig.json +9 -0
  205. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/vite.config.ts +18 -0
  206. package/template-vp-monorepo-react-nestjs/packages/i18n/core/tsconfig.json +1 -12
  207. package/template-vp-monorepo-react-nestjs/packages/i18n/react/tsconfig.json +3 -10
  208. package/template-vp-monorepo-react-nestjs/packages/i18n/vue/tsconfig.json +3 -10
  209. package/template-vp-monorepo-react-nestjs/packages/shared/package.json +40 -0
  210. package/template-vp-monorepo-react-nestjs/packages/shared/src/index.ts +13 -0
  211. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/auth.ts +35 -0
  212. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/common.ts +11 -0
  213. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/index.ts +11 -0
  214. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/account.ts +5 -0
  215. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/index.ts +2 -0
  216. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/session-terminal.ts +11 -0
  217. package/template-vp-monorepo-react-nestjs/packages/shared/tsconfig.json +4 -0
  218. package/template-vp-monorepo-react-nestjs/packages/shared/vite.config.ts +17 -0
  219. package/template-vp-monorepo-react-nestjs/pnpm-workspace.yaml +6 -6
  220. package/template-vp-monorepo-react-nestjs/scripts/clean.mjs +48 -0
  221. package/template-vp-monorepo-react-nestjs/tsconfig.base.json +10 -0
  222. package/template-vp-monorepo-react-nestjs/tsconfig.bundler.json +12 -0
  223. package/template-vp-monorepo-react-nestjs/tsconfig.json +8 -6
  224. package/template-vp-monorepo-react-nestjs/tsconfig.lib.json +10 -0
  225. package/dist/index.mjs +0 -422
  226. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/decorator.ts +0 -2
  227. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/bypass.ts +0 -12
  228. package/template-vp-monorepo-react-nestjs/apps/server/src/common/interceptors/transform.ts +0 -34
  229. package/template-vp-monorepo-react-nestjs/apps/server/src/common/model/response.ts +0 -23
  230. package/template-vp-monorepo-react-nestjs/apps/server/src/config/auth.config.ts +0 -64
  231. package/template-vp-monorepo-react-nestjs/apps/web-vue/pnpm-workspace.yaml +0 -16
  232. /package/{template-vp-monorepo-react-nestjs/apps/server/src/config/security.config.ts → template-vp-monorepo-react-hono/.agents/auth.config.ts} +0 -0
@@ -0,0 +1,512 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { isAbsolute, relative, resolve, sep } from 'node:path'
3
+
4
+ import type { I18nProjectConfig } from './config.ts'
5
+ import { scanFiles, type ScanFilesResult } from './files.ts'
6
+ import { loadConfig } from './load-config.ts'
7
+ import { createSyncReport, type SyncFileReport, writeSyncReport } from './report.ts'
8
+ import { syncProject } from './sync.ts'
9
+
10
+ export type CliCommand = 'check' | 'sync'
11
+
12
+ export interface CliEnvironment {
13
+ cwd?: string
14
+ stdout?: (message: string) => void
15
+ stderr?: (message: string) => void
16
+ }
17
+
18
+ interface ParsedArguments {
19
+ command: CliCommand
20
+ configPath?: string
21
+ projects: string[]
22
+ clean: boolean
23
+ allowEmpty: boolean
24
+ dryRun: boolean
25
+ failOnStale: boolean
26
+ }
27
+
28
+ interface ProjectExecution {
29
+ name: string
30
+ scan: ScanFilesResult
31
+ catalogs: Record<string, string>
32
+ }
33
+
34
+ interface ProjectCatalogs {
35
+ name: string
36
+ catalogs: Record<string, string>
37
+ }
38
+
39
+ interface SummaryTotals {
40
+ added: number
41
+ unused: number
42
+ deleted: number
43
+ unchanged: number
44
+ }
45
+
46
+ export class CliUsageError extends Error {
47
+ override name = 'CliUsageError'
48
+ }
49
+
50
+ const helpText = `用法:
51
+ bubbles-i18n sync [options]
52
+ bubbles-i18n check [options]
53
+
54
+ 命令:
55
+ sync 把源码中缺失的静态 tr() key 添加到配置的语言包。
56
+ check 只检查、不修改语言包;存在缺失 key 时返回失败。
57
+
58
+ 选项:
59
+ --config <path> 使用指定配置文件,不再自动查找 i18n.config.ts。
60
+ --project <name> 只处理指定项目;可以多次传入。
61
+ --dry-run 预览同步结果,不修改语言包。
62
+ --clean 删除源码中已不存在的 key。
63
+ --allow-empty 配合 --clean,允许空扫描结果清空语言包。
64
+ --fail-on-stale check 时发现废弃 key 也返回失败。
65
+ -h, --help 显示帮助信息。
66
+
67
+ 安全保护:
68
+ 项目的 include 未匹配到任何源文件时,--clean 始终拒绝执行。
69
+ 已有翻译保持不变;新增项默认使用 key 作为翻译值。`
70
+
71
+ export async function runCli(
72
+ arguments_: readonly string[],
73
+ environment: CliEnvironment = {},
74
+ ): Promise<number> {
75
+ const writeOutput = environment.stdout ?? defaultStdout
76
+
77
+ if (arguments_.length === 0) {
78
+ writeOutput(helpText)
79
+ return 1
80
+ }
81
+
82
+ if (arguments_.includes('--help') || arguments_.includes('-h')) {
83
+ writeOutput(helpText)
84
+ return 0
85
+ }
86
+
87
+ const options = parseArguments(arguments_)
88
+ const cwd = resolve(environment.cwd ?? process.cwd())
89
+ const loaded = await loadConfig({ cwd, configPath: options.configPath })
90
+ const allProjectCatalogs = Object.entries(loaded.config.projects).map(
91
+ ([name, project]): ProjectCatalogs => ({
92
+ name,
93
+ catalogs: resolveCatalogs(loaded.rootDir, project.catalogs),
94
+ }),
95
+ )
96
+ const reportPath = loaded.config.report
97
+ ? resolveFromRoot(loaded.rootDir, loaded.config.report)
98
+ : undefined
99
+
100
+ validateOutputPaths(allProjectCatalogs, reportPath, loaded.configPath)
101
+ if (reportPath) {
102
+ await assertSafeReportTarget(reportPath)
103
+ }
104
+
105
+ const projectEntries = selectProjects(loaded.config.projects, options.projects)
106
+ const executions = await Promise.all(
107
+ projectEntries.map(async ([name, project]): Promise<ProjectExecution> => {
108
+ const scan = await scanFiles({
109
+ rootDir: loaded.rootDir,
110
+ include: project.include,
111
+ exclude: project.exclude,
112
+ callNames: loaded.config.callNames,
113
+ })
114
+
115
+ if (options.clean && scan.files.length === 0) {
116
+ throw new CliUsageError(
117
+ `Refusing --clean for project "${name}" because its include patterns matched no source files.`,
118
+ )
119
+ }
120
+
121
+ return {
122
+ name,
123
+ scan,
124
+ catalogs: resolveCatalogs(loaded.rootDir, project.catalogs),
125
+ }
126
+ }),
127
+ )
128
+
129
+ if (reportPath) {
130
+ assertReportIsNotSourceFile(executions, reportPath)
131
+ }
132
+
133
+ // Validate every selected catalog before any project is allowed to write.
134
+ const previews = await Promise.all(
135
+ executions.map((execution) =>
136
+ syncProject({
137
+ project: execution.name,
138
+ catalogs: execution.catalogs,
139
+ keys: execution.scan.keys,
140
+ clean: options.clean,
141
+ allowEmpty: options.allowEmpty,
142
+ dryRun: true,
143
+ }),
144
+ ),
145
+ )
146
+
147
+ let fileReports = previews.flat()
148
+ if (options.command === 'sync' && !options.dryRun) {
149
+ fileReports = []
150
+ for (const execution of executions) {
151
+ fileReports.push(
152
+ ...(await syncProject({
153
+ project: execution.name,
154
+ catalogs: execution.catalogs,
155
+ keys: execution.scan.keys,
156
+ clean: options.clean,
157
+ allowEmpty: options.allowEmpty,
158
+ })),
159
+ )
160
+ }
161
+ }
162
+
163
+ const portableReports = fileReports.map((file) => ({
164
+ ...file,
165
+ path: toPortablePath(loaded.rootDir, file.path),
166
+ }))
167
+ const report = createSyncReport({
168
+ command: options.command,
169
+ clean: options.clean,
170
+ dryRun: options.command === 'check' || options.dryRun,
171
+ files: portableReports,
172
+ })
173
+
174
+ if (reportPath) {
175
+ await writeSyncReport(report, reportPath)
176
+ }
177
+
178
+ printSummary({
179
+ command: options.command,
180
+ dryRun: options.dryRun,
181
+ configPath: toPortablePath(cwd, loaded.configPath),
182
+ reportPath: reportPath ? toPortablePath(loaded.rootDir, reportPath) : undefined,
183
+ executions,
184
+ files: portableReports,
185
+ writeOutput,
186
+ })
187
+
188
+ if (options.command === 'check') {
189
+ const totals = summarize(portableReports)
190
+ const failedForMissing = totals.added > 0
191
+ const failedForStale = options.failOnStale && totals.unused > 0
192
+
193
+ if (failedForMissing || failedForStale) {
194
+ writeOutput(
195
+ `Check failed: ${totals.added} missing catalog entries, ${totals.unused} unused entries.`,
196
+ )
197
+ return 1
198
+ }
199
+
200
+ writeOutput('Check passed.')
201
+ }
202
+
203
+ return 0
204
+ }
205
+
206
+ export async function main(
207
+ arguments_: readonly string[] = process.argv.slice(2),
208
+ environment: CliEnvironment = {},
209
+ ): Promise<number> {
210
+ const writeError = environment.stderr ?? defaultStderr
211
+
212
+ try {
213
+ return await runCli(arguments_, environment)
214
+ } catch (error) {
215
+ writeError(`Error: ${errorMessage(error)}`)
216
+ if (error instanceof CliUsageError) {
217
+ writeError('Run "bubbles-i18n --help" for usage.')
218
+ }
219
+ return 2
220
+ }
221
+ }
222
+
223
+ function parseArguments(arguments_: readonly string[]): ParsedArguments {
224
+ const command = arguments_[0]
225
+ if (command !== 'sync' && command !== 'check') {
226
+ throw new CliUsageError(`Unknown command "${command ?? ''}".`)
227
+ }
228
+
229
+ let configPath: string | undefined
230
+ const projects = new Set<string>()
231
+ let clean = false
232
+ let allowEmpty = false
233
+ let dryRun = false
234
+ let failOnStale = false
235
+
236
+ for (let index = 1; index < arguments_.length; index += 1) {
237
+ const argument = arguments_[index] ?? ''
238
+
239
+ if (argument === '--config') {
240
+ const [value, nextIndex] = readOptionValue(arguments_, index, '--config')
241
+ configPath = value
242
+ index = nextIndex
243
+ } else if (argument.startsWith('--config=')) {
244
+ configPath = readInlineOptionValue(argument, '--config')
245
+ } else if (argument === '--project') {
246
+ const [value, nextIndex] = readOptionValue(arguments_, index, '--project')
247
+ projects.add(value)
248
+ index = nextIndex
249
+ } else if (argument.startsWith('--project=')) {
250
+ projects.add(readInlineOptionValue(argument, '--project'))
251
+ } else if (argument === '--clean') {
252
+ clean = true
253
+ } else if (argument === '--allow-empty') {
254
+ allowEmpty = true
255
+ } else if (argument === '--dry-run') {
256
+ dryRun = true
257
+ } else if (argument === '--fail-on-stale') {
258
+ failOnStale = true
259
+ } else {
260
+ throw new CliUsageError(`Unknown option "${argument}".`)
261
+ }
262
+ }
263
+
264
+ if (command === 'check' && clean) {
265
+ throw new CliUsageError('--clean is only available with the sync command.')
266
+ }
267
+ if (command === 'check' && dryRun) {
268
+ throw new CliUsageError('check is already read-only; --dry-run is only available with sync.')
269
+ }
270
+ if (command === 'sync' && failOnStale) {
271
+ throw new CliUsageError('--fail-on-stale is only available with the check command.')
272
+ }
273
+ if (allowEmpty && !clean) {
274
+ throw new CliUsageError('--allow-empty requires --clean.')
275
+ }
276
+
277
+ return {
278
+ command,
279
+ configPath,
280
+ projects: [...projects],
281
+ clean,
282
+ allowEmpty,
283
+ dryRun,
284
+ failOnStale,
285
+ }
286
+ }
287
+
288
+ function readOptionValue(
289
+ arguments_: readonly string[],
290
+ index: number,
291
+ option: string,
292
+ ): [value: string, nextIndex: number] {
293
+ const value = arguments_[index + 1]
294
+ if (!value || value.startsWith('--')) {
295
+ throw new CliUsageError(`${option} requires a value.`)
296
+ }
297
+
298
+ return [value, index + 1]
299
+ }
300
+
301
+ function readInlineOptionValue(argument: string, option: string): string {
302
+ const value = argument.slice(option.length + 1)
303
+ if (value.length === 0) {
304
+ throw new CliUsageError(`${option} requires a value.`)
305
+ }
306
+
307
+ return value
308
+ }
309
+
310
+ function selectProjects(
311
+ projects: Readonly<Record<string, I18nProjectConfig>>,
312
+ selectedNames: readonly string[],
313
+ ): Array<[name: string, project: I18nProjectConfig]> {
314
+ if (selectedNames.length === 0) {
315
+ return Object.entries(projects)
316
+ }
317
+
318
+ return selectedNames.map((name) => {
319
+ if (!Object.prototype.hasOwnProperty.call(projects, name)) {
320
+ throw new CliUsageError(
321
+ `Unknown project "${name}". Available projects: ${Object.keys(projects).join(', ')}.`,
322
+ )
323
+ }
324
+
325
+ const project = projects[name]
326
+ if (!project) {
327
+ throw new CliUsageError(`Project "${name}" has an invalid configuration.`)
328
+ }
329
+
330
+ return [name, project]
331
+ })
332
+ }
333
+
334
+ function resolveCatalogs(
335
+ rootDir: string,
336
+ catalogs: Readonly<Record<string, string>>,
337
+ ): Record<string, string> {
338
+ return Object.fromEntries(
339
+ Object.entries(catalogs).map(([locale, path]) => [locale, resolveFromRoot(rootDir, path)]),
340
+ )
341
+ }
342
+
343
+ function resolveFromRoot(rootDir: string, path: string): string {
344
+ return isAbsolute(path) ? resolve(path) : resolve(rootDir, path)
345
+ }
346
+
347
+ function validateOutputPaths(
348
+ projects: readonly ProjectCatalogs[],
349
+ reportPath: string | undefined,
350
+ configPath: string,
351
+ ): void {
352
+ const owners = new Map<string, string>()
353
+
354
+ for (const project of projects) {
355
+ for (const [locale, path] of Object.entries(project.catalogs)) {
356
+ const normalizedPath = normalizeComparablePath(path)
357
+ const owner = `${project.name}/${locale}`
358
+ const existingOwner = owners.get(normalizedPath)
359
+ if (existingOwner) {
360
+ throw new CliUsageError(
361
+ `Catalog path "${path}" is configured by both ${existingOwner} and ${owner}.`,
362
+ )
363
+ }
364
+ owners.set(normalizedPath, owner)
365
+ }
366
+ }
367
+
368
+ if (reportPath && owners.has(normalizeComparablePath(reportPath))) {
369
+ throw new CliUsageError(`Report path must not overwrite a catalog: "${reportPath}".`)
370
+ }
371
+
372
+ if (reportPath && normalizeComparablePath(reportPath) === normalizeComparablePath(configPath)) {
373
+ throw new CliUsageError(`Report path must not overwrite the config file: "${reportPath}".`)
374
+ }
375
+ }
376
+
377
+ async function assertSafeReportTarget(reportPath: string): Promise<void> {
378
+ let source: string
379
+
380
+ try {
381
+ source = await readFile(reportPath, 'utf8')
382
+ } catch (error) {
383
+ if (isMissingPathError(error)) {
384
+ return
385
+ }
386
+ throw error
387
+ }
388
+
389
+ let value: unknown
390
+ try {
391
+ value = JSON.parse(source)
392
+ } catch {
393
+ throw new CliUsageError(`Refusing to overwrite existing non-report file at "${reportPath}".`)
394
+ }
395
+
396
+ if (!isSyncReportLike(value)) {
397
+ throw new CliUsageError(`Refusing to overwrite existing non-report file at "${reportPath}".`)
398
+ }
399
+ }
400
+
401
+ function assertReportIsNotSourceFile(
402
+ executions: readonly ProjectExecution[],
403
+ reportPath: string,
404
+ ): void {
405
+ const normalizedReportPath = normalizeComparablePath(reportPath)
406
+
407
+ for (const execution of executions) {
408
+ if (
409
+ execution.scan.files.some(
410
+ (sourcePath) => normalizeComparablePath(sourcePath) === normalizedReportPath,
411
+ )
412
+ ) {
413
+ throw new CliUsageError(
414
+ `Report path must not overwrite a scanned source file: "${reportPath}".`,
415
+ )
416
+ }
417
+ }
418
+ }
419
+
420
+ function normalizeComparablePath(path: string): string {
421
+ const normalized = resolve(path)
422
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized
423
+ }
424
+
425
+ function toPortablePath(rootDir: string, path: string): string {
426
+ const relativePath = relative(rootDir, path)
427
+ const displayPath = relativePath === '' ? '.' : relativePath
428
+ return displayPath.split(sep).join('/')
429
+ }
430
+
431
+ function summarize(files: readonly SyncFileReport[]): SummaryTotals {
432
+ return files.reduce<SummaryTotals>(
433
+ (totals, file) => ({
434
+ added: totals.added + file.added.length,
435
+ unused: totals.unused + file.unused.length,
436
+ deleted: totals.deleted + file.deleted.length,
437
+ unchanged: totals.unchanged + file.unchangedCount,
438
+ }),
439
+ { added: 0, unused: 0, deleted: 0, unchanged: 0 },
440
+ )
441
+ }
442
+
443
+ function printSummary(options: {
444
+ command: CliCommand
445
+ dryRun: boolean
446
+ configPath: string
447
+ reportPath?: string
448
+ executions: readonly ProjectExecution[]
449
+ files: readonly SyncFileReport[]
450
+ writeOutput: (message: string) => void
451
+ }): void {
452
+ const action = options.command === 'check' ? 'check' : options.dryRun ? 'sync dry-run' : 'sync'
453
+ options.writeOutput(`bubbles-i18n ${action}`)
454
+ options.writeOutput(`Config: ${options.configPath}`)
455
+
456
+ for (const execution of options.executions) {
457
+ options.writeOutput(
458
+ `Project ${execution.name}: ${execution.scan.files.length} source files, ${execution.scan.keys.size} keys.`,
459
+ )
460
+ if (execution.scan.skippedBinaryFiles.length > 0) {
461
+ options.writeOutput(` Skipped binary files: ${execution.scan.skippedBinaryFiles.length}.`)
462
+ }
463
+
464
+ for (const file of options.files.filter((candidate) => candidate.project === execution.name)) {
465
+ options.writeOutput(
466
+ ` ${file.locale} ${file.path}: added ${file.added.length}, unused ${file.unused.length}, deleted ${file.deleted.length}, unchanged ${file.unchangedCount}.`,
467
+ )
468
+ }
469
+ }
470
+
471
+ const totals = summarize(options.files)
472
+ options.writeOutput(
473
+ `Total: added ${totals.added}, unused ${totals.unused}, deleted ${totals.deleted}, unchanged ${totals.unchanged}.`,
474
+ )
475
+ if (options.reportPath) {
476
+ options.writeOutput(`Report: ${options.reportPath}`)
477
+ }
478
+ }
479
+
480
+ function defaultStdout(message: string): void {
481
+ process.stdout.write(`${message}\n`)
482
+ }
483
+
484
+ function defaultStderr(message: string): void {
485
+ process.stderr.write(`${message}\n`)
486
+ }
487
+
488
+ function errorMessage(error: unknown): string {
489
+ if (!(error instanceof Error)) {
490
+ return String(error)
491
+ }
492
+
493
+ const cause = (error as Error & { cause?: unknown }).cause
494
+ return cause === undefined ? error.message : `${error.message}: ${errorMessage(cause)}`
495
+ }
496
+
497
+ function isSyncReportLike(value: unknown): boolean {
498
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
499
+ return false
500
+ }
501
+
502
+ const report = value as Record<string, unknown>
503
+ return (report.command === 'sync' || report.command === 'check') && Array.isArray(report.files)
504
+ }
505
+
506
+ function isMissingPathError(error: unknown): boolean {
507
+ return (
508
+ error instanceof Error &&
509
+ 'code' in error &&
510
+ (error.code === 'ENOENT' || error.code === 'ENOTDIR')
511
+ )
512
+ }
@@ -0,0 +1,15 @@
1
+ export interface I18nProjectConfig {
2
+ include: readonly string[]
3
+ exclude?: readonly string[]
4
+ catalogs: Readonly<Record<string, string>>
5
+ }
6
+
7
+ export interface I18nConfig {
8
+ callNames?: readonly string[]
9
+ projects: Readonly<Record<string, I18nProjectConfig>>
10
+ report?: string
11
+ }
12
+
13
+ export function defineConfig<const Config extends I18nConfig>(config: Config): Config {
14
+ return config
15
+ }
@@ -0,0 +1,67 @@
1
+ import { readFile } from 'node:fs/promises'
2
+
3
+ import fg from 'fast-glob'
4
+
5
+ import { scanSource, type MessageOccurrence } from './scanner.ts'
6
+
7
+ export interface LocatedMessageOccurrence extends MessageOccurrence {
8
+ file: string
9
+ }
10
+
11
+ export interface ScanFilesOptions {
12
+ rootDir: string
13
+ include: readonly string[]
14
+ exclude?: readonly string[]
15
+ callNames?: readonly string[]
16
+ }
17
+
18
+ export interface ScanFilesResult {
19
+ files: string[]
20
+ keys: Set<string>
21
+ occurrences: LocatedMessageOccurrence[]
22
+ skippedBinaryFiles: string[]
23
+ }
24
+
25
+ const defaultExcludes = [
26
+ '**/.git/**',
27
+ '**/.pnpm-store/**',
28
+ '**/.vite/**',
29
+ '**/build/**',
30
+ '**/coverage/**',
31
+ '**/dist/**',
32
+ '**/node_modules/**',
33
+ ]
34
+
35
+ export async function scanFiles(options: ScanFilesOptions): Promise<ScanFilesResult> {
36
+ const files = await fg([...options.include], {
37
+ absolute: true,
38
+ cwd: options.rootDir,
39
+ dot: true,
40
+ followSymbolicLinks: false,
41
+ ignore: [...defaultExcludes, ...(options.exclude ?? [])],
42
+ onlyFiles: true,
43
+ unique: true,
44
+ })
45
+
46
+ files.sort((left, right) => left.localeCompare(right))
47
+
48
+ const keys = new Set<string>()
49
+ const occurrences: LocatedMessageOccurrence[] = []
50
+ const skippedBinaryFiles: string[] = []
51
+
52
+ for (const file of files) {
53
+ const source = await readFile(file, 'utf8')
54
+ if (source.includes('\0')) {
55
+ skippedBinaryFiles.push(file)
56
+ continue
57
+ }
58
+
59
+ const normalizedSource = source.charCodeAt(0) === 0xfe_ff ? source.slice(1) : source
60
+ for (const occurrence of scanSource(normalizedSource, { callNames: options.callNames })) {
61
+ keys.add(occurrence.key)
62
+ occurrences.push({ ...occurrence, file })
63
+ }
64
+ }
65
+
66
+ return { files, keys, occurrences, skippedBinaryFiles }
67
+ }
@@ -0,0 +1,19 @@
1
+ export { CliUsageError, main, runCli } from './command.ts'
2
+ export type { CliCommand, CliEnvironment } from './command.ts'
3
+ export { defineConfig } from './config.ts'
4
+ export type { I18nConfig, I18nProjectConfig } from './config.ts'
5
+ export { scanFiles } from './files.ts'
6
+ export type { LocatedMessageOccurrence, ScanFilesOptions, ScanFilesResult } from './files.ts'
7
+ export {
8
+ ConfigLoadError,
9
+ ConfigNotFoundError,
10
+ ConfigValidationError,
11
+ loadConfig,
12
+ } from './load-config.ts'
13
+ export type { LoadConfigOptions, LoadedI18nConfig } from './load-config.ts'
14
+ export { createSyncReport, writeSyncReport } from './report.ts'
15
+ export type { CreateSyncReportOptions, SyncFileReport, SyncReport } from './report.ts'
16
+ export { scanSource } from './scanner.ts'
17
+ export type { MessageOccurrence, ScanOptions } from './scanner.ts'
18
+ export { CatalogValidationError, EmptyScanCleanError, syncCatalog, syncProject } from './sync.ts'
19
+ export type { SyncCatalogOptions, SyncProjectOptions } from './sync.ts'