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,228 @@
1
+ import { stat } from 'node:fs/promises'
2
+ import { dirname, isAbsolute, resolve } from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
4
+
5
+ import type { I18nConfig } from './config.ts'
6
+
7
+ export const CONFIG_FILE_NAMES = [
8
+ 'i18n.config.ts',
9
+ 'i18n.config.mts',
10
+ 'i18n.config.js',
11
+ 'i18n.config.mjs',
12
+ 'i18n.config.cts',
13
+ 'i18n.config.cjs',
14
+ ] as const
15
+
16
+ export interface LoadConfigOptions {
17
+ readonly cwd?: string
18
+ readonly configPath?: string
19
+ }
20
+
21
+ export interface LoadedI18nConfig {
22
+ readonly configPath: string
23
+ readonly rootDir: string
24
+ readonly config: I18nConfig
25
+ }
26
+
27
+ export class ConfigNotFoundError extends Error {
28
+ override readonly name = 'ConfigNotFoundError'
29
+ }
30
+
31
+ export class ConfigLoadError extends Error {
32
+ override readonly name = 'ConfigLoadError'
33
+ readonly cause: unknown
34
+
35
+ constructor(message: string, cause?: unknown) {
36
+ super(message)
37
+ this.cause = cause
38
+ }
39
+ }
40
+
41
+ export class ConfigValidationError extends Error {
42
+ override readonly name = 'ConfigValidationError'
43
+ }
44
+
45
+ export async function loadConfig(options: LoadConfigOptions = {}): Promise<LoadedI18nConfig> {
46
+ const cwd = resolve(options.cwd ?? process.cwd())
47
+ const configPath =
48
+ options.configPath === undefined
49
+ ? await findConfigPath(cwd)
50
+ : await resolveExplicitConfigPath(options.configPath, cwd)
51
+
52
+ let importedConfig: unknown
53
+
54
+ try {
55
+ const module = (await import(pathToFileURL(configPath).href)) as Record<string, unknown>
56
+
57
+ if (!Object.prototype.hasOwnProperty.call(module, 'default')) {
58
+ throw new ConfigValidationError(
59
+ `Invalid i18n config at "${configPath}": the module must have a default export`,
60
+ )
61
+ }
62
+
63
+ importedConfig = module.default
64
+ } catch (error) {
65
+ if (error instanceof ConfigValidationError) {
66
+ throw error
67
+ }
68
+
69
+ throw new ConfigLoadError(`Failed to load i18n config at "${configPath}"`, error)
70
+ }
71
+
72
+ return {
73
+ configPath,
74
+ rootDir: dirname(configPath),
75
+ config: validateConfig(importedConfig, configPath),
76
+ }
77
+ }
78
+
79
+ export function validateConfig(value: unknown, configPath = '<config>'): I18nConfig {
80
+ const prefix = `Invalid i18n config at "${configPath}"`
81
+
82
+ if (!isRecord(value)) {
83
+ throw new ConfigValidationError(`${prefix}: the default export must be an object`)
84
+ }
85
+
86
+ const projects = value.projects
87
+ if (!isRecord(projects) || Object.keys(projects).length === 0) {
88
+ throw new ConfigValidationError(`${prefix}: projects must be a non-empty object`)
89
+ }
90
+
91
+ for (const [projectName, project] of Object.entries(projects)) {
92
+ if (projectName.trim() === '') {
93
+ throw new ConfigValidationError(`${prefix}: project names must be non-empty strings`)
94
+ }
95
+
96
+ const projectPath = `projects[${JSON.stringify(projectName)}]`
97
+ if (!isRecord(project)) {
98
+ throw new ConfigValidationError(`${prefix}: ${projectPath} must be an object`)
99
+ }
100
+
101
+ validateStringArray(project.include, `${projectPath}.include`, prefix, { nonEmpty: true })
102
+
103
+ if (project.exclude !== undefined) {
104
+ validateStringArray(project.exclude, `${projectPath}.exclude`, prefix)
105
+ }
106
+
107
+ const catalogs = project.catalogs
108
+ if (!isRecord(catalogs) || Object.keys(catalogs).length === 0) {
109
+ throw new ConfigValidationError(
110
+ `${prefix}: ${projectPath}.catalogs must be a non-empty object`,
111
+ )
112
+ }
113
+
114
+ for (const [locale, catalogPath] of Object.entries(catalogs)) {
115
+ if (locale.trim() === '') {
116
+ throw new ConfigValidationError(
117
+ `${prefix}: ${projectPath}.catalogs locale names must be non-empty strings`,
118
+ )
119
+ }
120
+
121
+ if (typeof catalogPath !== 'string' || catalogPath.trim() === '') {
122
+ throw new ConfigValidationError(
123
+ `${prefix}: ${projectPath}.catalogs[${JSON.stringify(locale)}] must be a non-empty string`,
124
+ )
125
+ }
126
+ }
127
+ }
128
+
129
+ if (value.callNames !== undefined) {
130
+ validateStringArray(value.callNames, 'callNames', prefix, { nonEmpty: true })
131
+ }
132
+
133
+ if (
134
+ value.report !== undefined &&
135
+ (typeof value.report !== 'string' || value.report.trim() === '')
136
+ ) {
137
+ throw new ConfigValidationError(`${prefix}: report must be a non-empty string`)
138
+ }
139
+
140
+ return value as unknown as I18nConfig
141
+ }
142
+
143
+ async function findConfigPath(cwd: string): Promise<string> {
144
+ let directory = cwd
145
+
146
+ while (true) {
147
+ for (const fileName of CONFIG_FILE_NAMES) {
148
+ const candidate = resolve(directory, fileName)
149
+ if (await isFile(candidate)) {
150
+ return candidate
151
+ }
152
+ }
153
+
154
+ const parent = dirname(directory)
155
+ if (parent === directory) {
156
+ break
157
+ }
158
+ directory = parent
159
+ }
160
+
161
+ throw new ConfigNotFoundError(
162
+ `Could not find an i18n config from "${cwd}" upward. Expected one of: ${CONFIG_FILE_NAMES.join(', ')}`,
163
+ )
164
+ }
165
+
166
+ async function resolveExplicitConfigPath(configPath: string, cwd: string): Promise<string> {
167
+ if (configPath.trim() === '') {
168
+ throw new ConfigNotFoundError('The explicit i18n config path must be a non-empty string')
169
+ }
170
+
171
+ const resolvedPath = isAbsolute(configPath) ? resolve(configPath) : resolve(cwd, configPath)
172
+ if (!(await isFile(resolvedPath))) {
173
+ throw new ConfigNotFoundError(
174
+ `Could not find the explicit i18n config at "${resolvedPath}" (resolved from "${cwd}")`,
175
+ )
176
+ }
177
+
178
+ return resolvedPath
179
+ }
180
+
181
+ function validateStringArray(
182
+ value: unknown,
183
+ path: string,
184
+ prefix: string,
185
+ options: { readonly nonEmpty?: boolean } = {},
186
+ ): asserts value is string[] {
187
+ if (!Array.isArray(value)) {
188
+ throw new ConfigValidationError(`${prefix}: ${path} must be an array of non-empty strings`)
189
+ }
190
+
191
+ if (options.nonEmpty === true && value.length === 0) {
192
+ throw new ConfigValidationError(`${prefix}: ${path} must be a non-empty array of strings`)
193
+ }
194
+
195
+ for (const [index, item] of value.entries()) {
196
+ if (typeof item !== 'string' || item.trim() === '') {
197
+ throw new ConfigValidationError(`${prefix}: ${path}[${index}] must be a non-empty string`)
198
+ }
199
+ }
200
+ }
201
+
202
+ function isRecord(value: unknown): value is Record<string, unknown> {
203
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
204
+ return false
205
+ }
206
+
207
+ const prototype = Object.getPrototypeOf(value) as unknown
208
+ return prototype === Object.prototype || prototype === null
209
+ }
210
+
211
+ async function isFile(path: string): Promise<boolean> {
212
+ try {
213
+ return (await stat(path)).isFile()
214
+ } catch (error) {
215
+ if (isMissingPathError(error)) {
216
+ return false
217
+ }
218
+ throw error
219
+ }
220
+ }
221
+
222
+ function isMissingPathError(error: unknown): boolean {
223
+ return (
224
+ error instanceof Error &&
225
+ 'code' in error &&
226
+ (error.code === 'ENOENT' || error.code === 'ENOTDIR')
227
+ )
228
+ }
@@ -0,0 +1,57 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { mkdir, rename, unlink, writeFile } from 'node:fs/promises'
3
+ import { basename, dirname, join } from 'node:path'
4
+
5
+ export interface SyncFileReport {
6
+ project: string
7
+ path: string
8
+ locale: string
9
+ added: string[]
10
+ unused: string[]
11
+ deleted: string[]
12
+ unchangedCount: number
13
+ }
14
+
15
+ export interface SyncReport {
16
+ command: 'sync' | 'check'
17
+ clean: boolean
18
+ dryRun: boolean
19
+ files: SyncFileReport[]
20
+ }
21
+
22
+ export interface CreateSyncReportOptions {
23
+ command?: SyncReport['command']
24
+ clean?: boolean
25
+ dryRun?: boolean
26
+ files?: readonly SyncFileReport[]
27
+ }
28
+
29
+ export function createSyncReport(options: CreateSyncReportOptions = {}): SyncReport {
30
+ return {
31
+ command: options.command ?? 'sync',
32
+ clean: options.clean ?? false,
33
+ dryRun: options.dryRun ?? false,
34
+ files: (options.files ?? []).map((file) => ({
35
+ ...file,
36
+ added: [...file.added],
37
+ unused: [...file.unused],
38
+ deleted: [...file.deleted],
39
+ })),
40
+ }
41
+ }
42
+
43
+ export async function writeSyncReport(report: SyncReport, path: string): Promise<void> {
44
+ const content = `${JSON.stringify(report, undefined, 2)}\n`
45
+ const directory = dirname(path)
46
+ const temporaryPath = join(directory, `.${basename(path)}.${process.pid}.${randomUUID()}.tmp`)
47
+
48
+ await mkdir(directory, { recursive: true })
49
+
50
+ try {
51
+ await writeFile(temporaryPath, content, 'utf8')
52
+ await rename(temporaryPath, path)
53
+ } catch (error) {
54
+ await unlink(temporaryPath).catch(() => undefined)
55
+ throw error
56
+ }
57
+ }
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from 'vite-plus/test'
2
+
3
+ import { scanSource } from './scanner.ts'
4
+
5
+ describe('scanSource', () => {
6
+ it('extracts Chinese text and all three static quote styles', () => {
7
+ const source = ["tr('保存')", 'tr("Open")', 'tr(`用户`)'].join('\n')
8
+
9
+ expect(scanSource(source)).toEqual([
10
+ { key: '保存', index: 0, line: 1, column: 1 },
11
+ { key: 'Open', index: 9, line: 2, column: 1 },
12
+ { key: '用户', index: 20, line: 3, column: 1 },
13
+ ])
14
+ })
15
+
16
+ it('allows whitespace and newlines around the first argument', () => {
17
+ const source = ['const message = tr', ' (', " '你好 {name}',", ' { name },', ' )'].join(
18
+ '\n',
19
+ )
20
+
21
+ expect(scanSource(source)).toEqual([{ key: '你好 {name}', index: 16, line: 1, column: 17 }])
22
+ })
23
+
24
+ it('decodes common escapes without evaluating source code', () => {
25
+ const source = String.raw`tr('It\'s\n中\t\x41\u6587\u{1F600}')`
26
+
27
+ expect(scanSource(source).map(({ key }) => key)).toEqual(["It's\n中\tA文😀"])
28
+ })
29
+
30
+ it('allows escaped interpolation syntax in a static template', () => {
31
+ const source = 'tr(`price: \\${value}`)'
32
+
33
+ expect(scanSource(source).map(({ key }) => key)).toEqual(['price: ${value}'])
34
+ })
35
+
36
+ it('ignores dynamic arguments, concatenation, and template interpolation', () => {
37
+ const source = [
38
+ 'tr(key)',
39
+ 'tr(getKey())',
40
+ "tr('a' + value)",
41
+ 'tr(`hello ${name}`)',
42
+ "tr('kept')",
43
+ ].join('\n')
44
+
45
+ expect(scanSource(source).map(({ key }) => key)).toEqual(['kept'])
46
+ })
47
+
48
+ it('requires an identifier boundary around the call name', () => {
49
+ const source = [
50
+ "notr('prefix')",
51
+ "trailingtr('suffix')",
52
+ "tr2('number')",
53
+ "_tr('underscore')",
54
+ "obj.tr('method')",
55
+ "tr('direct')",
56
+ ].join('\n')
57
+
58
+ expect(scanSource(source).map(({ key }) => key)).toEqual(['method', 'direct'])
59
+ })
60
+
61
+ it('supports custom call names', () => {
62
+ const source = ["t('short')", "$t('dollar')", "tr('default')"].join('\n')
63
+
64
+ expect(scanSource(source, { callNames: ['t', '$t'] }).map(({ key }) => key)).toEqual([
65
+ 'short',
66
+ 'dollar',
67
+ ])
68
+ })
69
+
70
+ it('returns every occurrence and leaves deduplication to the caller', () => {
71
+ const source = "tr('same')\ntr('same')"
72
+
73
+ expect(scanSource(source).map(({ key }) => key)).toEqual(['same', 'same'])
74
+ })
75
+
76
+ it('rejects a static string followed by an unexpected token', () => {
77
+ const source = ["tr('plus' + value)", "tr('semicolon'; value)"].join('\n')
78
+
79
+ expect(scanSource(source)).toEqual([])
80
+ })
81
+ })
@@ -0,0 +1,290 @@
1
+ export interface MessageOccurrence {
2
+ key: string
3
+ index: number
4
+ line: number
5
+ column: number
6
+ }
7
+
8
+ export interface ScanOptions {
9
+ callNames?: readonly string[]
10
+ }
11
+
12
+ interface ParsedString {
13
+ value: string
14
+ endIndex: number
15
+ }
16
+
17
+ interface ParsedEscape {
18
+ value: string
19
+ nextIndex: number
20
+ }
21
+
22
+ const identifierContinuePattern = /[\p{ID_Continue}_$]/u
23
+ const whitespacePattern = /\s/u
24
+ const hexPattern = /^[\dA-Fa-f]+$/
25
+
26
+ export function scanSource(source: string, options: ScanOptions = {}): MessageOccurrence[] {
27
+ const callNames = normalizeCallNames(options.callNames)
28
+ const occurrences: MessageOccurrence[] = []
29
+
30
+ if (callNames.length === 0) {
31
+ return occurrences
32
+ }
33
+
34
+ const lineStarts = collectLineStarts(source)
35
+
36
+ for (let index = 0; index < source.length; index += 1) {
37
+ for (const callName of callNames) {
38
+ if (!source.startsWith(callName, index)) {
39
+ continue
40
+ }
41
+
42
+ if (!hasIdentifierBoundaries(source, index, callName)) {
43
+ continue
44
+ }
45
+
46
+ let cursor = skipWhitespace(source, index + callName.length)
47
+ if (source[cursor] !== '(') {
48
+ continue
49
+ }
50
+
51
+ cursor = skipWhitespace(source, cursor + 1)
52
+ const quote = source[cursor]
53
+ if (quote !== "'" && quote !== '"' && quote !== '`') {
54
+ continue
55
+ }
56
+
57
+ const parsed = parseString(source, cursor, quote)
58
+ if (!parsed) {
59
+ continue
60
+ }
61
+
62
+ const nextTokenIndex = skipWhitespace(source, parsed.endIndex)
63
+ const nextToken = source[nextTokenIndex]
64
+ if (nextToken !== ',' && nextToken !== ')') {
65
+ continue
66
+ }
67
+
68
+ const position = locatePosition(lineStarts, index)
69
+ occurrences.push({
70
+ key: parsed.value,
71
+ index,
72
+ line: position.line,
73
+ column: position.column,
74
+ })
75
+
76
+ index += callName.length - 1
77
+ break
78
+ }
79
+ }
80
+
81
+ return occurrences
82
+ }
83
+
84
+ function normalizeCallNames(callNames: readonly string[] | undefined): string[] {
85
+ const names = callNames ?? ['tr']
86
+ return [...new Set(names.filter((name) => name.length > 0))].sort(
87
+ (left, right) => right.length - left.length,
88
+ )
89
+ }
90
+
91
+ function hasIdentifierBoundaries(source: string, index: number, callName: string): boolean {
92
+ const firstCharacter = callName[0]
93
+ const lastCharacter = callName[callName.length - 1]
94
+
95
+ if (isIdentifierContinue(firstCharacter) && isIdentifierContinue(source[index - 1])) {
96
+ return false
97
+ }
98
+
99
+ return !(
100
+ isIdentifierContinue(lastCharacter) && isIdentifierContinue(source[index + callName.length])
101
+ )
102
+ }
103
+
104
+ function isIdentifierContinue(character: string | undefined): boolean {
105
+ return (
106
+ character !== undefined &&
107
+ (identifierContinuePattern.test(character) ||
108
+ character === '\u{200C}' ||
109
+ character === '\u{200D}')
110
+ )
111
+ }
112
+
113
+ function skipWhitespace(source: string, startIndex: number): number {
114
+ let index = startIndex
115
+ while (index < source.length && whitespacePattern.test(source[index] ?? '')) {
116
+ index += 1
117
+ }
118
+ return index
119
+ }
120
+
121
+ function parseString(
122
+ source: string,
123
+ startIndex: number,
124
+ quote: "'" | '"' | '`',
125
+ ): ParsedString | undefined {
126
+ let value = ''
127
+
128
+ for (let index = startIndex + 1; index < source.length; ) {
129
+ const character = source[index]
130
+
131
+ if (character === quote) {
132
+ return { value, endIndex: index + 1 }
133
+ }
134
+
135
+ if (character === '\\') {
136
+ const escape = parseEscape(source, index)
137
+ if (!escape) {
138
+ return undefined
139
+ }
140
+ value += escape.value
141
+ index = escape.nextIndex
142
+ continue
143
+ }
144
+
145
+ if (quote === '`' && character === '$' && source[index + 1] === '{') {
146
+ return undefined
147
+ }
148
+
149
+ if (quote !== '`' && (character === '\n' || character === '\r')) {
150
+ return undefined
151
+ }
152
+
153
+ value += character
154
+ index += 1
155
+ }
156
+
157
+ return undefined
158
+ }
159
+
160
+ function parseEscape(source: string, slashIndex: number): ParsedEscape | undefined {
161
+ const escaped = source[slashIndex + 1]
162
+ if (escaped === undefined) {
163
+ return undefined
164
+ }
165
+
166
+ const simpleEscapes: Readonly<Record<string, string>> = {
167
+ "'": "'",
168
+ '"': '"',
169
+ '`': '`',
170
+ '\\': '\\',
171
+ $: '$',
172
+ b: '\b',
173
+ f: '\f',
174
+ n: '\n',
175
+ r: '\r',
176
+ t: '\t',
177
+ v: '\v',
178
+ 0: '\0',
179
+ }
180
+
181
+ if (escaped in simpleEscapes) {
182
+ return {
183
+ value: simpleEscapes[escaped] ?? '',
184
+ nextIndex: slashIndex + 2,
185
+ }
186
+ }
187
+
188
+ if (escaped === '\n') {
189
+ return { value: '', nextIndex: slashIndex + 2 }
190
+ }
191
+
192
+ if (escaped === '\r') {
193
+ const nextIndex = source[slashIndex + 2] === '\n' ? slashIndex + 3 : slashIndex + 2
194
+ return { value: '', nextIndex }
195
+ }
196
+
197
+ if (escaped === 'x') {
198
+ return parseFixedHexEscape(source, slashIndex, 2, 2)
199
+ }
200
+
201
+ if (escaped === 'u') {
202
+ if (source[slashIndex + 2] === '{') {
203
+ return parseCodePointEscape(source, slashIndex)
204
+ }
205
+ return parseFixedHexEscape(source, slashIndex, 2, 4)
206
+ }
207
+
208
+ return { value: escaped, nextIndex: slashIndex + 2 }
209
+ }
210
+
211
+ function parseFixedHexEscape(
212
+ source: string,
213
+ slashIndex: number,
214
+ prefixLength: number,
215
+ digitCount: number,
216
+ ): ParsedEscape | undefined {
217
+ const digitsStart = slashIndex + prefixLength
218
+ const digits = source.slice(digitsStart, digitsStart + digitCount)
219
+ if (digits.length !== digitCount || !hexPattern.test(digits)) {
220
+ return undefined
221
+ }
222
+
223
+ return {
224
+ value: String.fromCodePoint(Number.parseInt(digits, 16)),
225
+ nextIndex: digitsStart + digitCount,
226
+ }
227
+ }
228
+
229
+ function parseCodePointEscape(source: string, slashIndex: number): ParsedEscape | undefined {
230
+ const digitsStart = slashIndex + 3
231
+ const closingBraceIndex = source.indexOf('}', digitsStart)
232
+ if (closingBraceIndex === -1) {
233
+ return undefined
234
+ }
235
+
236
+ const digits = source.slice(digitsStart, closingBraceIndex)
237
+ if (digits.length === 0 || digits.length > 6 || !hexPattern.test(digits)) {
238
+ return undefined
239
+ }
240
+
241
+ const codePoint = Number.parseInt(digits, 16)
242
+ if (codePoint > 0x10_ff_ff) {
243
+ return undefined
244
+ }
245
+
246
+ return {
247
+ value: String.fromCodePoint(codePoint),
248
+ nextIndex: closingBraceIndex + 1,
249
+ }
250
+ }
251
+
252
+ function collectLineStarts(source: string): number[] {
253
+ const starts = [0]
254
+
255
+ for (let index = 0; index < source.length; index += 1) {
256
+ if (source[index] === '\r') {
257
+ if (source[index + 1] === '\n') {
258
+ index += 1
259
+ }
260
+ starts.push(index + 1)
261
+ } else if (source[index] === '\n') {
262
+ starts.push(index + 1)
263
+ }
264
+ }
265
+
266
+ return starts
267
+ }
268
+
269
+ function locatePosition(
270
+ lineStarts: readonly number[],
271
+ index: number,
272
+ ): { line: number; column: number } {
273
+ let low = 0
274
+ let high = lineStarts.length - 1
275
+
276
+ while (low <= high) {
277
+ const middle = Math.floor((low + high) / 2)
278
+ const lineStart = lineStarts[middle] ?? 0
279
+
280
+ if (lineStart <= index) {
281
+ low = middle + 1
282
+ } else {
283
+ high = middle - 1
284
+ }
285
+ }
286
+
287
+ const lineIndex = Math.max(0, high)
288
+ const lineStart = lineStarts[lineIndex] ?? 0
289
+ return { line: lineIndex + 1, column: index - lineStart + 1 }
290
+ }