@starci/hfs 4.0.8 → 4.0.10

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 (703) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +81 -39
  4. package/emit/contracts.mjs +56 -9
  5. package/emit/db-types.mjs +169 -0
  6. package/emit/events.mjs +41 -0
  7. package/emit/operations-worker.mjs +1 -1
  8. package/emit/operations.mjs +2 -2
  9. package/emit/schema-worker.mjs +1 -1
  10. package/lint/bound-sys.cjs +50 -0
  11. package/lint/run.mjs +131 -24
  12. package/package.json +16 -10
  13. package/report/sonar.mjs +8 -8
  14. package/runtime/engine/admission.mjs +308 -0
  15. package/runtime/engine/canonical-json.mjs +10 -0
  16. package/runtime/engine/config.mjs +486 -0
  17. package/runtime/engine/db/blob.mjs +315 -0
  18. package/runtime/engine/db/ledger-paths.mjs +83 -0
  19. package/runtime/engine/db/ledger.mjs +1118 -0
  20. package/runtime/engine/db/machine-connection.mjs +135 -0
  21. package/runtime/engine/db/machine-schema.mjs +84 -0
  22. package/runtime/engine/db/machine.mjs +1367 -0
  23. package/runtime/engine/db/migrations/machine/0001-init.sql +924 -0
  24. package/runtime/engine/db/migrations/runtime/0001-init.sql +1108 -0
  25. package/runtime/engine/db/provider-reservations.mjs +101 -0
  26. package/runtime/engine/digest.mjs +16 -0
  27. package/runtime/engine/invalid-config.mjs +15 -0
  28. package/runtime/engine/orca-config.mjs +27 -0
  29. package/runtime/engine/refuse.mjs +11 -0
  30. package/runtime/engine/secrets.mjs +130 -0
  31. package/runtime/knowledge/hfs/canon-pins.yaml +162 -31
  32. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
  33. package/runtime/knowledge/hfs/rules.yaml +2059 -0
  34. package/runtime/knowledge/hfs/slots.yaml +912 -120
  35. package/runtime/knowledge/patterns/be/api.yaml +369 -0
  36. package/runtime/knowledge/patterns/be/cli.yaml +297 -0
  37. package/runtime/knowledge/patterns/be/domain.yaml +113 -0
  38. package/runtime/knowledge/patterns/be/event-bus.yaml +285 -0
  39. package/runtime/knowledge/patterns/be/jobs.yaml +261 -0
  40. package/runtime/knowledge/patterns/be/projections.yaml +178 -0
  41. package/runtime/knowledge/patterns/be/queues.yaml +236 -0
  42. package/runtime/knowledge/patterns/be/reactors.yaml +140 -0
  43. package/runtime/knowledge/patterns/be/realtime.yaml +222 -0
  44. package/runtime/knowledge/patterns/be/saga.yaml +248 -0
  45. package/runtime/knowledge/patterns/be/webhooks.yaml +221 -0
  46. package/runtime/knowledge/patterns/fe/folder.yaml +131 -25
  47. package/runtime/knowledge/sonar-gate.yaml +52 -18
  48. package/runtime/modules/kernel/failure-codes.yaml +611 -51
  49. package/runtime/modules/models/registry.yaml +553 -0
  50. package/runtime/modules/models/runtimes.yaml +695 -0
  51. package/runtime/modules/ops/_labels.yaml +53 -0
  52. package/runtime/scripts/api/fs/forbidden-root.mjs +51 -0
  53. package/runtime/scripts/api/fs/is-link-like.mjs +25 -0
  54. package/runtime/scripts/api/fs/lib.mjs +16 -2
  55. package/runtime/scripts/api/fs/safe-remove.mjs +12 -132
  56. package/runtime/scripts/api/git/config-get.mjs +10 -0
  57. package/runtime/scripts/api/git/diff.mjs +5 -0
  58. package/runtime/scripts/api/git/lib.mjs +18 -26
  59. package/runtime/scripts/api/git/log.mjs +5 -0
  60. package/runtime/scripts/api/git/ls-files.mjs +6 -0
  61. package/runtime/scripts/api/git/ls-tree.mjs +5 -0
  62. package/runtime/scripts/api/git/merge-base.mjs +9 -0
  63. package/runtime/scripts/api/git/remote.mjs +5 -0
  64. package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
  65. package/runtime/scripts/api/git/show.mjs +6 -0
  66. package/runtime/scripts/api/node/lib.mjs +14 -0
  67. package/runtime/scripts/api/node/spawn-node.mjs +6 -0
  68. package/runtime/scripts/api/process/lib.mjs +111 -0
  69. package/runtime/scripts/api/process/owned-process.mjs +78 -0
  70. package/runtime/scripts/api/process/resolve-real-tool.mjs +46 -0
  71. package/runtime/scripts/api/process/run-program.mjs +7 -0
  72. package/runtime/scripts/api/process/stop-owned-process.mjs +9 -0
  73. package/runtime/scripts/api/sops/decrypt.mjs +9 -12
  74. package/runtime/scripts/api/sops/lib.mjs +255 -0
  75. package/runtime/scripts/api/sops/seal.mjs +22 -0
  76. package/runtime/scripts/connectors/lib.mjs +488 -0
  77. package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
  78. package/runtime/scripts/hfs/architecture/backend.mjs +16 -23
  79. package/runtime/scripts/hfs/architecture/background-unowned.mjs +16 -34
  80. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +3 -1
  81. package/runtime/scripts/hfs/architecture/clones.mjs +4 -4
  82. package/runtime/scripts/hfs/architecture/config.mjs +9 -6
  83. package/runtime/scripts/hfs/architecture/connection-map.mjs +20 -8
  84. package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
  85. package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
  86. package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
  87. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
  88. package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
  89. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +4 -10
  90. package/runtime/scripts/hfs/architecture/contracts.mjs +33 -162
  91. package/runtime/scripts/hfs/architecture/dead-exports.mjs +58 -29
  92. package/runtime/scripts/hfs/architecture/doc-language.mjs +1 -1
  93. package/runtime/scripts/hfs/architecture/entrypoint.mjs +2 -1
  94. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -2
  95. package/runtime/scripts/hfs/architecture/feature-shape.mjs +5 -2
  96. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  97. package/runtime/scripts/hfs/architecture/frontend.mjs +17 -43
  98. package/runtime/scripts/hfs/architecture/hfs.mjs +24 -39
  99. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +1 -1
  100. package/runtime/scripts/hfs/architecture/index.mjs +17 -4
  101. package/runtime/scripts/hfs/architecture/machine-ast.mjs +44 -11
  102. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
  103. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +30 -6
  104. package/runtime/scripts/hfs/architecture/next-data.mjs +22 -63
  105. package/runtime/scripts/hfs/architecture/owners.mjs +35 -15
  106. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -1
  107. package/runtime/scripts/hfs/architecture/registration.mjs +23 -81
  108. package/runtime/scripts/hfs/architecture/required-files.mjs +20 -12
  109. package/runtime/scripts/hfs/architecture/schema-owner.mjs +17 -114
  110. package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
  111. package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
  112. package/runtime/scripts/hfs/architecture/source-names.mjs +27 -147
  113. package/runtime/scripts/hfs/architecture/sql-owner.mjs +59 -18
  114. package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
  115. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
  116. package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
  117. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
  118. package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
  119. package/runtime/scripts/hfs/architecture/surface.mjs +9 -3
  120. package/runtime/scripts/hfs/architecture/test-world-files.mjs +9 -19
  121. package/runtime/scripts/hfs/architecture/tiers.mjs +47 -5
  122. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -3
  123. package/runtime/scripts/hfs/architecture/typescript.mjs +12 -26
  124. package/runtime/scripts/hfs/architecture.mjs +4 -4
  125. package/runtime/scripts/hfs/check.mjs +75 -72
  126. package/runtime/scripts/hfs/coverage-scope.mjs +162 -0
  127. package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
  128. package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
  129. package/runtime/scripts/hfs/edition-slots.mjs +66 -0
  130. package/runtime/scripts/hfs/manifest-shape.mjs +109 -32
  131. package/runtime/scripts/hfs/param-names.mjs +18 -0
  132. package/runtime/scripts/hfs/path-findings.mjs +1 -1
  133. package/runtime/scripts/hfs/repo-identity.mjs +9 -6
  134. package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
  135. package/runtime/scripts/hfs/rules/app-root.mjs +51 -0
  136. package/runtime/scripts/hfs/rules/cli.mjs +37 -0
  137. package/runtime/scripts/hfs/rules/contract-compat.mjs +71 -0
  138. package/runtime/scripts/hfs/rules/contract.mjs +10 -9
  139. package/runtime/scripts/hfs/rules/database-config.mjs +102 -0
  140. package/runtime/scripts/hfs/rules/database-constants.mjs +13 -0
  141. package/runtime/scripts/hfs/rules/database-migrations.mjs +111 -0
  142. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +233 -0
  143. package/runtime/scripts/hfs/rules/database-sql.mjs +336 -0
  144. package/runtime/scripts/hfs/rules/database.mjs +68 -0
  145. package/runtime/scripts/hfs/rules/deps.mjs +1 -1
  146. package/runtime/scripts/hfs/rules/docker.mjs +259 -0
  147. package/runtime/scripts/hfs/rules/edition.mjs +111 -0
  148. package/runtime/scripts/hfs/rules/event-bus.mjs +153 -0
  149. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -1
  150. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +6 -5
  151. package/runtime/scripts/hfs/rules/frontend-tree.mjs +9 -9
  152. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -6
  153. package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
  154. package/runtime/scripts/hfs/rules/lint-suppression.mjs +1 -1
  155. package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
  156. package/runtime/scripts/hfs/rules/peer-integrations.mjs +3 -3
  157. package/runtime/scripts/hfs/rules/pipeline.mjs +11 -21
  158. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -2
  159. package/runtime/scripts/hfs/rules/read.mjs +1 -1
  160. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +2 -2
  161. package/runtime/scripts/hfs/rules/saga.mjs +170 -0
  162. package/runtime/scripts/hfs/rules/secrets.mjs +3 -3
  163. package/runtime/scripts/hfs/rules/services.mjs +220 -0
  164. package/runtime/scripts/hfs/rules/spec-placement.mjs +1 -1
  165. package/runtime/scripts/hfs/rules/stacks.mjs +3 -3
  166. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
  167. package/runtime/scripts/hfs/rules/test-topology.mjs +2 -2
  168. package/runtime/scripts/hfs/secret.mjs +209 -0
  169. package/runtime/scripts/hfs/slots.mjs +78 -108
  170. package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
  171. package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
  172. package/runtime/scripts/hfs/tree.mjs +5 -4
  173. package/runtime/scripts/hfs/typescript-programs.mjs +0 -5
  174. package/runtime/scripts/hfs/view.mjs +3 -2
  175. package/runtime/scripts/lib/clip.mjs +19 -0
  176. package/runtime/scripts/lib/display-names.mjs +259 -0
  177. package/runtime/scripts/lib/dockerfile.mjs +124 -0
  178. package/runtime/scripts/lib/env.mjs +10 -0
  179. package/runtime/scripts/lib/event-contract.mjs +103 -0
  180. package/runtime/scripts/lib/example-refs.mjs +158 -0
  181. package/runtime/scripts/lib/fs-kind.mjs +14 -4
  182. package/runtime/scripts/lib/git.mjs +36 -0
  183. package/runtime/scripts/lib/graphql-contract.mjs +1 -1
  184. package/runtime/scripts/lib/i18n.mjs +86 -0
  185. package/runtime/scripts/lib/json-schema.mjs +52 -0
  186. package/runtime/scripts/lib/json.mjs +13 -3
  187. package/runtime/scripts/lib/language.mjs +24 -5
  188. package/runtime/scripts/lib/list.mjs +20 -0
  189. package/runtime/scripts/lib/mutation-fence.mjs +15 -0
  190. package/runtime/scripts/lib/package-at.mjs +37 -0
  191. package/runtime/scripts/lib/path-key.mjs +57 -2
  192. package/runtime/scripts/lib/process-identity.mjs +6 -0
  193. package/runtime/scripts/lib/read-text.mjs +34 -0
  194. package/runtime/scripts/lib/read-yaml.mjs +18 -0
  195. package/runtime/scripts/lib/redact.mjs +161 -0
  196. package/runtime/scripts/lib/regex.mjs +4 -0
  197. package/runtime/scripts/lib/same-text.mjs +5 -0
  198. package/runtime/scripts/lib/secret-patterns.mjs +4 -3
  199. package/runtime/scripts/lib/sleep-sync.mjs +2 -1
  200. package/runtime/scripts/lib/sleep.mjs +7 -0
  201. package/runtime/scripts/lib/sops-envelope.mjs +112 -0
  202. package/runtime/scripts/lib/source-phrases.mjs +34 -0
  203. package/runtime/scripts/lib/sqlite.mjs +21 -0
  204. package/runtime/scripts/lib/stack-services.mjs +7 -31
  205. package/runtime/scripts/lib/ts-ast.mjs +35 -0
  206. package/runtime/scripts/lib/walk.mjs +4 -3
  207. package/scaffold/add-app.mjs +174 -0
  208. package/scaffold/add-cli-lite.mjs +257 -0
  209. package/scaffold/add-inbox-lite.mjs +314 -0
  210. package/scaffold/add-table.mjs +344 -0
  211. package/scaffold/add.mjs +299 -0
  212. package/scaffold/app.mjs +358 -69
  213. package/scaffold/edition-gate.mjs +25 -0
  214. package/scaffold/image.mjs +19 -0
  215. package/scaffold/lite-exports.mjs +186 -0
  216. package/scaffold/service.mjs +32 -26
  217. package/scaffold/supabase-ports.mjs +26 -0
  218. package/src/main.mjs +327 -0
  219. package/sync/cli.mjs +6 -9
  220. package/sync/format.mjs +1 -1
  221. package/sync/hygiene.mjs +10 -10
  222. package/sync/index.mjs +167 -104
  223. package/sync/managed.mjs +19 -11
  224. package/sync/sonar-key.mjs +5 -0
  225. package/templates/app/ci-workflows/github/workflows/ci.yml +14 -14
  226. package/templates/app/ci-workflows/github/workflows/images.yml +37 -0
  227. package/templates/app/ci-workflows/sonar-steps.yml +9 -0
  228. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +66 -0
  229. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +31 -0
  230. package/templates/app/ci-workflows-lite/github/workflows/images.yml +37 -0
  231. package/templates/app/codegen-lite/scripts/codegen.mjs +32 -0
  232. package/templates/app/docker-ignore/dockerignore +50 -0
  233. package/templates/app/gitignore-lite +35 -0
  234. package/templates/app/hooks/husky/pre-commit +3 -8
  235. package/templates/app/hooks/husky/pre-push +45 -6
  236. package/templates/app/package-scripts/core.json +8 -0
  237. package/templates/app/package-scripts/package.json +3 -10
  238. package/templates/app/package-scripts-lite/package.json +15 -0
  239. package/templates/app/quality-config/codecov.yml +15 -3
  240. package/templates/app/quality-config/sonar-base.properties +4 -0
  241. package/templates/app/quality-config/sonar-project.properties +1 -4
  242. package/templates/app/quality-config-lite/sonar-project.properties +5 -0
  243. package/templates/app/skeleton/.starcistacks/dev/seeds/primary-notes.sql +5 -0
  244. package/templates/app/skeleton/.starciwork/features/system-health/index.yaml +4 -0
  245. package/templates/app/skeleton/.starciwork/{features/index.yaml → index.yaml} +2 -2
  246. package/templates/app/skeleton/README.md +7 -5
  247. package/templates/app/skeleton-lite/.sops.yaml +3 -0
  248. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +5 -0
  249. package/templates/app/skeleton-lite/.starcistacks/dev/secrets/.gitkeep +1 -0
  250. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +55 -0
  251. package/templates/app/skeleton-lite/README.md +40 -0
  252. package/templates/app/skeleton-lite/supabase/config.toml +41 -0
  253. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +56 -0
  254. package/templates/app/skeleton-lite/supabase/seed.sql +0 -0
  255. package/templates/app/task-graph/turbo.json +23 -0
  256. package/templates/app/tool-config-lite/prettierignore +2 -0
  257. package/templates/app/tool-config-lite/prettierrc +1 -0
  258. package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +38 -0
  259. package/templates/app/upgrade-full/.starcistacks/dev/README.md +10 -0
  260. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +13 -0
  261. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +3 -0
  262. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +12 -0
  263. package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +7 -0
  264. package/templates/be/common/main-listen.ts.partial +14 -0
  265. package/templates/be/common/sql-ident.ts.partial +7 -0
  266. package/templates/be/common/sql-tag.ts.partial +5 -0
  267. package/templates/be/image/api/Dockerfile +38 -0
  268. package/templates/be/image/cli/Dockerfile +38 -0
  269. package/templates/be/image/worker/Dockerfile +36 -0
  270. package/templates/be/inbox/database-client.ts.tpl +50 -0
  271. package/templates/be/inbox/index.ts.tpl +2 -0
  272. package/templates/be/inbox/migration.sql.tpl +29 -0
  273. package/templates/be/inbox/module-definition.ts.tpl +6 -0
  274. package/templates/be/inbox/module.ts.tpl +14 -0
  275. package/templates/be/inbox/options.ts.tpl +2 -0
  276. package/templates/be/inbox/service.ts.tpl +33 -0
  277. package/templates/be/inbox/sql.ts.tpl +16 -0
  278. package/templates/be/inbox/webhook-door.ts.tpl +36 -0
  279. package/templates/be/patterns/api/application.command.http.ts.tpl +10 -0
  280. package/templates/be/patterns/api/application.command.ts.tpl +10 -0
  281. package/templates/be/patterns/api/application.contracts.ts.tpl +11 -0
  282. package/templates/be/patterns/api/application.handler.http.ts.tpl +22 -0
  283. package/templates/be/patterns/api/application.handler.ts.tpl +22 -0
  284. package/templates/be/patterns/api/feature.index.http.ts.tpl +1 -0
  285. package/templates/be/patterns/api/feature.index.ts.tpl +1 -0
  286. package/templates/be/patterns/api/feature.module.ts.tpl +6 -0
  287. package/templates/be/patterns/api/graphql.input.ts.tpl +13 -0
  288. package/templates/be/patterns/api/graphql.mapper.ts.tpl +9 -0
  289. package/templates/be/patterns/api/graphql.module.ts.tpl +7 -0
  290. package/templates/be/patterns/api/graphql.resolver.ts.tpl +27 -0
  291. package/templates/be/patterns/api/graphql.type.ts.tpl +9 -0
  292. package/templates/be/patterns/api/http.controller.ts.tpl +27 -0
  293. package/templates/be/patterns/api/http.mapper.ts.tpl +9 -0
  294. package/templates/be/patterns/api/http.module.ts.tpl +7 -0
  295. package/templates/be/patterns/api/http.request.ts.tpl +10 -0
  296. package/templates/be/patterns/api/http.response.ts.tpl +5 -0
  297. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +15 -0
  298. package/templates/be/patterns/cli/group.cli.ts.tpl +11 -0
  299. package/templates/be/patterns/cli/group.module.ts.tpl +7 -0
  300. package/templates/be/patterns/cli/sub.run.cli.spec.ts.tpl +17 -0
  301. package/templates/be/patterns/cli/sub.run.cli.ts.tpl +15 -0
  302. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +22 -0
  303. package/templates/be/patterns/event-bus/platform/event-bus.config.ts.tpl +19 -0
  304. package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +58 -0
  305. package/templates/be/patterns/event-bus/platform/event-bus.decorators.ts.tpl +46 -0
  306. package/templates/be/patterns/event-bus/platform/event-bus.log-events.ts.tpl +12 -0
  307. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +10 -0
  308. package/templates/be/patterns/event-bus/platform/event-bus.module.ts.tpl +52 -0
  309. package/templates/be/patterns/event-bus/platform/event-bus.options.ts.tpl +19 -0
  310. package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +28 -0
  311. package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +165 -0
  312. package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +93 -0
  313. package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +90 -0
  314. package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +58 -0
  315. package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +136 -0
  316. package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +56 -0
  317. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +272 -0
  318. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +128 -0
  319. package/templates/be/patterns/event-bus/platform/event-transport.port.ts.tpl +180 -0
  320. package/templates/be/patterns/event-bus/platform/event.policy.spec.ts.tpl +67 -0
  321. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +38 -0
  322. package/templates/be/patterns/event-bus/platform/index.ts.tpl +11 -0
  323. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.spec.ts.tpl +348 -0
  324. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +241 -0
  325. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +15 -0
  326. package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +8 -0
  327. package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +37 -0
  328. package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +11 -0
  329. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +12 -0
  330. package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +26 -0
  331. package/templates/be/patterns/jobs/job.index.ts.tpl +1 -0
  332. package/templates/be/patterns/jobs/job.module.ts.tpl +6 -0
  333. package/templates/be/patterns/jobs/job.processor.ts.tpl +20 -0
  334. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +20 -0
  335. package/templates/be/patterns/jobs/job.step.ts.tpl +19 -0
  336. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +31 -0
  337. package/templates/be/patterns/jobs/platform/fenced.processor.ts.tpl +13 -0
  338. package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +150 -0
  339. package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +100 -0
  340. package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +138 -0
  341. package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +69 -0
  342. package/templates/be/patterns/jobs/platform/jobs.config.ts.tpl +11 -0
  343. package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +62 -0
  344. package/templates/be/patterns/jobs/platform/jobs.decorators.ts.tpl +31 -0
  345. package/templates/be/patterns/jobs/platform/jobs.error.ts.tpl +22 -0
  346. package/templates/be/patterns/jobs/platform/jobs.index.ts.tpl +11 -0
  347. package/templates/be/patterns/jobs/platform/jobs.log-events.ts.tpl +9 -0
  348. package/templates/be/patterns/jobs/platform/jobs.messages.ts.tpl +15 -0
  349. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +10 -0
  350. package/templates/be/patterns/jobs/platform/jobs.module.ts.tpl +32 -0
  351. package/templates/be/patterns/jobs/platform/jobs.options.ts.tpl +11 -0
  352. package/templates/be/patterns/jobs/platform/jobs.port.ts.tpl +36 -0
  353. package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +8 -0
  354. package/templates/be/patterns/jobs/platform/persistence.job.entity.ts.tpl +53 -0
  355. package/templates/be/patterns/jobs/platform/persistence.jobs.rows.ts.tpl +13 -0
  356. package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +33 -0
  357. package/templates/be/patterns/jobs/platform/persistence.migration.ts.tpl +29 -0
  358. package/templates/be/patterns/outbox/platform/index.ts.tpl +2 -0
  359. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +96 -0
  360. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +78 -0
  361. package/templates/be/patterns/outbox/platform/outbox.log-events.ts.tpl +5 -0
  362. package/templates/be/patterns/projections/migration.ts.tpl +18 -0
  363. package/templates/be/patterns/projections/persistence.connection.ts.tpl +8 -0
  364. package/templates/be/patterns/projections/projection.entity.ts.tpl +9 -0
  365. package/templates/be/patterns/projections/projection.index.ts.tpl +3 -0
  366. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +7 -0
  367. package/templates/be/patterns/projections/projection.module.ts.tpl +18 -0
  368. package/templates/be/patterns/projections/projection.options.ts.tpl +2 -0
  369. package/templates/be/patterns/projections/projection.projection.spec.ts.tpl +55 -0
  370. package/templates/be/patterns/projections/projection.projection.ts.tpl +21 -0
  371. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +142 -0
  372. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +81 -0
  373. package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +8 -0
  374. package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +25 -0
  375. package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +25 -0
  376. package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +9 -0
  377. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +12 -0
  378. package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +48 -0
  379. package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +17 -0
  380. package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +128 -0
  381. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +48 -0
  382. package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +115 -0
  383. package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +91 -0
  384. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +45 -0
  385. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +15 -0
  386. package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +31 -0
  387. package/templates/be/patterns/queues/platform/queue.decorators.ts.tpl +45 -0
  388. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +7 -0
  389. package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +5 -0
  390. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +10 -0
  391. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +62 -0
  392. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +22 -0
  393. package/templates/be/patterns/queues/platform/queue.policy.spec.ts.tpl +19 -0
  394. package/templates/be/patterns/queues/platform/queue.policy.ts.tpl +11 -0
  395. package/templates/be/patterns/queues/platform/queue.port.ts.tpl +14 -0
  396. package/templates/be/patterns/queues/queue.index.ts.tpl +3 -0
  397. package/templates/be/patterns/queues/queue.module.ts.tpl +6 -0
  398. package/templates/be/patterns/queues/queue.queue.ts.tpl +23 -0
  399. package/templates/be/patterns/reactors/application.command.ts.tpl +10 -0
  400. package/templates/be/patterns/reactors/application.contracts.ts.tpl +12 -0
  401. package/templates/be/patterns/reactors/application.handler.ts.tpl +22 -0
  402. package/templates/be/patterns/reactors/consumer.ts.tpl +19 -0
  403. package/templates/be/patterns/reactors/message.module.ts.tpl +20 -0
  404. package/templates/be/patterns/reactors/reactor.index.ts.tpl +1 -0
  405. package/templates/be/patterns/reactors/reactor.module.ts.tpl +6 -0
  406. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +6 -0
  407. package/templates/be/patterns/realtime/channel.index.ts.tpl +1 -0
  408. package/templates/be/patterns/realtime/channel.input.ts.tpl +12 -0
  409. package/templates/be/patterns/realtime/channel.subscription.spec.ts.tpl +51 -0
  410. package/templates/be/patterns/realtime/channel.subscription.ts.tpl +32 -0
  411. package/templates/be/patterns/realtime/channel.type.ts.tpl +9 -0
  412. package/templates/be/patterns/saga/saga.compensate.command.ts.tpl +11 -0
  413. package/templates/be/patterns/saga/saga.compensate.contracts.ts.tpl +7 -0
  414. package/templates/be/patterns/saga/saga.compensate.handler.ts.tpl +22 -0
  415. package/templates/be/patterns/saga/saga.compensation.ts.tpl +21 -0
  416. package/templates/be/patterns/saga/saga.complete.command.ts.tpl +11 -0
  417. package/templates/be/patterns/saga/saga.complete.contracts.ts.tpl +7 -0
  418. package/templates/be/patterns/saga/saga.complete.handler.ts.tpl +22 -0
  419. package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +21 -0
  420. package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +21 -0
  421. package/templates/be/patterns/saga/saga.index.ts.tpl +1 -0
  422. package/templates/be/patterns/saga/saga.message.module.ts.tpl +23 -0
  423. package/templates/be/patterns/saga/saga.module.ts.tpl +20 -0
  424. package/templates/be/patterns/saga/saga.service.spec.ts.tpl +70 -0
  425. package/templates/be/patterns/saga/saga.service.ts.tpl +44 -0
  426. package/templates/be/patterns/saga/saga.state.ts.tpl +14 -0
  427. package/templates/be/patterns/saga/saga.step.ts.tpl +11 -0
  428. package/templates/be/patterns/saga/saga.undo.command.ts.tpl +10 -0
  429. package/templates/be/patterns/saga/saga.undo.contracts.ts.tpl +5 -0
  430. package/templates/be/patterns/saga/saga.undo.handler.ts.tpl +21 -0
  431. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +72 -0
  432. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +36 -0
  433. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +6 -0
  434. package/templates/be/patterns/webhooks/webhook.index.ts.tpl +1 -0
  435. package/templates/be/patterns/webhooks/webhook.request.ts.tpl +9 -0
  436. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
  437. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -2
  438. package/templates/be/skeleton/apps/__app__/src/main.ts +7 -15
  439. package/templates/be/skeleton/apps/cli/src/app.module.ts +30 -0
  440. package/templates/be/skeleton/apps/cli/src/cli.options.ts +22 -0
  441. package/templates/be/skeleton/apps/cli/src/main.ts +24 -0
  442. package/templates/be/skeleton/src/features/cli/cli.module.ts +7 -0
  443. package/templates/be/skeleton/src/features/cli/index.ts +1 -0
  444. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +17 -0
  445. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +15 -0
  446. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +7 -0
  447. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.spec.ts +17 -0
  448. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.ts +18 -0
  449. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +17 -0
  450. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +15 -0
  451. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +7 -0
  452. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +33 -0
  453. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +34 -0
  454. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +13 -0
  455. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +40 -0
  456. package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +1 -1
  457. package/templates/be/skeleton/src/modules/domain/note/errors/note.error.ts +16 -0
  458. package/templates/be/skeleton/src/modules/domain/note/index.ts +3 -0
  459. package/templates/be/skeleton/src/modules/domain/note/note.contracts.ts +19 -0
  460. package/templates/be/skeleton/src/modules/domain/note/note.decorators.ts +9 -0
  461. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +7 -0
  462. package/templates/be/skeleton/src/modules/domain/note/note.module.ts +19 -0
  463. package/templates/be/skeleton/src/modules/domain/note/note.options.ts +2 -0
  464. package/templates/be/skeleton/src/modules/domain/note/note.service.spec.ts +76 -0
  465. package/templates/be/skeleton/src/modules/domain/note/note.service.ts +37 -0
  466. package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +8 -0
  467. package/templates/be/skeleton/src/modules/domain/note/persistence/entities/note.entity.ts +17 -0
  468. package/templates/be/skeleton/src/modules/domain/note/persistence/migrations/1790000000000-init-note.ts +22 -0
  469. package/templates/be/skeleton/src/modules/domain/note/persistence/note.rows.ts +17 -0
  470. package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +4 -0
  471. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +20 -0
  472. package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
  473. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +18 -0
  474. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +41 -0
  475. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.ts +17 -0
  476. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +13 -0
  477. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +77 -0
  478. package/templates/be/skeleton/src/modules/platform/database/database.options.ts +30 -0
  479. package/templates/be/skeleton/src/modules/platform/database/database.port.ts +65 -0
  480. package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +47 -0
  481. package/templates/be/skeleton/src/modules/platform/database/errors/database.error.ts +16 -0
  482. package/templates/be/skeleton/src/modules/platform/database/index.ts +9 -0
  483. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +30 -0
  484. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.spec.ts +84 -0
  485. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.ts +23 -0
  486. package/templates/be/skeleton/src/modules/platform/database/primary.config.ts +9 -0
  487. package/templates/be/skeleton/src/modules/platform/database/primary.connection.ts +2 -0
  488. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +10 -0
  489. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +108 -0
  490. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +75 -0
  491. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +88 -0
  492. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +29 -0
  493. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +48 -0
  494. package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +1 -1
  495. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +12 -0
  496. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -0
  497. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +8 -1
  498. package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +8 -0
  499. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +14 -1
  500. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +12 -0
  501. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +10 -2
  502. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +63 -0
  503. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +108 -0
  504. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.spec.ts +39 -0
  505. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.ts +25 -0
  506. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +172 -0
  507. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.ts +66 -0
  508. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +3 -1
  509. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +52 -0
  510. package/templates/be/skeleton-lite/apps/api/Dockerfile +37 -0
  511. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +16 -0
  512. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +81 -0
  513. package/templates/be/skeleton-lite/apps/api/src/main.ts +34 -0
  514. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +14 -0
  515. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +50 -0
  516. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +26 -0
  517. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +19 -0
  518. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +26 -0
  519. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +7 -0
  520. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +6 -0
  521. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +19 -0
  522. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +10 -0
  523. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +24 -0
  524. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +8 -0
  525. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +17 -0
  526. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +32 -0
  527. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +24 -0
  528. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +4 -0
  529. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +54 -0
  530. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +23 -0
  531. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +10 -0
  532. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +49 -0
  533. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +4 -0
  534. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +21 -0
  535. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +11 -0
  536. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +28 -0
  537. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +25 -0
  538. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +9 -0
  539. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +4 -0
  540. package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +5 -0
  541. package/templates/be/table/error.ts.tpl +16 -0
  542. package/templates/be/table/index.ts.tpl +2 -0
  543. package/templates/be/table/migration.sql.tpl +51 -0
  544. package/templates/be/table/module-definition.ts.tpl +7 -0
  545. package/templates/be/table/module.ts.tpl +14 -0
  546. package/templates/be/table/options.ts.tpl +2 -0
  547. package/templates/be/table/rows.ts.tpl +4 -0
  548. package/templates/be/table/service.ts.tpl +23 -0
  549. package/templates/be/table/sql.ts.tpl +4 -0
  550. package/templates/be/tool-config/jest.config.js +3 -1
  551. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +14 -0
  552. package/templates/be/upgrade-full/apps/cli/src/main.ts +26 -0
  553. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +6 -0
  554. package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +23 -0
  555. package/templates/common/gitignore.base +1 -2
  556. package/templates/fe/app/package.json.tpl +1 -0
  557. package/templates/fe/app/tsconfig.json +12 -0
  558. package/templates/fe/common/app-layout.tsx.partial +8 -0
  559. package/templates/fe/common/site-shell.tsx.partial +9 -0
  560. package/templates/fe/image/next/Dockerfile +42 -0
  561. package/templates/fe/skeleton/apps/app/next.config.ts +23 -0
  562. package/templates/fe/skeleton/apps/app/postcss.config.mjs +7 -0
  563. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/error.tsx +2 -2
  564. package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +22 -0
  565. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/loading.tsx +2 -2
  566. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/not-found.tsx +2 -2
  567. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +9 -0
  568. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/global-error.tsx +2 -2
  569. package/templates/fe/skeleton/apps/app/src/app/globals.css +10 -0
  570. package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +18 -0
  571. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/component.tsx +19 -0
  572. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/index.tsx +11 -0
  573. package/templates/fe/skeleton/apps/app/src/features/pages/AppGlobalErrorPage/index.tsx +16 -0
  574. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/component.tsx +10 -0
  575. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +15 -0
  576. package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +8 -0
  577. package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  578. package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +4 -0
  579. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +28 -0
  580. package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -0
  581. package/templates/fe/skeleton/apps/app/src/modules/routes/index.ts +4 -0
  582. package/templates/fe/skeleton/apps/app/src/proxy.ts +1 -0
  583. package/templates/fe/skeleton/apps/{__app__ → landing}/next.config.ts +4 -2
  584. package/templates/fe/skeleton/apps/landing/postcss.config.mjs +7 -0
  585. package/templates/fe/skeleton/apps/landing/src/app/[locale]/error.tsx +10 -0
  586. package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +22 -0
  587. package/templates/fe/skeleton/apps/landing/src/app/[locale]/loading.tsx +6 -0
  588. package/templates/fe/skeleton/apps/landing/src/app/[locale]/not-found.tsx +6 -0
  589. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +9 -0
  590. package/templates/fe/skeleton/apps/landing/src/app/global-error.tsx +10 -0
  591. package/templates/fe/skeleton/apps/landing/src/app/globals.css +10 -0
  592. package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +24 -0
  593. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/component.tsx +18 -0
  594. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/index.tsx +11 -0
  595. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingGlobalErrorPage/index.tsx +16 -0
  596. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +8 -0
  597. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +9 -0
  598. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/component.tsx +24 -0
  599. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +16 -0
  600. package/templates/fe/skeleton/apps/landing/src/modules/config/index.ts +12 -0
  601. package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +7 -0
  602. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/vi.json +31 -0
  603. package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -0
  604. package/templates/fe/skeleton/apps/landing/src/modules/routes/index.ts +4 -0
  605. package/templates/fe/skeleton/apps/landing/src/proxy.ts +1 -0
  606. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +49 -0
  607. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +24 -0
  608. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +20 -0
  609. package/templates/fe/skeleton/packages/__project__-ui/src/branches/Providers/index.tsx +23 -0
  610. package/templates/fe/skeleton/packages/__project__-ui/src/composites/ErrorNotice/index.tsx +14 -0
  611. package/templates/fe/skeleton/packages/__project__-ui/src/composites/GlobalErrorNotice/index.tsx +25 -0
  612. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LoadingNotice/index.tsx +9 -0
  613. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LocaleShell/index.tsx +21 -0
  614. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +19 -0
  615. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/classNames.ts +4 -0
  616. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/index.tsx +19 -0
  617. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +6 -0
  618. package/templates/fe/skeleton-lite/apps/web/next.config.ts +22 -0
  619. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/error.tsx +10 -0
  620. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +37 -0
  621. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/loading.tsx +6 -0
  622. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/not-found.tsx +6 -0
  623. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +9 -0
  624. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/providers.tsx +26 -0
  625. package/templates/fe/skeleton-lite/apps/web/src/app/auth/callback/route.ts +24 -0
  626. package/templates/fe/skeleton-lite/apps/web/src/app/global-error.tsx +10 -0
  627. package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/app/globals.css +1 -1
  628. package/templates/fe/skeleton-lite/apps/web/src/app/health/live/route.ts +5 -0
  629. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/component.tsx +29 -0
  630. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +41 -0
  631. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/component.tsx +17 -0
  632. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/index.tsx +38 -0
  633. package/templates/fe/skeleton-lite/apps/web/src/components/composites/ErrorNotice/index.tsx +13 -0
  634. package/templates/fe/skeleton-lite/apps/web/src/components/composites/GlobalErrorNotice/index.tsx +24 -0
  635. package/templates/fe/skeleton-lite/apps/web/src/components/composites/LoadingNotice/index.tsx +7 -0
  636. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +18 -0
  637. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/classNames.ts +4 -0
  638. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/index.tsx +13 -0
  639. package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +31 -0
  640. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/component.tsx +12 -0
  641. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/index.tsx +10 -0
  642. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppGlobalErrorPage/index.tsx +15 -0
  643. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +14 -0
  644. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +30 -0
  645. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +8 -0
  646. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  647. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/brand.css +20 -0
  648. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/index.ts +2 -0
  649. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +20 -0
  650. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/read-session.ts +25 -0
  651. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-in.ts +25 -0
  652. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +17 -0
  653. package/templates/fe/skeleton-lite/apps/web/src/modules/db/browser.ts +14 -0
  654. package/templates/fe/skeleton-lite/apps/web/src/modules/db/index.ts +3 -0
  655. package/templates/fe/skeleton-lite/apps/web/src/modules/db/outcome.ts +59 -0
  656. package/templates/fe/skeleton-lite/apps/web/src/modules/db/principal.ts +29 -0
  657. package/templates/fe/skeleton-lite/apps/web/src/modules/db/server.ts +70 -0
  658. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +50 -0
  659. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +8 -0
  660. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +48 -0
  661. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +16 -0
  662. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +14 -0
  663. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +7 -0
  664. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/safe-next-path.ts +26 -0
  665. package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +43 -0
  666. package/templates/fe/table/read.ts.tpl +16 -0
  667. package/templates/fe/table/write.ts.tpl +16 -0
  668. package/upgrade/check.mjs +137 -0
  669. package/upgrade/index.mjs +524 -0
  670. package/bin/hfs.mjs +0 -233
  671. package/runtime/scripts/api/fs/rmdir-link.mjs +0 -12
  672. package/runtime/scripts/lib/test-secrets.mjs +0 -116
  673. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +0 -20
  674. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +0 -9
  675. package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +0 -28
  676. package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +0 -35
  677. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +0 -19
  678. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -11
  679. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -24
  680. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -14
  681. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -21
  682. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +0 -15
  683. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +0 -23
  684. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +0 -8
  685. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +0 -28
  686. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +0 -8
  687. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/config.ts +0 -8
  688. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +0 -2
  689. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +0 -25
  690. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/request.ts +0 -19
  691. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.ts +0 -9
  692. package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +0 -4
  693. package/templates/fe/skeleton/apps/__app__/src/proxy.ts +0 -10
  694. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.contracts.ts +0 -0
  695. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.handler.ts +0 -0
  696. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.query.ts +0 -0
  697. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/index.ts +0 -0
  698. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/system-health.module.ts +0 -0
  699. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/live.controller.ts +0 -0
  700. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/system-health-http.module.ts +0 -0
  701. /package/templates/fe/skeleton/apps/{__app__ → app}/src/app/health/live/route.ts +0 -0
  702. /package/templates/fe/skeleton/apps/{__app__ → app}/src/modules/config/index.ts +0 -0
  703. /package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/postcss.config.mjs +0 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The async contract of a service: `contracts/<service>/events.json`, emitted from the typed event classes of the service,
3
+ * `src/modules/events/<service>/<event>.event.ts` (name, version, `compensates`, the payload fields of the interface `create` takes).
4
+ * Nothing is executed: the classes are read as a TypeScript syntax tree with the repository's own `typescript`. The reader and the
5
+ * printer are the runtime's (`scripts/lib/event-contract.mjs`), so `starci app check` (R166 HFS_EVENT_CONTRACT) compares the committed file
6
+ * with exactly this text.
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { createRequire } from 'node:module';
11
+ import { readEventClasses, snapshotText } from '../runtime/scripts/lib/event-contract.mjs';
12
+
13
+ const EVENTS_ROOT = 'src/modules/events';
14
+ const EVENT_SUFFIX = '.event.ts';
15
+
16
+ /** The repository-relative path of a service's event contract. */
17
+ export const eventsSnapshotPath = (service) => `contracts/${service}/events.json`;
18
+
19
+ /** The services that declare event classes under `src/modules/events/<service>/`. */
20
+ export function eventServices(repoRoot) {
21
+ const root = path.join(repoRoot, EVENTS_ROOT);
22
+ if (!fs.existsSync(root)) return [];
23
+ return fs.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
24
+ }
25
+
26
+ /** The text of the event contract of `service`, or null when it declares no event class; a class that cannot be read throws. */
27
+ export function emitEvents({ repoRoot, service }) {
28
+ const folder = path.join(repoRoot, EVENTS_ROOT, service);
29
+ const files = fs.existsSync(folder) ? fs.readdirSync(folder).filter((name) => name.endsWith(EVENT_SUFFIX)).sort() : [];
30
+ if (files.length === 0) return null;
31
+ const ts = createRequire(path.join(repoRoot, 'package.json'))('typescript');
32
+ const events = [];
33
+ const problems = [];
34
+ for (const name of files) {
35
+ const read = readEventClasses(ts, fs.readFileSync(path.join(folder, name), 'utf8'), name);
36
+ events.push(...read.events);
37
+ problems.push(...read.problems.map((problem) => `${name}: ${problem}`));
38
+ }
39
+ if (problems.length > 0) throw new Error(`starci app emit: the event classes of ${service} cannot be read: ${problems.join('; ')}`);
40
+ return snapshotText(service, events);
41
+ }
@@ -1,4 +1,4 @@
1
- // The child process of `hfs emit-contracts` for the OPERATIONS of ONE app: `node operations-worker.mjs <repoRoot> <app>`.
1
+ // The child process of `starci app emit` for the operations of one app.
2
2
  // Prints the OpenAPI 3.1 document of the app's typed operation table on stdout; exit 3 when the app has no `apps/<app>/src/operations.ts`.
3
3
  // Loads the repository's own typescript (resolved from the repository, never from hfs). Nothing of the repository is executed.
4
4
  import { createRequire } from 'node:module';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The versioned operations of an api app, read from its typed operation table and printed as OpenAPI 3.1.
3
3
  *
4
- * The table is the canon's one declaration of an app's operation surface (knowledge/patterns/be/operations.yaml, BE-OPERATIONS-1):
4
+ * The table is the canon's one declaration of an app's operation surface (knowledge/patterns/be/api.yaml, BE-OPERATIONS-1):
5
5
  * `apps/<app>/src/operations.ts` exports `OPERATIONS`, made by `defineOperations({ "<id>@<version>": operation<Input, Output,
6
6
  * RefusalCode>("query" | "mutation") })`. The types come from the TypeScript checker; nothing is executed. An input, an output or a
7
7
  * refusal set the checker cannot express (`any`, `unknown`, `Record<string, unknown>`, an unbound generic, `string` as a refusal code)
@@ -14,7 +14,7 @@
14
14
  import { createSchemaBuilder, stable } from './type-schema.mjs';
15
15
 
16
16
  /** The export of `apps/<app>/src/operations.ts` that holds the table. */
17
- export const OPERATIONS_EXPORT = 'OPERATIONS';
17
+ const OPERATIONS_EXPORT = 'OPERATIONS';
18
18
 
19
19
  /** The repository-relative path of an app's operation table. */
20
20
  export const operationsPath = (app) => `apps/${app}/src/operations.ts`;
@@ -1,4 +1,4 @@
1
- // The child process of `hfs emit-contracts` for ONE app: `node schema-worker.mjs <repoRoot> <app>`.
1
+ // The child process of `starci app emit` for one app.
2
2
  // Prints the schema the app serves on stdout; exit 3 when the app's module graph has no GraphQL server.
3
3
  // Loads the repository's own typescript, @nestjs/* and graphql (resolved from the repository, never from hfs).
4
4
  //
@@ -0,0 +1,50 @@
1
+ // bound-sys.cjs - the typed ESLint run never reads types from above the app root.
2
+ //
3
+ // `starci app lint` starts ESLint with `node --require <this file>` and STARCI_LINT_BOUND set to the app root. typescript-eslint's
4
+ // projectService builds its server host from `tsserver.sys` (typescript/lib/tsserverlibrary) and exposes no host through
5
+ // parserOptions, so without a bound a typed rule reads types from an enclosing repository's node_modules and judges the app
6
+ // greener than its own install allows. Here `sys` of the app's own TypeScript is patched, in place and before ESLint loads
7
+ // the parser, to answer only for paths inside the app root and TypeScript's own lib directory (the same bound the gate's
8
+ // tsc programs use, scripts/gates/gate.mjs boundedSys). realpath is the identity, so an install junctioned inside the app
9
+ // stays inside it. Nothing is patched when STARCI_LINT_BOUND is unset or TypeScript is not installed (nothing typed runs).
10
+ 'use strict'
11
+ const path = require('node:path')
12
+ const { createRequire } = require('node:module')
13
+
14
+ const key = (p) => path.resolve(String(p)).replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase()
15
+
16
+ /** Patch `sys` (the TypeScript system object) so it answers only for paths inside `bound` and `libDir`. Returns `sys`. */
17
+ function bindSys(sys, bound, libDir) {
18
+ const roots = [key(bound), key(libDir)]
19
+ const inside = (p) => {
20
+ const k = key(p)
21
+ return roots.some((root) => k === root || k.startsWith(`${root}/`))
22
+ }
23
+ const base = { ...sys }
24
+ return Object.assign(sys, {
25
+ fileExists: (p) => inside(p) && base.fileExists(p),
26
+ directoryExists: (p) => inside(p) && base.directoryExists(p),
27
+ readFile: (p, encoding) => (inside(p) ? base.readFile(p, encoding) : undefined),
28
+ getDirectories: (p) => (inside(p) ? base.getDirectories(p) : []),
29
+ readDirectory: (dir, ext, exclude, include, depth) => (inside(dir) ? base.readDirectory(dir, ext, exclude, include, depth).filter(inside) : []),
30
+ realpath: (p) => p,
31
+ })
32
+ }
33
+
34
+ /** Bind the `sys` of every TypeScript entry the typed lint loads, resolved from `from` (a side folder of the app). */
35
+ function bindTypeScript(bound, from) {
36
+ const require_ = createRequire(path.join(from, 'package.json'))
37
+ const bound_ = [];
38
+ for (const entry of ['typescript/lib/tsserverlibrary', 'typescript']) {
39
+ let ts
40
+ try { ts = require_(entry) } catch { continue }
41
+ if (!ts?.sys || bound_.includes(ts.sys)) continue
42
+ bindSys(ts.sys, bound, path.dirname(ts.getDefaultLibFilePath({})))
43
+ bound_.push(ts.sys)
44
+ }
45
+ return bound_.length
46
+ }
47
+
48
+ if (process.env.STARCI_LINT_BOUND) bindTypeScript(process.env.STARCI_LINT_BOUND, process.cwd())
49
+
50
+ module.exports = { bindSys, bindTypeScript }
package/lint/run.mjs CHANGED
@@ -1,51 +1,72 @@
1
- // hfs lint - the ONE lint entry and the ONE report of a StarCi app.
1
+ // starci app lint - the ONE lint entry and the ONE report of a StarCi app.
2
2
  //
3
- // hfs lint [--repo <dir>] [--changed <file>...] [--fix] [--format text|json] [--sonar <file>]
3
+ // starci app lint [--cwd <dir>] [--changed <file>...] [--workspace <dir>] [--fix] [--format text|json] [--sonar <file>]
4
4
  //
5
5
  // It runs at the app root (the folder of hfs.json), over the app (or over the app-relative files named by --changed):
6
6
  // 1. ESLint once per side, from the side folder, through the app's own install and that side's eslint.config.mjs (be/ the be canon,
7
7
  // fe/ the fe canon: the per-file rules and the project-graph rules of the architecture machine, findings on the line of the
8
8
  // offending TypeScript file); each side lints only its own files, so the be rules never see fe/ and the fe rules never see be/;
9
- // 2. `hfs check` for what has no TypeScript file to sit on (the tree, managed files, pins, CI, contracts, docs, .starciwork), root and sides;
9
+ // 2. `starci app check` for what has no TypeScript file to sit on (the tree, managed files, pins, CI, contracts, docs, .starciwork), root and sides;
10
10
  // 3. stylelint over the fe side's stylesheets (sync STYLE_GLOB), from fe/ with its stylelint.config.mjs.
11
11
  // Their findings become one list of one shape (`starci/lint@1`), every path app-relative. `--format json` prints that report on stdout;
12
12
  // `--sonar <file>` writes the same findings as THE Sonar Generic Issue Import file (engine ids starci-hfs, eslint, stylelint in one
13
13
  // document); the exit code is the land-gate input: 0 no finding, 1 at least one, 2 a tool could not run (never a pass).
14
14
  // --changed restricts ESLint and stylelint to the listed files and keeps of the app findings only those on a listed file or on no file.
15
+ // --workspace scopes the same lint to one npm workspace of the fe side (fe/apps/<app> or fe/packages/<pkg>, app-relative): ESLint and
16
+ // stylelint over that folder only, and of the app findings only those inside it. It is the `lint` script of every fe workspace (the
17
+ // turbo `lint` task); the root `npm run lint` stays the whole app. There is no second linter: a workspace lint is this lint, scoped.
15
18
  import fs from 'node:fs';
16
19
  import path from 'node:path';
17
20
  import { createRequire } from 'node:module';
18
21
  import { spawnSync } from 'node:child_process';
22
+ import { fileURLToPath, pathToFileURL } from 'node:url';
19
23
  import { linterReport, mergeReports, sonarReport, sourceRootsOf } from '../report/sonar.mjs';
20
24
  import { SIDES, appRelativeMessages, loadSlotManifest, readRepoDeclaration } from '../runtime/scripts/hfs/slots.mjs';
21
25
  import { STYLE_GLOB } from '../sync/index.mjs';
26
+ import { braceVariants, globExpression } from '../runtime/scripts/lib/glob.mjs';
22
27
 
23
28
  export const LINT_SCHEMA = 'starci/lint@1';
24
29
  /** The side whose stylesheets stylelint judges. */
25
30
  const STYLE_SIDE = 'fe';
26
31
  const CODE_PREFIX = /^\[([A-Z][A-Z0-9_]+)\] /;
27
32
  const posix = (file) => String(file).replace(/\\/g, '/').replace(/^\.\//, '');
33
+ /** An npm workspace of the fe side, app-relative: one folder below fe/apps or fe/packages (the root package.json workspaces). */
34
+ const WORKSPACE = /^fe\/(?:apps|packages)\/[a-z0-9][a-z0-9-]*$/;
28
35
 
29
- /** The flags of `hfs lint`; `--changed` takes every argument up to the next flag. */
36
+ /** The app-relative workspace of `opts.workspace` (already app-relative), or a refusal when it is not one folder of fe/apps or fe/packages. */
37
+ export function workspaceOf(workspace) {
38
+ const rel = posix(workspace).replace(/\/+$/, '');
39
+ if (!WORKSPACE.test(rel)) throw new Error(`--workspace ${workspace} is not an fe workspace (fe/apps/<app> or fe/packages/<pkg>, from the app root)`);
40
+ return rel;
41
+ }
42
+
43
+ /** The flags of `starci app lint`; `--changed` takes every argument up to the next flag, and a repeated `--changed` adds to the list (the
44
+ * catalog's list flag, `--changed a --changed b`, as the CLI dispatcher passes it through). */
30
45
  export function parseLintArgs(argv) {
31
- const opts = { changed: null, fix: false, format: 'text', repo: undefined, sonar: undefined };
46
+ const opts = { changed: null, fix: false, format: 'text', sonar: undefined, workspace: undefined };
32
47
  for (let i = 0; i < argv.length; i += 1) {
33
48
  const arg = argv[i];
34
49
  if (arg === '--changed') {
35
- opts.changed = [];
50
+ opts.changed ??= [];
36
51
  while (i + 1 < argv.length && !argv[i + 1].startsWith('--')) opts.changed.push(posix(argv[++i]));
37
52
  } else if (arg === '--fix') opts.fix = true;
38
- else if (['--format', '--repo', '--sonar'].includes(arg)) {
53
+ else if (arg === '--json') opts.format = 'json';
54
+ else if (['--format', '--sonar', '--workspace'].includes(arg)) {
39
55
  if (argv[i + 1] === undefined) throw new Error(`${arg} needs a value`);
40
56
  opts[arg.slice(2)] = argv[++i];
41
57
  } else throw new Error(`unknown argument ${arg}`);
42
58
  }
43
59
  if (!['text', 'json'].includes(opts.format)) throw new Error('--format is text or json');
60
+ if (opts.workspace !== undefined && opts.changed !== null) throw new Error('--workspace and --changed do not combine: a workspace lint judges the whole workspace');
44
61
  return opts;
45
62
  }
46
63
 
64
+ /** The typed ESLint run reads nothing above the app root: its TypeScript `sys` is bound by this preload (3.3, bound-sys.cjs). */
65
+ export const BOUND_ENV = 'STARCI_LINT_BOUND';
66
+ export const linterBoundArgs = () => ['--require', fileURLToPath(new URL('./bound-sys.cjs', import.meta.url))];
67
+
47
68
  /** A linter's json results through the app's own install, run from `cwd` (a side folder): `{ results }` or `{ error }`. */
48
- function runLinter({ cwd, pkg, bin, args }) {
69
+ function runLinter({ cwd, pkg, bin, args, bound = null }) {
49
70
  let entry;
50
71
  try {
51
72
  const manifest = createRequire(path.join(cwd, 'package.json')).resolve(`${pkg}/package.json`);
@@ -54,13 +75,15 @@ function runLinter({ cwd, pkg, bin, args }) {
54
75
  } catch {
55
76
  return { error: `${pkg} is not installed for ${cwd}` };
56
77
  }
57
- const run = spawnSync(process.execPath, [entry, ...args], { cwd, encoding: 'utf8', maxBuffer: 512 * 1024 * 1024 });
58
- // The linters exit 1 when they found something; no json at all means they could not run. ESLint prints its report on stdout,
59
- // stylelint (16 and later) its formatter output on stderr.
78
+ const run = spawnSync(process.execPath, [...(bound ? linterBoundArgs() : []), entry, ...args], { cwd, encoding: 'utf8', maxBuffer: 512 * 1024 * 1024, ...(bound ? { env: { ...process.env, [BOUND_ENV]: bound } } : {}) });
79
+ // ESLint prints its report on stdout; stylelint uses stderr. JSON does not prove a completed child: only exits 0/1
80
+ // without a spawn error or signal are measurable, and exit 1 must carry findings.
60
81
  let results;
61
82
  try { results = JSON.parse(run.stdout.trim() || run.stderr); } catch { return { error: `${pkg} produced no json report in ${path.basename(cwd)}/ (exit ${run.status}): ${String(run.stderr || run.stdout).trim().split('\n')[0]}` }; }
62
83
  if (!Array.isArray(results)) return { error: `${pkg} produced a report that is not a result list` };
63
- return { results };
84
+ const completed = !run.error && !run.signal && [0, 1].includes(run.status)
85
+ && (run.status === 0 || results.some((result) => result?.messages?.length || result?.warnings?.length));
86
+ return { results, ...(completed ? {} : { error: `${pkg} could not complete in ${path.basename(cwd)}/ (exit ${run.status}, signal ${run.signal ?? 'none'}): ${String(run.error?.message || run.stderr || run.stdout || '').trim().split('\n')[0]}` }) };
64
87
  }
65
88
 
66
89
  const eslintFindings = (results, appRoot) => results.flatMap((result) => (result.messages ?? []).map((message) => ({
@@ -78,30 +101,111 @@ const byLocation = (a, b) => `${a.path ?? ''}:${String(a.line ?? 0).padStart(7,
78
101
  /** The files of `changed` (app-relative) below `side`/, relative to the side folder. */
79
102
  const onSide = (changed, side) => changed.filter((file) => file.startsWith(`${side}/`)).map((file) => file.slice(side.length + 1));
80
103
 
104
+ const fileKey = (file) => process.platform === 'win32' ? path.resolve(file).toLowerCase() : path.resolve(file);
105
+ const inside = (root, file) => { const rel = path.relative(root, file); return rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel); };
106
+
107
+ /** Read the installed canon's source selectors, then enumerate that scope; unavailable or unsupported selectors cannot prove coverage. */
108
+ async function sourceFiles({ repoRoot, cwd, side, scope }) {
109
+ const entry = createRequire(path.join(cwd, 'package.json')).resolve(`@starci/eslint-canon-${side}`);
110
+ if (!inside(fs.realpathSync(repoRoot), fs.realpathSync(entry))) throw new Error('the installed canon is outside the app root');
111
+ const canon = await import(pathToFileURL(entry).href);
112
+ const factory = side === 'be' ? canon.starciBeConfig : canon.starciFeConfig;
113
+ if (typeof factory !== 'function' || typeof canon.loadHfs !== 'function') throw new Error('the installed canon has no supported config factory');
114
+ const config = await factory({ hfs: canon.loadHfs(pathToFileURL(path.join(cwd, 'eslint.config.mjs')).href) });
115
+ if (!Array.isArray(config)) throw new Error('the installed canon did not return a flat config');
116
+ const selectors = [], ignored = [];
117
+ for (const block of config) {
118
+ if (!block || typeof block !== 'object') throw new Error('the installed canon returned an invalid config block');
119
+ for (const [field, target] of [['files', selectors], ['ignores', ignored]]) {
120
+ if (block[field] === undefined) continue;
121
+ if (!Array.isArray(block[field]) || block[field].some((glob) => typeof glob !== 'string' || glob.startsWith('!'))) throw new Error(`unsupported canon ${field} selectors`);
122
+ if (field === 'ignores' && Object.keys(block).some((key) => !['ignores', 'name'].includes(key))) throw new Error('unsupported non-global canon ignores');
123
+ target.push(...block[field].flatMap((glob) => braceVariants(glob).map(globExpression)));
124
+ }
125
+ }
126
+ if (!selectors.length) throw new Error('the installed canon has no source selectors');
127
+ const matches = (patterns, file) => patterns.some((pattern) => pattern.test(file));
128
+ const files = [];
129
+ const visit = (folder) => {
130
+ for (const child of fs.readdirSync(path.join(cwd, folder), { withFileTypes: true })) {
131
+ const file = posix(path.join(folder, child.name));
132
+ // Ask the canon's global ignore patterns before entering output or installed-package trees.
133
+ if (matches(ignored, file) || matches(ignored, `${file}/__starci_scope_probe__`)) continue;
134
+ if (child.isSymbolicLink()) throw new Error(`cannot enumerate symlink ${file}`);
135
+ if (child.isDirectory()) visit(file);
136
+ else if (child.isFile() && matches(selectors, file)) files.push(file);
137
+ }
138
+ };
139
+ visit(scope ?? '');
140
+ return files.sort();
141
+ }
142
+
143
+ /** Every requested source needs an explicit, well-formed result; suppressed findings and foreign paths cannot witness a completed lint. */
144
+ function checkedEslintResults({ results, expected, cwd, scope, changed }) {
145
+ const required = new Set(expected.map((file) => fileKey(path.join(cwd, file))));
146
+ const seen = new Set(), valid = [], errors = [];
147
+ for (const result of results) {
148
+ if (!result || typeof result.filePath !== 'string' || !path.isAbsolute(result.filePath) || !Array.isArray(result.messages)
149
+ || result.messages.some((message) => !message || typeof message.message !== 'string')
150
+ || (result.suppressedMessages !== undefined && !Array.isArray(result.suppressedMessages))) {
151
+ errors.push('eslint produced a malformed file result'); continue;
152
+ }
153
+ const key = fileKey(result.filePath);
154
+ if (!inside(cwd, result.filePath) || (scope && !inside(path.join(cwd, scope), result.filePath)) || (changed && !required.has(key))) {
155
+ errors.push(`eslint produced a result outside the requested scope: ${result.filePath}`); continue;
156
+ }
157
+ if (seen.has(key)) errors.push(`eslint produced duplicate results for ${result.filePath}`);
158
+ seen.add(key);
159
+ if (result.suppressedMessages?.length) errors.push(`eslint suppressed findings for ${result.filePath}`);
160
+ valid.push(result);
161
+ }
162
+ const missing = expected.filter((file) => !seen.has(fileKey(path.join(cwd, file))));
163
+ if (missing.length) errors.push(`eslint produced no result for requested source files: ${missing.join(', ')}`);
164
+ return { results: valid, errors };
165
+ }
166
+
81
167
  /**
82
- * Run the whole lint of the app at `repoRoot`. `hfsCheck(repoRoot)` returns the `hfs check` result (`{ findings, tracked }`); the CLI
168
+ * Run the whole lint of the app at `repoRoot`. `hfsCheck(repoRoot)` returns the `starci app check` result (`{ findings, tracked }`); the CLI
83
169
  * injects it. Returns `{ report, sonar, exit }`; `sonar` is the merged Generic Issue Import document.
84
170
  */
85
171
  export async function lintRepository({ repoRoot, opts, hfsCheck, trackedFiles = () => [] }) {
86
172
  const changed = opts.changed === null ? null : new Set(opts.changed);
87
- const existing = changed ? [...changed].filter((file) => fs.existsSync(path.join(repoRoot, file))) : [];
88
173
  const errors = [];
174
+ let workspace = null;
175
+ if (opts.workspace !== undefined) {
176
+ try { workspace = workspaceOf(opts.workspace); } catch (error) { errors.push(String(error?.message ?? error)); }
177
+ }
178
+ /** Whether an app-relative path lies inside the scoped workspace. */
179
+ const inWorkspace = (file) => posix(file).startsWith(`${workspace}/`);
180
+ const existing = changed ? [...changed].filter((file) => fs.existsSync(path.join(repoRoot, file))) : [];
89
181
  const findings = [];
90
182
  const raw = { eslint: [], stylelint: [] };
91
183
  const engines = {};
92
184
 
93
185
  let app = null;
94
- try { app = readRepoDeclaration(loadSlotManifest(), repoRoot); } catch (error) { errors.push(`hfs lint runs at the app root: ${String(error?.message ?? error)}`); }
95
- if (app && !app.sides) { errors.push(`${repoRoot} is the ${app.side} side of an app; hfs lint runs at the app root, the folder of hfs.json`); app = null; }
186
+ try { app = readRepoDeclaration(loadSlotManifest(), repoRoot); } catch (error) { errors.push(`starci app lint runs at the app root: ${String(error?.message ?? error)}`); }
187
+ if (app && !app.sides) { errors.push(`${repoRoot} is the ${app.side} side of an app; starci app lint runs at the app root, the folder of hfs.json`); app = null; }
188
+ if (opts.workspace !== undefined && workspace === null) app = null;
189
+ if (workspace !== null && !fs.existsSync(path.join(repoRoot, workspace, 'package.json'))) { errors.push(`--workspace ${workspace} holds no package.json; it is not a workspace of this app`); app = null; }
190
+ /** The workspace folder relative to the fe side folder (apps/<app> or packages/<pkg>). */
191
+ const onFe = workspace === null ? null : workspace.slice(STYLE_SIDE.length + 1);
96
192
 
97
193
  // 1. ESLint, once per side, from the side folder with that side's config.
98
194
  engines.eslint = { sides: {} };
99
195
  for (const side of app ? SIDES : []) {
196
+ if (workspace !== null && side !== STYLE_SIDE) { engines.eslint.sides[side] = { files: 0, skipped: `outside --workspace ${workspace}` }; continue; }
100
197
  const sources = onSide(existing, side).filter((file) => /\.(?:[cm]?[jt]sx?)$/.test(file));
101
198
  if (changed !== null && !sources.length) { engines.eslint.sides[side] = { files: 0, skipped: 'no changed source file' }; continue; }
102
- const linted = runLinter({ cwd: path.join(repoRoot, side), pkg: 'eslint', bin: 'eslint', args: ['--format', 'json', ...(opts.fix ? ['--fix'] : []), ...(changed ? sources : ['.'])] });
199
+ const cwd = path.join(repoRoot, side);
200
+ let expected;
201
+ try { expected = changed ? sources : await sourceFiles({ repoRoot, cwd, side, scope: onFe }); }
202
+ catch (error) { errors.push(`eslint source scope is unavailable in ${side}/: ${String(error?.message ?? error)}`); engines.eslint.sides[side] = { files: 0 }; continue; }
203
+ const linted = runLinter({ cwd, pkg: 'eslint', bin: 'eslint', args: ['--format', 'json', ...(opts.fix ? ['--fix'] : []), ...(changed ? sources : [onFe ?? '.'])], bound: repoRoot });
103
204
  if (linted.error) errors.push(linted.error);
104
- else {
205
+ if (linted.results) {
206
+ const checked = checkedEslintResults({ results: linted.results, expected, cwd, scope: onFe, changed: changed !== null });
207
+ errors.push(...checked.errors);
208
+ linted.results = checked.results;
105
209
  // The side canon names side-relative paths in its messages; the report names every path from the app root.
106
210
  const appRelative = appRelativeMessages(side, path.join(repoRoot, side));
107
211
  for (const result of linted.results) for (const message of result.messages ?? []) message.message = appRelative(message.message);
@@ -115,9 +219,9 @@ export async function lintRepository({ repoRoot, opts, hfsCheck, trackedFiles =
115
219
  if (app) {
116
220
  const styles = onSide(existing, STYLE_SIDE).filter((file) => file.endsWith('.css'));
117
221
  if (changed === null || styles.length) {
118
- const linted = runLinter({ cwd: path.join(repoRoot, STYLE_SIDE), pkg: 'stylelint', bin: 'stylelint', args: [...(changed ? styles : [STYLE_GLOB]), '--formatter', 'json', ...(opts.fix ? ['--fix'] : [])] });
222
+ const linted = runLinter({ cwd: path.join(repoRoot, STYLE_SIDE), pkg: 'stylelint', bin: 'stylelint', args: [...(changed ? styles : [onFe ? `${onFe}/src/**/*.css` : STYLE_GLOB]), '--formatter', 'json', ...(opts.fix ? ['--fix'] : [])] });
119
223
  if (linted.error) errors.push(linted.error);
120
- else {
224
+ if (linted.results) {
121
225
  const appRelative = appRelativeMessages(STYLE_SIDE, path.join(repoRoot, STYLE_SIDE));
122
226
  for (const result of linted.results) for (const warning of result.warnings ?? []) warning.text = appRelative(warning.text);
123
227
  raw.stylelint = linted.results;
@@ -130,10 +234,13 @@ export async function lintRepository({ repoRoot, opts, hfsCheck, trackedFiles =
130
234
  // 2. The app check: root and sides.
131
235
  let checked = { findings: [] };
132
236
  if (app) {
133
- try { checked = await hfsCheck(repoRoot); } catch (error) { errors.push(`hfs check could not run: ${String(error?.message ?? error)}`); }
237
+ try { checked = await hfsCheck(repoRoot); } catch (error) { errors.push(`starci app check could not run: ${String(error?.message ?? error)}`); }
134
238
  }
135
239
  const repoErrors = checked.findings.filter((finding) => finding.level === 'error');
136
- const kept = changed === null ? repoErrors : repoErrors.filter((finding) => !finding.path || changed.has(posix(finding.path)));
240
+ // A workspace lint keeps only the app findings inside the workspace; the rest belong to the root lint of the whole app.
241
+ const kept = workspace !== null
242
+ ? repoErrors.filter((finding) => finding.path && inWorkspace(finding.path))
243
+ : changed === null ? repoErrors : repoErrors.filter((finding) => !finding.path || changed.has(posix(finding.path)));
137
244
  for (const finding of kept) {
138
245
  findings.push({ engine: 'hfs', rule: finding.code, code: finding.code, severity: 'error', path: finding.path ? posix(finding.path) : null, line: finding.line ?? null, column: finding.column ?? null, message: finding.message, titleVi: finding.titleVi, nextStepVi: finding.nextStepVi });
139
246
  }
@@ -149,7 +256,7 @@ export async function lintRepository({ repoRoot, opts, hfsCheck, trackedFiles =
149
256
  linterReport('eslint', raw.eslint, { root: repoRoot, sourceRoots, tracked }),
150
257
  linterReport('stylelint', raw.stylelint, { root: repoRoot, sourceRoots, tracked }),
151
258
  ]);
152
- const report = { schema: LINT_SCHEMA, ok: findings.length === 0 && errors.length === 0, repoRoot, changed: changed ? [...changed].sort() : null, counts: { error: findings.length }, engines, errors, findings };
259
+ const report = { schema: LINT_SCHEMA, ok: findings.length === 0 && errors.length === 0, repoRoot, changed: changed ? [...changed].sort() : null, workspace, counts: { error: findings.length }, engines, errors, findings };
153
260
  return { report, sonar, exit: errors.length ? 2 : findings.length ? 1 : 0 };
154
261
  }
155
262
 
@@ -157,5 +264,5 @@ export function printLintText(report, out) {
157
264
  for (const f of report.findings) out(`${f.path ?? '-'}${f.line ? `:${f.line}${f.column ? `:${f.column}` : ''}` : ''} ${f.engine}/${f.rule} ${f.message}\n`);
158
265
  for (const e of report.errors) out(`ERROR ${e}\n`);
159
266
  const per = Object.keys(report.engines).map((engine) => `${engine} ${report.findings.filter((f) => f.engine === engine).length}`).join(', ');
160
- out(`\nhfs lint: ${report.counts.error} finding${report.counts.error === 1 ? '' : 's'} (${per})${report.errors.length ? `, ${report.errors.length} tool error${report.errors.length === 1 ? '' : 's'}` : ''}\n`);
267
+ out(`\nstarci app lint: ${report.counts.error} finding${report.counts.error === 1 ? '' : 's'} (${per})${report.errors.length ? `, ${report.errors.length} tool error${report.errors.length === 1 ? '' : 's'}` : ''}\n`);
161
268
  }
package/package.json CHANGED
@@ -1,25 +1,29 @@
1
1
  {
2
2
  "name": "@starci/hfs",
3
- "version": "4.0.8",
4
- "description": "The HFS command line of a StarCi app (one repository: the root, be/ and fe/): hfs lint, check, scaffold app, explain, sync and work-hygiene. Self-contained: it carries the runtime files it reads.",
3
+ "version": "4.0.10",
4
+ "description": "The StarCi app command implementation for scaffolding, linting, checking and synchronizing one product repository.",
5
5
  "type": "module",
6
- "license": "UNLICENSED",
6
+ "license": "MIT",
7
7
  "private": false,
8
- "bin": {
9
- "hfs": "./bin/hfs.mjs"
10
- },
11
8
  "exports": {
9
+ ".": "./src/main.mjs",
12
10
  "./package.json": "./package.json",
13
11
  "./runtime/*": "./runtime/*"
14
12
  },
13
+ "dependencies": {
14
+ "libpg-query": "18.1.5",
15
+ "smol-toml": "1.9.0"
16
+ },
15
17
  "files": [
16
- "bin/hfs.mjs",
18
+ "LICENSE",
19
+ "src/main.mjs",
17
20
  "emit/**",
18
21
  "lint/**",
19
22
  "scaffold/**",
20
23
  "report/**",
21
24
  "runtime/**",
22
25
  "sync/**",
26
+ "upgrade/**",
23
27
  "templates/**",
24
28
  "README.md",
25
29
  "CHANGELOG.md"
@@ -31,8 +35,10 @@
31
35
  "access": "public"
32
36
  },
33
37
  "scripts": {
34
- "sync": "node scripts/sync-runtime.mjs",
35
- "sync:check": "node scripts/sync-runtime.mjs --check",
36
- "test": "node --test \"bin/*.spec.mjs\""
38
+ "sync": "node ../../scripts/hfs/sync-runtime.mjs",
39
+ "sync:check": "node ../../scripts/hfs/sync-runtime.mjs --check",
40
+ "prepack": "node prep-runtime.mjs",
41
+ "pretest": "node prep-runtime.mjs",
42
+ "test": "node --test \"src/*.spec.mjs\""
37
43
  }
38
44
  }
package/report/sonar.mjs CHANGED
@@ -1,10 +1,10 @@
1
- // Sonar documents of the ONE lint entry `hfs lint` (contract changes hfs-sonar-import, hfs-lint-entry): the building blocks that turn the
1
+ // Sonar documents of the ONE lint entry `starci app lint`: the building blocks that turn the
2
2
  // findings of the StarCi canon into a Sonar Generic Issue Import document (SonarQube 10.3+ format: `{ rules, issues }`).
3
- // sonarReport(findings) the repository findings of `hfs check`, engineId `starci-hfs`, rule id = the finding code
3
+ // sonarReport(findings) the repository findings of `starci app check`, engineId `starci-hfs`, rule id = the finding code
4
4
  // linterReport(kind, results) a linter's own json output (`eslint -f json`, `stylelint --formatter json`), engineId `eslint` / `stylelint`,
5
5
  // rule id = the linter's rule. Sonar's own ESLint import (sonar.eslint.reportPaths) is not used: it drops an
6
6
  // issue on a file outside sonar.sources, and a stylelint result has no native import at all.
7
- // mergeReports(reports) the documents of one `hfs lint` run as ONE file (reports/lint.sonar.json, sonar.externalIssuesReportPaths)
7
+ // mergeReports(reports) the documents of one `starci app lint` run as ONE file
8
8
  // One placement rule for every engine: Sonar imports an issue only on a file it indexes (a tracked source or stylesheet under
9
9
  // sonar.sources). A finding on any other path (hfs.json, a workflow, a package under an unindexed root, e2e/, a directory) is filed
10
10
  // on the first source file of sonar.sources and its message names the real path, so no finding is dropped.
@@ -13,9 +13,9 @@
13
13
  import fs from 'node:fs';
14
14
  import path from 'node:path';
15
15
 
16
- export const HFS_ENGINE = 'starci-hfs';
17
- export const ESLINT_ENGINE = 'eslint';
18
- export const STYLELINT_ENGINE = 'stylelint';
16
+ const HFS_ENGINE = 'starci-hfs';
17
+ const ESLINT_ENGINE = 'eslint';
18
+ const STYLELINT_ENGINE = 'stylelint';
19
19
  /** Every imported issue is a maintainability defect of the highest impact: the gate holds the count at zero. */
20
20
  const IMPACT = Object.freeze([Object.freeze({ softwareQuality: 'MAINTAINABILITY', severity: 'HIGH' })]);
21
21
  const CLEAN_CODE_ATTRIBUTE = 'CONVENTIONAL';
@@ -39,7 +39,7 @@ function document(rules, issues) {
39
39
  return { rules: [...rules.values()].filter((rule) => used.has(rule.id)).sort((a, b) => cmp(a.id, b.id)), issues: sorted };
40
40
  }
41
41
 
42
- /** One document of several (the starci-hfs, eslint and stylelint documents of one `hfs lint` run): rules by id, issues in the one sort order. */
42
+ /** One document of several (the starci-hfs, eslint and stylelint documents of one app lint): rules by id, issues in the one sort order. */
43
43
  export function mergeReports(reports) {
44
44
  const rules = new Map(reports.flatMap((report) => report.rules).map((rule) => [rule.id, rule]));
45
45
  return document(rules, reports.flatMap((report) => report.issues));
@@ -86,7 +86,7 @@ export function sonarReport(findings, { sourceRoots = [], tracked = [] } = {}) {
86
86
  for (const finding of findings) {
87
87
  if (finding.level !== 'error') continue;
88
88
  if (!rules.has(finding.code)) {
89
- rules.set(finding.code, ruleOf(finding.code, HFS_ENGINE, finding.title ?? finding.code, `${finding.title ?? finding.code}. ${finding.titleVi}: ${finding.whyVi} Cách sửa: ${finding.nextStepVi}`));
89
+ rules.set(finding.code, ruleOf(finding.code, HFS_ENGINE, finding.title ?? finding.code, `${finding.title ?? finding.code}. ${finding.titleVi}: ${finding.whyVi} C\u00e1ch s\u1eeda: ${finding.nextStepVi}`));
90
90
  }
91
91
  const primaryLocation = place(finding.path ? posix(finding.path) : null, finding.message, finding.line);
92
92
  if (primaryLocation === null) continue;