@sibyllinesoft/arbiter 0.1.0

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 (302) hide show
  1. package/README.md +222 -0
  2. package/dist/cli.js +28718 -0
  3. package/dist/templates/plopfiles/__tests__/build-systems.test.ts +730 -0
  4. package/dist/templates/plopfiles/__tests__/e2e.test.ts +451 -0
  5. package/dist/templates/plopfiles/__tests__/generation.test.ts +384 -0
  6. package/dist/templates/plopfiles/__tests__/linting.test.ts +854 -0
  7. package/dist/templates/plopfiles/__tests__/modules.test.ts +278 -0
  8. package/dist/templates/plopfiles/__tests__/smoke.test.ts +619 -0
  9. package/dist/templates/plopfiles/__tests__/templates.test.ts +341 -0
  10. package/dist/templates/plopfiles/_modules/backends/go-chi/module.js +43 -0
  11. package/dist/templates/plopfiles/_modules/backends/go-chi/templates/.air.toml.hbs +30 -0
  12. package/dist/templates/plopfiles/_modules/backends/go-chi/templates/cmd/server/main.go.hbs +50 -0
  13. package/dist/templates/plopfiles/_modules/backends/go-chi/templates/go.mod.hbs +9 -0
  14. package/dist/templates/plopfiles/_modules/backends/go-chi/templates/internal/handlers/health.go.hbs +51 -0
  15. package/dist/templates/plopfiles/_modules/backends/kotlin-ktor/module.js +38 -0
  16. package/dist/templates/plopfiles/_modules/backends/kotlin-ktor/templates/build.gradle.kts.hbs +37 -0
  17. package/dist/templates/plopfiles/_modules/backends/kotlin-ktor/templates/settings.gradle.kts.hbs +1 -0
  18. package/dist/templates/plopfiles/_modules/backends/kotlin-ktor/templates/src/main/kotlin/Application.kt.hbs +44 -0
  19. package/dist/templates/plopfiles/_modules/backends/kotlin-ktor/templates/src/main/kotlin/routes/Health.kt.hbs +41 -0
  20. package/dist/templates/plopfiles/_modules/backends/kotlin-ktor/templates/src/main/resources/logback.xml.hbs +11 -0
  21. package/dist/templates/plopfiles/_modules/backends/node-express/module.js +54 -0
  22. package/dist/templates/plopfiles/_modules/backends/node-express/templates/src/index.ts.hbs +37 -0
  23. package/dist/templates/plopfiles/_modules/backends/node-express/templates/src/routes/health.ts.hbs +21 -0
  24. package/dist/templates/plopfiles/_modules/backends/node-express/templates/tsconfig.json.hbs +18 -0
  25. package/dist/templates/plopfiles/_modules/backends/node-hono/module.js +56 -0
  26. package/dist/templates/plopfiles/_modules/backends/node-hono/templates/src/index.ts.hbs +30 -0
  27. package/dist/templates/plopfiles/_modules/backends/node-hono/templates/src/routes/health.ts.hbs +64 -0
  28. package/dist/templates/plopfiles/_modules/backends/node-hono/templates/src/routes/index.ts.hbs +38 -0
  29. package/dist/templates/plopfiles/_modules/backends/node-hono/templates/tsconfig.json.hbs +20 -0
  30. package/dist/templates/plopfiles/_modules/backends/node-hono/templates/typedoc.json.hbs +25 -0
  31. package/dist/templates/plopfiles/_modules/backends/python-fastapi/module.js +65 -0
  32. package/dist/templates/plopfiles/_modules/backends/python-fastapi/templates/app/__init__.py.hbs +1 -0
  33. package/dist/templates/plopfiles/_modules/backends/python-fastapi/templates/app/config.py.hbs +28 -0
  34. package/dist/templates/plopfiles/_modules/backends/python-fastapi/templates/app/main.py.hbs +40 -0
  35. package/dist/templates/plopfiles/_modules/backends/python-fastapi/templates/app/routers/__init__.py.hbs +1 -0
  36. package/dist/templates/plopfiles/_modules/backends/python-fastapi/templates/app/routers/health.py.hbs +24 -0
  37. package/dist/templates/plopfiles/_modules/backends/python-fastapi/templates/pyproject.toml.hbs +40 -0
  38. package/dist/templates/plopfiles/_modules/backends/python-fastapi/templates/requirements.txt.hbs +6 -0
  39. package/dist/templates/plopfiles/_modules/backends/rust-axum/module.js +44 -0
  40. package/dist/templates/plopfiles/_modules/backends/rust-axum/templates/Cargo.toml.hbs +23 -0
  41. package/dist/templates/plopfiles/_modules/backends/rust-axum/templates/src/health.rs.hbs +41 -0
  42. package/dist/templates/plopfiles/_modules/backends/rust-axum/templates/src/main.rs.hbs +57 -0
  43. package/dist/templates/plopfiles/_modules/cloud/aws/module.js +33 -0
  44. package/dist/templates/plopfiles/_modules/cloud/aws/templates/aws-alb.tf.hbs +92 -0
  45. package/dist/templates/plopfiles/_modules/cloud/aws/templates/aws-ecs.tf.hbs +84 -0
  46. package/dist/templates/plopfiles/_modules/cloud/aws/templates/aws-iam.tf.hbs +60 -0
  47. package/dist/templates/plopfiles/_modules/cloud/aws/templates/aws-vpc.tf.hbs +106 -0
  48. package/dist/templates/plopfiles/_modules/cloud/aws/templates/aws.tf.hbs +56 -0
  49. package/dist/templates/plopfiles/_modules/cloud/azure/module.js +31 -0
  50. package/dist/templates/plopfiles/_modules/cloud/azure/templates/azure-container-apps.tf.hbs +99 -0
  51. package/dist/templates/plopfiles/_modules/cloud/azure/templates/azure.tf.hbs +54 -0
  52. package/dist/templates/plopfiles/_modules/cloud/cloudflare/module.js +43 -0
  53. package/dist/templates/plopfiles/_modules/cloud/cloudflare/templates/src/types.d.ts.hbs +6 -0
  54. package/dist/templates/plopfiles/_modules/cloud/cloudflare/templates/src/worker.ts.hbs +51 -0
  55. package/dist/templates/plopfiles/_modules/cloud/cloudflare/templates/wrangler.toml.hbs +36 -0
  56. package/dist/templates/plopfiles/_modules/cloud/gcp/module.js +31 -0
  57. package/dist/templates/plopfiles/_modules/cloud/gcp/templates/gcp-cloudrun.tf.hbs +89 -0
  58. package/dist/templates/plopfiles/_modules/cloud/gcp/templates/gcp.tf.hbs +62 -0
  59. package/dist/templates/plopfiles/_modules/cloud/supabase/module.js +51 -0
  60. package/dist/templates/plopfiles/_modules/cloud/supabase/templates/src/lib/supabase.ts.hbs +25 -0
  61. package/dist/templates/plopfiles/_modules/cloud/supabase/templates/src/types/supabase.ts.hbs +52 -0
  62. package/dist/templates/plopfiles/_modules/cloud/supabase/templates/supabase/config.toml.hbs +71 -0
  63. package/dist/templates/plopfiles/_modules/cloud/supabase/templates/supabase/functions/hello/index.ts.hbs +34 -0
  64. package/dist/templates/plopfiles/_modules/cloud/supabase/templates/supabase/migrations/00000000000000_init.sql.hbs +60 -0
  65. package/dist/templates/plopfiles/_modules/composer.js +180 -0
  66. package/dist/templates/plopfiles/_modules/databases/postgres-drizzle/drizzle.config.ts.hbs +10 -0
  67. package/dist/templates/plopfiles/_modules/databases/postgres-drizzle/module.js +53 -0
  68. package/dist/templates/plopfiles/_modules/databases/postgres-drizzle/templates/index.ts.hbs +15 -0
  69. package/dist/templates/plopfiles/_modules/databases/postgres-drizzle/templates/schema.ts.hbs +21 -0
  70. package/dist/templates/plopfiles/_modules/desktop/electron/module.js +51 -0
  71. package/dist/templates/plopfiles/_modules/desktop/electron/templates/forge.config.ts.hbs +38 -0
  72. package/dist/templates/plopfiles/_modules/desktop/electron/templates/package.json.hbs +22 -0
  73. package/dist/templates/plopfiles/_modules/desktop/electron/templates/src/main.ts.hbs +48 -0
  74. package/dist/templates/plopfiles/_modules/desktop/electron/templates/src/preload.ts.hbs +18 -0
  75. package/dist/templates/plopfiles/_modules/desktop/electron/templates/tsconfig.json.hbs +14 -0
  76. package/dist/templates/plopfiles/_modules/desktop/tauri/module.js +38 -0
  77. package/dist/templates/plopfiles/_modules/desktop/tauri/templates/src-tauri/Cargo.toml.hbs +20 -0
  78. package/dist/templates/plopfiles/_modules/desktop/tauri/templates/src-tauri/build.rs.hbs +3 -0
  79. package/dist/templates/plopfiles/_modules/desktop/tauri/templates/src-tauri/src/main.rs.hbs +30 -0
  80. package/dist/templates/plopfiles/_modules/desktop/tauri/templates/src-tauri/tauri.conf.json.hbs +44 -0
  81. package/dist/templates/plopfiles/_modules/docs/mkdocs/module.js +61 -0
  82. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/docs/architecture/components.md.hbs +37 -0
  83. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/docs/architecture/overview.md.hbs +43 -0
  84. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/docs/contributing/development.md.hbs +164 -0
  85. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/docs/contributing/guidelines.md.hbs +50 -0
  86. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/docs/getting-started/configuration.md.hbs +37 -0
  87. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/docs/getting-started/installation.md.hbs +53 -0
  88. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/docs/getting-started/quickstart.md.hbs +39 -0
  89. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/docs/index.md.hbs +44 -0
  90. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/docs/javascripts/extra.js.hbs +23 -0
  91. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/docs/stylesheets/extra.css.hbs +51 -0
  92. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/mkdocs.yml.hbs +141 -0
  93. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/requirements-docs.txt.hbs +15 -0
  94. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/scripts/aggregate-docs.mjs.hbs +188 -0
  95. package/dist/templates/plopfiles/_modules/docs/mkdocs/templates/scripts/gen_ref_pages.py.hbs +54 -0
  96. package/dist/templates/plopfiles/_modules/frontends/react-vite/module.js +60 -0
  97. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/index.html.hbs +13 -0
  98. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/nginx.conf.hbs +31 -0
  99. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/postcss.config.js.hbs +6 -0
  100. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/src/App.tsx.hbs +12 -0
  101. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/src/index.css.hbs +25 -0
  102. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/src/lib/api.ts.hbs +52 -0
  103. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/src/main.tsx.hbs +18 -0
  104. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/src/pages/Home.tsx.hbs +31 -0
  105. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/tailwind.config.js.hbs +8 -0
  106. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/tsconfig.json.hbs +25 -0
  107. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/tsconfig.node.json.hbs +10 -0
  108. package/dist/templates/plopfiles/_modules/frontends/react-vite/templates/vite.config.ts.hbs +21 -0
  109. package/dist/templates/plopfiles/_modules/frontends/solid-vite/module.js +51 -0
  110. package/dist/templates/plopfiles/_modules/frontends/solid-vite/templates/index.html.hbs +13 -0
  111. package/dist/templates/plopfiles/_modules/frontends/solid-vite/templates/postcss.config.js.hbs +6 -0
  112. package/dist/templates/plopfiles/_modules/frontends/solid-vite/templates/src/App.tsx.hbs +12 -0
  113. package/dist/templates/plopfiles/_modules/frontends/solid-vite/templates/src/index.css.hbs +3 -0
  114. package/dist/templates/plopfiles/_modules/frontends/solid-vite/templates/src/index.tsx.hbs +21 -0
  115. package/dist/templates/plopfiles/_modules/frontends/solid-vite/templates/src/lib/api.ts.hbs +52 -0
  116. package/dist/templates/plopfiles/_modules/frontends/solid-vite/templates/src/pages/Home.tsx.hbs +34 -0
  117. package/dist/templates/plopfiles/_modules/frontends/solid-vite/templates/tailwind.config.js.hbs +8 -0
  118. package/dist/templates/plopfiles/_modules/frontends/solid-vite/templates/tsconfig.json.hbs +24 -0
  119. package/dist/templates/plopfiles/_modules/frontends/solid-vite/templates/vite.config.ts.hbs +21 -0
  120. package/dist/templates/plopfiles/_modules/frontends/vue-vite/module.js +54 -0
  121. package/dist/templates/plopfiles/_modules/frontends/vue-vite/templates/index.html.hbs +13 -0
  122. package/dist/templates/plopfiles/_modules/frontends/vue-vite/templates/postcss.config.js.hbs +6 -0
  123. package/dist/templates/plopfiles/_modules/frontends/vue-vite/templates/src/App.vue.hbs +9 -0
  124. package/dist/templates/plopfiles/_modules/frontends/vue-vite/templates/src/index.css.hbs +3 -0
  125. package/dist/templates/plopfiles/_modules/frontends/vue-vite/templates/src/lib/api.ts.hbs +52 -0
  126. package/dist/templates/plopfiles/_modules/frontends/vue-vite/templates/src/main.ts.hbs +14 -0
  127. package/dist/templates/plopfiles/_modules/frontends/vue-vite/templates/src/pages/Home.vue.hbs +27 -0
  128. package/dist/templates/plopfiles/_modules/frontends/vue-vite/templates/src/router.ts.hbs +13 -0
  129. package/dist/templates/plopfiles/_modules/frontends/vue-vite/templates/tailwind.config.js.hbs +8 -0
  130. package/dist/templates/plopfiles/_modules/frontends/vue-vite/templates/tsconfig.json.hbs +25 -0
  131. package/dist/templates/plopfiles/_modules/frontends/vue-vite/templates/vite.config.ts.hbs +21 -0
  132. package/dist/templates/plopfiles/_modules/infra/docker-compose/module.js +52 -0
  133. package/dist/templates/plopfiles/_modules/infra/docker-compose/templates/Dockerfile.backend.hbs +32 -0
  134. package/dist/templates/plopfiles/_modules/infra/docker-compose/templates/Dockerfile.frontend.hbs +31 -0
  135. package/dist/templates/plopfiles/_modules/infra/docker-compose/templates/docker-compose.yml.hbs +97 -0
  136. package/dist/templates/plopfiles/_modules/infra/github-actions/module.js +24 -0
  137. package/dist/templates/plopfiles/_modules/infra/github-actions/templates/.github/workflows/ci.yml.hbs +78 -0
  138. package/dist/templates/plopfiles/_modules/infra/github-actions/templates/.github/workflows/deploy.yml.hbs +60 -0
  139. package/dist/templates/plopfiles/_modules/infra/just/module.js +26 -0
  140. package/dist/templates/plopfiles/_modules/infra/just/templates/justfile.hbs +205 -0
  141. package/dist/templates/plopfiles/_modules/infra/kubernetes/module.js +30 -0
  142. package/dist/templates/plopfiles/_modules/infra/kubernetes/templates/k8s/base/deployment.yaml.hbs +48 -0
  143. package/dist/templates/plopfiles/_modules/infra/kubernetes/templates/k8s/base/ingress.yaml.hbs +23 -0
  144. package/dist/templates/plopfiles/_modules/infra/kubernetes/templates/k8s/base/kustomization.yaml.hbs +11 -0
  145. package/dist/templates/plopfiles/_modules/infra/kubernetes/templates/k8s/base/service.yaml.hbs +15 -0
  146. package/dist/templates/plopfiles/_modules/infra/kubernetes/templates/k8s/overlays/dev/deployment-patch.yaml.hbs +17 -0
  147. package/dist/templates/plopfiles/_modules/infra/kubernetes/templates/k8s/overlays/dev/kustomization.yaml.hbs +15 -0
  148. package/dist/templates/plopfiles/_modules/infra/kubernetes/templates/k8s/overlays/prod/deployment-patch.yaml.hbs +17 -0
  149. package/dist/templates/plopfiles/_modules/infra/kubernetes/templates/k8s/overlays/prod/kustomization.yaml.hbs +15 -0
  150. package/dist/templates/plopfiles/_modules/infra/pulumi/module.js +30 -0
  151. package/dist/templates/plopfiles/_modules/infra/pulumi/templates/.gitignore.hbs +5 -0
  152. package/dist/templates/plopfiles/_modules/infra/pulumi/templates/Pulumi.dev.yaml.hbs +3 -0
  153. package/dist/templates/plopfiles/_modules/infra/pulumi/templates/Pulumi.yaml.hbs +7 -0
  154. package/dist/templates/plopfiles/_modules/infra/pulumi/templates/index.ts.hbs +26 -0
  155. package/dist/templates/plopfiles/_modules/infra/pulumi/templates/package.json.hbs +12 -0
  156. package/dist/templates/plopfiles/_modules/infra/pulumi/templates/tsconfig.json.hbs +16 -0
  157. package/dist/templates/plopfiles/_modules/infra/terraform/module.js +31 -0
  158. package/dist/templates/plopfiles/_modules/infra/terraform/templates/.gitignore.hbs +12 -0
  159. package/dist/templates/plopfiles/_modules/infra/terraform/templates/main.tf.hbs +34 -0
  160. package/dist/templates/plopfiles/_modules/infra/terraform/templates/outputs.tf.hbs +15 -0
  161. package/dist/templates/plopfiles/_modules/infra/terraform/templates/terraform.tfvars.example.hbs +6 -0
  162. package/dist/templates/plopfiles/_modules/infra/terraform/templates/variables.tf.hbs +30 -0
  163. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/module.js +38 -0
  164. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/app/build.gradle.kts.hbs +102 -0
  165. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/app/proguard-rules.pro.hbs +20 -0
  166. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/app/src/main/AndroidManifest.xml.hbs +25 -0
  167. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/app/src/main/kotlin/com/example/app/ApiClient.kt.hbs +38 -0
  168. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/app/src/main/kotlin/com/example/app/App.kt.hbs +116 -0
  169. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/app/src/main/kotlin/com/example/app/MainActivity.kt.hbs +28 -0
  170. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/app/src/main/kotlin/com/example/app/ui/theme/Color.kt.hbs +11 -0
  171. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/app/src/main/kotlin/com/example/app/ui/theme/Theme.kt.hbs +55 -0
  172. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/app/src/main/kotlin/com/example/app/ui/theme/Type.kt.hbs +31 -0
  173. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/app/src/main/res/values/strings.xml.hbs +3 -0
  174. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/app/src/main/res/values/themes.xml.hbs +4 -0
  175. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/build.gradle.kts.hbs +6 -0
  176. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/gradle.properties.hbs +4 -0
  177. package/dist/templates/plopfiles/_modules/mobile/android-kotlin/templates/settings.gradle.kts.hbs +18 -0
  178. package/dist/templates/plopfiles/_modules/mobile/flutter/module.js +36 -0
  179. package/dist/templates/plopfiles/_modules/mobile/flutter/templates/.env.hbs +1 -0
  180. package/dist/templates/plopfiles/_modules/mobile/flutter/templates/lib/main.dart.hbs +29 -0
  181. package/dist/templates/plopfiles/_modules/mobile/flutter/templates/lib/screens/home_screen.dart.hbs +88 -0
  182. package/dist/templates/plopfiles/_modules/mobile/flutter/templates/lib/services/api_service.dart.hbs +61 -0
  183. package/dist/templates/plopfiles/_modules/mobile/flutter/templates/pubspec.yaml.hbs +25 -0
  184. package/dist/templates/plopfiles/_modules/mobile/ios-swift/module.js +39 -0
  185. package/dist/templates/plopfiles/_modules/mobile/ios-swift/templates/App/App.swift.hbs +10 -0
  186. package/dist/templates/plopfiles/_modules/mobile/ios-swift/templates/App/Assets.xcassets/AccentColor.colorset/Contents.json.hbs +11 -0
  187. package/dist/templates/plopfiles/_modules/mobile/ios-swift/templates/App/Assets.xcassets/AppIcon.appiconset/Contents.json.hbs +13 -0
  188. package/dist/templates/plopfiles/_modules/mobile/ios-swift/templates/App/Assets.xcassets/Contents.json.hbs +6 -0
  189. package/dist/templates/plopfiles/_modules/mobile/ios-swift/templates/App/ContentView.swift.hbs +13 -0
  190. package/dist/templates/plopfiles/_modules/mobile/ios-swift/templates/App/Services/APIClient.swift.hbs +58 -0
  191. package/dist/templates/plopfiles/_modules/mobile/ios-swift/templates/App/Views/DetailsView.swift.hbs +22 -0
  192. package/dist/templates/plopfiles/_modules/mobile/ios-swift/templates/App/Views/HomeView.swift.hbs +62 -0
  193. package/dist/templates/plopfiles/_modules/mobile/ios-swift/templates/App.xcodeproj/project.pbxproj.hbs +363 -0
  194. package/dist/templates/plopfiles/_modules/mobile/react-native/module.js +36 -0
  195. package/dist/templates/plopfiles/_modules/mobile/react-native/templates/app/_layout.tsx.hbs +16 -0
  196. package/dist/templates/plopfiles/_modules/mobile/react-native/templates/app/index.tsx.hbs +66 -0
  197. package/dist/templates/plopfiles/_modules/mobile/react-native/templates/app.json.hbs +37 -0
  198. package/dist/templates/plopfiles/_modules/mobile/react-native/templates/package.json.hbs +29 -0
  199. package/dist/templates/plopfiles/_modules/mobile/react-native/templates/src/lib/api.ts.hbs +52 -0
  200. package/dist/templates/plopfiles/_modules/mobile/react-native/templates/tsconfig.json.hbs +11 -0
  201. package/dist/templates/plopfiles/_modules/quality/biome/module.js +44 -0
  202. package/dist/templates/plopfiles/_modules/quality/biome/templates/biome.json.hbs +126 -0
  203. package/dist/templates/plopfiles/_modules/quality/clippy/module.js +40 -0
  204. package/dist/templates/plopfiles/_modules/quality/clippy/templates/clippy.toml.hbs +29 -0
  205. package/dist/templates/plopfiles/_modules/quality/clippy/templates/rustfmt.toml.hbs +44 -0
  206. package/dist/templates/plopfiles/_modules/quality/golangci-lint/module.js +40 -0
  207. package/dist/templates/plopfiles/_modules/quality/golangci-lint/templates/.golangci.yml.hbs +199 -0
  208. package/dist/templates/plopfiles/_modules/quality/ruff/module.js +43 -0
  209. package/dist/templates/plopfiles/_modules/quality/ruff/templates/mypy.ini.hbs +48 -0
  210. package/dist/templates/plopfiles/_modules/quality/ruff/templates/ruff.toml.hbs +111 -0
  211. package/dist/templates/plopfiles/_modules/storybook/react-storybook/module.js +54 -0
  212. package/dist/templates/plopfiles/_modules/storybook/react-storybook/templates/.storybook/main.ts.hbs +44 -0
  213. package/dist/templates/plopfiles/_modules/storybook/react-storybook/templates/.storybook/preview.ts.hbs +68 -0
  214. package/dist/templates/plopfiles/_modules/storybook/react-storybook/templates/src/stories/Button.stories.tsx.hbs +158 -0
  215. package/dist/templates/plopfiles/_modules/types.ts +98 -0
  216. package/dist/templates/plopfiles/full-stack/plopfile.js +132 -0
  217. package/dist/templates/plopfiles/full-stack/templates/gitignore.hbs +44 -0
  218. package/dist/templates/plopfiles/full-stack/templates/package.json.hbs +21 -0
  219. package/dist/templates/plopfiles/presets/cli-go/module.js +43 -0
  220. package/dist/templates/plopfiles/presets/cli-go/templates/README.md.hbs +42 -0
  221. package/dist/templates/plopfiles/presets/cli-go/templates/config.json.hbs +9 -0
  222. package/dist/templates/plopfiles/presets/cli-node/module.js +45 -0
  223. package/dist/templates/plopfiles/presets/cli-node/templates/README.md.hbs +42 -0
  224. package/dist/templates/plopfiles/presets/cli-node/templates/config.json.hbs +9 -0
  225. package/dist/templates/plopfiles/presets/cli-python/module.js +43 -0
  226. package/dist/templates/plopfiles/presets/cli-python/templates/README.md.hbs +42 -0
  227. package/dist/templates/plopfiles/presets/cli-python/templates/config.json.hbs +9 -0
  228. package/dist/templates/plopfiles/presets/cli-rust/module.js +43 -0
  229. package/dist/templates/plopfiles/presets/cli-rust/templates/README.md.hbs +42 -0
  230. package/dist/templates/plopfiles/presets/cli-rust/templates/config.json.hbs +9 -0
  231. package/dist/templates/plopfiles/presets/loader.js +125 -0
  232. package/dist/templates/plopfiles/typescript-service/plopfile.js +186 -0
  233. package/dist/templates/plopfiles/typescript-service/templates/Dockerfile.hbs +29 -0
  234. package/dist/templates/plopfiles/typescript-service/templates/README.md.hbs +85 -0
  235. package/dist/templates/plopfiles/typescript-service/templates/config.ts.hbs +15 -0
  236. package/dist/templates/plopfiles/typescript-service/templates/db/index.ts.hbs +19 -0
  237. package/dist/templates/plopfiles/typescript-service/templates/db/schema.ts.hbs +16 -0
  238. package/dist/templates/plopfiles/typescript-service/templates/index.ts.hbs +89 -0
  239. package/dist/templates/plopfiles/typescript-service/templates/package.json.hbs +36 -0
  240. package/dist/templates/plopfiles/typescript-service/templates/routes/health.ts.hbs +61 -0
  241. package/dist/templates/plopfiles/typescript-service/templates/routes/index.ts.hbs +35 -0
  242. package/dist/templates/plopfiles/typescript-service/templates/tsconfig.json.hbs +24 -0
  243. package/dist/templates/plopfiles/typescript-service/templates/types.ts.hbs +28 -0
  244. package/dist/templates/typescript/component.module.css.tpl +6 -0
  245. package/dist/templates/typescript/component.test.tsx.tpl +9 -0
  246. package/dist/templates/typescript/component.tsx.tpl +17 -0
  247. package/dist/templates/typescript/component.types.ts.tpl +3 -0
  248. package/dist/templates/typescript/project/nextjs/app/globals.css.tpl +10 -0
  249. package/dist/templates/typescript/project/nextjs/app/layout.tsx.tpl +15 -0
  250. package/dist/templates/typescript/project/nextjs/app/page.tsx.tpl +8 -0
  251. package/dist/templates/typescript/project/nextjs/babel.config.js.tpl +8 -0
  252. package/dist/templates/typescript/project/nextjs/jest.config.js.tpl +10 -0
  253. package/dist/templates/typescript/project/nextjs/jest.setup.ts.tpl +6 -0
  254. package/dist/templates/typescript/project/nextjs/next-env.d.ts.tpl +5 -0
  255. package/dist/templates/typescript/project/nextjs/next.config.js.tpl +9 -0
  256. package/dist/templates/typescript/project/nextjs/package.json.tpl +1 -0
  257. package/dist/templates/typescript/project/nextjs/tsconfig.json.tpl +1 -0
  258. package/dist/templates/typescript/project/vite/App.css.tpl +8 -0
  259. package/dist/templates/typescript/project/vite/App.tsx.tpl +17 -0
  260. package/dist/templates/typescript/project/vite/index.css.tpl +8 -0
  261. package/dist/templates/typescript/project/vite/index.html.tpl +13 -0
  262. package/dist/templates/typescript/project/vite/main.tsx.tpl +10 -0
  263. package/dist/templates/typescript/project/vite/package.json.tpl +1 -0
  264. package/dist/templates/typescript/project/vite/test-setup.ts.tpl +1 -0
  265. package/dist/templates/typescript/project/vite/tsconfig.build.json.tpl +1 -0
  266. package/dist/templates/typescript/project/vite/tsconfig.json.tpl +1 -0
  267. package/dist/templates/typescript/project/vite/tsconfig.node.json.tpl +1 -0
  268. package/dist/templates/typescript/project/vite/vite.config.ts.tpl +18 -0
  269. package/dist/templates/typescript/service.api.ts.tpl +27 -0
  270. package/dist/templates/typescript/service.class.ts.tpl +28 -0
  271. package/dist/templates/typescript/service.handler.ts.tpl +42 -0
  272. package/dist/templates/typescript/service.schema.ts.tpl +16 -0
  273. package/package.json +95 -0
  274. package/templates/typescript/component.module.css.tpl +6 -0
  275. package/templates/typescript/component.test.tsx.tpl +9 -0
  276. package/templates/typescript/component.tsx.tpl +17 -0
  277. package/templates/typescript/component.types.ts.tpl +3 -0
  278. package/templates/typescript/project/nextjs/app/globals.css.tpl +10 -0
  279. package/templates/typescript/project/nextjs/app/layout.tsx.tpl +15 -0
  280. package/templates/typescript/project/nextjs/app/page.tsx.tpl +8 -0
  281. package/templates/typescript/project/nextjs/babel.config.js.tpl +8 -0
  282. package/templates/typescript/project/nextjs/jest.config.js.tpl +10 -0
  283. package/templates/typescript/project/nextjs/jest.setup.ts.tpl +6 -0
  284. package/templates/typescript/project/nextjs/next-env.d.ts.tpl +5 -0
  285. package/templates/typescript/project/nextjs/next.config.js.tpl +9 -0
  286. package/templates/typescript/project/nextjs/package.json.tpl +1 -0
  287. package/templates/typescript/project/nextjs/tsconfig.json.tpl +1 -0
  288. package/templates/typescript/project/vite/App.css.tpl +8 -0
  289. package/templates/typescript/project/vite/App.tsx.tpl +17 -0
  290. package/templates/typescript/project/vite/index.css.tpl +8 -0
  291. package/templates/typescript/project/vite/index.html.tpl +13 -0
  292. package/templates/typescript/project/vite/main.tsx.tpl +10 -0
  293. package/templates/typescript/project/vite/package.json.tpl +1 -0
  294. package/templates/typescript/project/vite/test-setup.ts.tpl +1 -0
  295. package/templates/typescript/project/vite/tsconfig.build.json.tpl +1 -0
  296. package/templates/typescript/project/vite/tsconfig.json.tpl +1 -0
  297. package/templates/typescript/project/vite/tsconfig.node.json.tpl +1 -0
  298. package/templates/typescript/project/vite/vite.config.ts.tpl +18 -0
  299. package/templates/typescript/service.api.ts.tpl +27 -0
  300. package/templates/typescript/service.class.ts.tpl +28 -0
  301. package/templates/typescript/service.handler.ts.tpl +42 -0
  302. package/templates/typescript/service.schema.ts.tpl +16 -0
@@ -0,0 +1,854 @@
1
+ /**
2
+ * Infrastructure Linting Tests
3
+ *
4
+ * Uses specialized linters to validate generated infrastructure files.
5
+ * Tests are skipped if the linter isn't available.
6
+ *
7
+ * Supported linters:
8
+ * - terraform: terraform validate, tflint
9
+ * - kubernetes: kubectl --dry-run, kubeconform
10
+ * - docker: docker compose config, hadolint
11
+ * - github actions: actionlint
12
+ * - yaml: yamllint
13
+ *
14
+ * Run with: bun test linting.test.ts
15
+ */
16
+
17
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
18
+ import { tmpdir } from "os";
19
+ import { dirname, join } from "path";
20
+ import { fileURLToPath } from "url";
21
+ import { $ } from "bun";
22
+ import { mkdir, mkdtemp, readdir, rm } from "fs/promises";
23
+ import nodePlop from "node-plop";
24
+ import { loadModule } from "../_modules/composer.js";
25
+
26
+ const __dirname = dirname(fileURLToPath(import.meta.url));
27
+
28
+ const LINT_TIMEOUT = 60_000;
29
+
30
+ // Helper to execute plop actions
31
+ async function executePlopActions(
32
+ destPath: string,
33
+ actions: Array<Record<string, unknown>>,
34
+ context: Record<string, unknown>,
35
+ ) {
36
+ const plop = await nodePlop(undefined, {
37
+ destBasePath: destPath,
38
+ force: true,
39
+ });
40
+
41
+ // Register helpers on plop's Handlebars instance
42
+ plop.setHelper(
43
+ "kebabCase",
44
+ (str: string) =>
45
+ str
46
+ ?.toLowerCase()
47
+ .replace(/\s+/g, "-")
48
+ .replace(/[^a-z0-9-]/g, "") || "",
49
+ );
50
+ plop.setHelper(
51
+ "snakeCase",
52
+ (str: string) =>
53
+ str
54
+ ?.toLowerCase()
55
+ .replace(/[\s-]+/g, "_")
56
+ .replace(/[^a-z0-9_]/g, "") || "",
57
+ );
58
+ plop.setHelper("camelCase", (str: string) => {
59
+ if (!str) return "";
60
+ return str.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (_, chr: string) => chr.toUpperCase());
61
+ });
62
+ plop.setHelper("pascalCase", (str: string) => {
63
+ if (!str) return "";
64
+ const camel = str
65
+ .toLowerCase()
66
+ .replace(/[^a-zA-Z0-9]+(.)/g, (_, chr: string) => chr.toUpperCase());
67
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
68
+ });
69
+ plop.setHelper(
70
+ "titleCase",
71
+ (str: string) => str?.replace(/\b\w/g, (char) => char.toUpperCase()) || "",
72
+ );
73
+ plop.setHelper("eq", (a: unknown, b: unknown) => a === b);
74
+ plop.setHelper("ne", (a: unknown, b: unknown) => a !== b);
75
+ plop.setHelper("json", (obj: unknown) => JSON.stringify(obj, null, 2));
76
+
77
+ plop.setGenerator("temp", {
78
+ description: "temp",
79
+ prompts: [],
80
+ actions: actions as any[],
81
+ });
82
+
83
+ const gen = plop.getGenerator("temp");
84
+ const results = await gen.runActions(context);
85
+
86
+ if (results.failures && results.failures.length > 0) {
87
+ const errors = results.failures.map((f) => f.error || f.message || String(f)).join("\n");
88
+ throw new Error(`Generation failed:\n${errors}`);
89
+ }
90
+
91
+ return results;
92
+ }
93
+
94
+ // Helper to generate a single module
95
+ async function generateModule(
96
+ category: string,
97
+ moduleName: string,
98
+ destPath: string,
99
+ context: Record<string, unknown>,
100
+ ) {
101
+ const mod = await loadModule(category, moduleName);
102
+ const actions = mod.default(context);
103
+ await executePlopActions(destPath, actions, context);
104
+ return mod;
105
+ }
106
+
107
+ // Helper to check if a command exists
108
+ async function commandExists(cmd: string): Promise<boolean> {
109
+ try {
110
+ await $`which ${cmd}`.quiet();
111
+ return true;
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+
117
+ // Track available linters
118
+ const linters: Record<string, boolean> = {};
119
+
120
+ beforeAll(async () => {
121
+ linters.terraform = await commandExists("terraform");
122
+ linters.tflint = await commandExists("tflint");
123
+ linters.kubectl = await commandExists("kubectl");
124
+ linters.kubeconform = await commandExists("kubeconform");
125
+ linters.docker = await commandExists("docker");
126
+ linters.hadolint = await commandExists("hadolint");
127
+ linters.actionlint = await commandExists("actionlint");
128
+ linters.yamllint = await commandExists("yamllint");
129
+
130
+ console.log(
131
+ "Available linters:",
132
+ Object.entries(linters)
133
+ .filter(([_, v]) => v)
134
+ .map(([k]) => k)
135
+ .join(", ") || "none",
136
+ );
137
+ });
138
+
139
+ describe("Terraform Linting", () => {
140
+ let tempDir: string;
141
+
142
+ beforeAll(async () => {
143
+ tempDir = await mkdtemp(join(tmpdir(), "arbiter-lint-tf-"));
144
+ });
145
+
146
+ afterAll(async () => {
147
+ await rm(tempDir, { recursive: true, force: true });
148
+ });
149
+
150
+ test(
151
+ "terraform validate passes",
152
+ async () => {
153
+ if (!linters.terraform) {
154
+ console.log("Skipping: terraform not available");
155
+ return;
156
+ }
157
+
158
+ const projectDir = join(tempDir, "tf-validate");
159
+ await mkdir(projectDir, { recursive: true });
160
+
161
+ const context = { name: "tf-validate", projectDir };
162
+ await generateModule("infra", "terraform", projectDir, context);
163
+
164
+ const tfDir = join(projectDir, "infra/terraform");
165
+
166
+ // Init and validate
167
+ const initResult = await $`cd ${tfDir} && terraform init -backend=false`.nothrow();
168
+ expect(initResult.exitCode).toBe(0);
169
+
170
+ const validateResult = await $`cd ${tfDir} && terraform validate`.nothrow();
171
+ expect(validateResult.exitCode).toBe(0);
172
+
173
+ // Check formatting
174
+ const fmtResult = await $`cd ${tfDir} && terraform fmt -check -recursive`.nothrow();
175
+ if (fmtResult.exitCode !== 0) {
176
+ console.warn("Terraform formatting issues detected (non-blocking)");
177
+ }
178
+ },
179
+ LINT_TIMEOUT,
180
+ );
181
+
182
+ test(
183
+ "tflint passes",
184
+ async () => {
185
+ if (!linters.tflint) {
186
+ console.log("Skipping: tflint not available");
187
+ return;
188
+ }
189
+
190
+ const projectDir = join(tempDir, "tf-tflint");
191
+ await mkdir(projectDir, { recursive: true });
192
+
193
+ const context = { name: "tf-tflint", projectDir };
194
+ await generateModule("infra", "terraform", projectDir, context);
195
+
196
+ const tfDir = join(projectDir, "infra/terraform");
197
+
198
+ // Run tflint
199
+ const result = await $`cd ${tfDir} && tflint --init && tflint`.nothrow();
200
+ // tflint may have warnings, exit 0 or 2 is acceptable
201
+ expect(result.exitCode).toBeLessThanOrEqual(2);
202
+ },
203
+ LINT_TIMEOUT,
204
+ );
205
+ });
206
+
207
+ describe("Kubernetes Linting", () => {
208
+ let tempDir: string;
209
+
210
+ beforeAll(async () => {
211
+ tempDir = await mkdtemp(join(tmpdir(), "arbiter-lint-k8s-"));
212
+ });
213
+
214
+ afterAll(async () => {
215
+ await rm(tempDir, { recursive: true, force: true });
216
+ });
217
+
218
+ test(
219
+ "kubectl dry-run validates manifests",
220
+ async () => {
221
+ if (!linters.kubectl) {
222
+ console.log("Skipping: kubectl not available");
223
+ return;
224
+ }
225
+
226
+ // Check if kubectl can actually connect to a cluster (not just have a config)
227
+ // Use a simple API call that requires a working connection
228
+ const clusterCheck = await $`kubectl get --raw /api/v1 2>&1`.quiet().nothrow();
229
+ if (clusterCheck.exitCode !== 0) {
230
+ console.log(
231
+ "Skipping: No Kubernetes cluster connection (use kubeconform for offline validation)",
232
+ );
233
+ return;
234
+ }
235
+
236
+ const projectDir = join(tempDir, "k8s-kubectl");
237
+ await mkdir(projectDir, { recursive: true });
238
+
239
+ const context = { name: "k8s-kubectl", projectDir, backendDir: "backend" };
240
+ await generateModule("infra", "kubernetes", projectDir, context);
241
+
242
+ const k8sDir = join(projectDir, "k8s/base");
243
+
244
+ // Validate each yaml file (skip kustomization.yaml and ingress.yaml - they need cluster CRDs)
245
+ const files = await readdir(k8sDir);
246
+ const yamlFiles = files.filter(
247
+ (f) =>
248
+ (f.endsWith(".yaml") || f.endsWith(".yml")) &&
249
+ !f.startsWith("kustomization") &&
250
+ !f.includes("ingress"), // ingress needs networking.k8s.io CRD from cluster
251
+ );
252
+
253
+ for (const file of yamlFiles) {
254
+ const result =
255
+ await $`kubectl apply --dry-run=client -f ${join(k8sDir, file)} 2>&1`.nothrow();
256
+ if (result.exitCode !== 0) {
257
+ console.error(`kubectl validation failed for ${file}:`, result.stdout.toString());
258
+ }
259
+ expect(result.exitCode).toBe(0);
260
+ }
261
+ },
262
+ LINT_TIMEOUT,
263
+ );
264
+
265
+ test(
266
+ "kubeconform validates against schemas",
267
+ async () => {
268
+ if (!linters.kubeconform) {
269
+ console.log("Skipping: kubeconform not available");
270
+ return;
271
+ }
272
+
273
+ const projectDir = join(tempDir, "k8s-kubeconform");
274
+ await mkdir(projectDir, { recursive: true });
275
+
276
+ const context = { name: "k8s-kubeconform", projectDir, backendDir: "backend" };
277
+ await generateModule("infra", "kubernetes", projectDir, context);
278
+
279
+ const k8sDir = join(projectDir, "k8s/base");
280
+
281
+ // Run kubeconform, skipping Kustomization CRDs (not in standard schemas)
282
+ const result = await $`kubeconform -summary -skip Kustomization ${k8sDir}`.nothrow();
283
+ expect(result.exitCode).toBe(0);
284
+ },
285
+ LINT_TIMEOUT,
286
+ );
287
+ });
288
+
289
+ describe("Docker Linting", () => {
290
+ let tempDir: string;
291
+
292
+ beforeAll(async () => {
293
+ tempDir = await mkdtemp(join(tmpdir(), "arbiter-lint-docker-"));
294
+ });
295
+
296
+ afterAll(async () => {
297
+ await rm(tempDir, { recursive: true, force: true });
298
+ });
299
+
300
+ test(
301
+ "docker compose config validates",
302
+ async () => {
303
+ if (!linters.docker) {
304
+ console.log("Skipping: docker not available");
305
+ return;
306
+ }
307
+
308
+ const projectDir = join(tempDir, "docker-compose");
309
+ await mkdir(projectDir, { recursive: true });
310
+
311
+ const context = {
312
+ name: "docker-compose",
313
+ projectDir,
314
+ backendDir: "backend",
315
+ frontendDir: "frontend",
316
+ backend: "node-hono",
317
+ frontend: "react-vite",
318
+ database: "postgres-drizzle",
319
+ };
320
+ await generateModule("infra", "docker-compose", projectDir, context);
321
+
322
+ // Validate docker-compose.yml
323
+ const result = await $`cd ${projectDir} && docker compose config`.nothrow();
324
+ expect(result.exitCode).toBe(0);
325
+ },
326
+ LINT_TIMEOUT,
327
+ );
328
+
329
+ test(
330
+ "hadolint validates Dockerfiles",
331
+ async () => {
332
+ if (!linters.hadolint) {
333
+ console.log("Skipping: hadolint not available");
334
+ return;
335
+ }
336
+
337
+ const projectDir = join(tempDir, "hadolint");
338
+ await mkdir(projectDir, { recursive: true });
339
+
340
+ // Generate a backend that includes a Dockerfile
341
+ const context = { name: "hadolint", projectDir, backendDir: "backend" };
342
+ await generateModule("backends", "node-hono", projectDir, context);
343
+
344
+ const dockerfilePath = join(projectDir, "backend/Dockerfile");
345
+
346
+ // Check if Dockerfile exists
347
+ const dockerfileExists = await Bun.file(dockerfilePath).exists();
348
+ if (!dockerfileExists) {
349
+ console.log("Skipping: No Dockerfile in node-hono module");
350
+ return;
351
+ }
352
+
353
+ // Run hadolint
354
+ const result = await $`hadolint ${dockerfilePath}`.nothrow();
355
+ // hadolint may have warnings (exit 1), only fail on errors (exit 2+)
356
+ expect(result.exitCode).toBeLessThanOrEqual(1);
357
+ },
358
+ LINT_TIMEOUT,
359
+ );
360
+ });
361
+
362
+ describe("GitHub Actions Linting", () => {
363
+ let tempDir: string;
364
+
365
+ beforeAll(async () => {
366
+ tempDir = await mkdtemp(join(tmpdir(), "arbiter-lint-gha-"));
367
+ });
368
+
369
+ afterAll(async () => {
370
+ await rm(tempDir, { recursive: true, force: true });
371
+ });
372
+
373
+ test(
374
+ "actionlint validates workflow files",
375
+ async () => {
376
+ if (!linters.actionlint) {
377
+ console.log("Skipping: actionlint not available");
378
+ return;
379
+ }
380
+
381
+ const projectDir = join(tempDir, "gha");
382
+ await mkdir(projectDir, { recursive: true });
383
+
384
+ const context = {
385
+ name: "gha",
386
+ projectDir,
387
+ backendDir: "backend",
388
+ frontendDir: "frontend",
389
+ backend: "node-hono",
390
+ frontend: "react-vite",
391
+ };
392
+ await generateModule("infra", "github-actions", projectDir, context);
393
+
394
+ const workflowsDir = join(projectDir, ".github/workflows");
395
+
396
+ // List workflow files first (glob doesn't expand in bun shell)
397
+ let files: string[];
398
+ try {
399
+ files = await readdir(workflowsDir);
400
+ } catch (e) {
401
+ // Directory may not exist if generation failed
402
+ console.log("Skipping: Workflows directory not generated");
403
+ return;
404
+ }
405
+
406
+ const workflowFiles = files
407
+ .filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"))
408
+ .map((f) => join(workflowsDir, f));
409
+
410
+ if (workflowFiles.length === 0) {
411
+ console.log("Skipping: No workflow files generated");
412
+ return;
413
+ }
414
+
415
+ // Run actionlint on each file
416
+ for (const file of workflowFiles) {
417
+ const result = await $`actionlint ${file}`.nothrow();
418
+ if (result.exitCode !== 0) {
419
+ console.error(`actionlint errors in ${file}:`, result.stderr.toString());
420
+ }
421
+ expect(result.exitCode).toBe(0);
422
+ }
423
+ },
424
+ LINT_TIMEOUT,
425
+ );
426
+ });
427
+
428
+ describe("YAML Linting", () => {
429
+ let tempDir: string;
430
+
431
+ beforeAll(async () => {
432
+ tempDir = await mkdtemp(join(tmpdir(), "arbiter-lint-yaml-"));
433
+ });
434
+
435
+ afterAll(async () => {
436
+ await rm(tempDir, { recursive: true, force: true });
437
+ });
438
+
439
+ test(
440
+ "yamllint validates kubernetes manifests",
441
+ async () => {
442
+ if (!linters.yamllint) {
443
+ console.log("Skipping: yamllint not available");
444
+ return;
445
+ }
446
+
447
+ const projectDir = join(tempDir, "yaml-k8s");
448
+ await mkdir(projectDir, { recursive: true });
449
+
450
+ const context = { name: "yaml-k8s", projectDir, backendDir: "backend" };
451
+ await generateModule("infra", "kubernetes", projectDir, context);
452
+
453
+ const k8sDir = join(projectDir, "k8s/base");
454
+
455
+ // Run yamllint with relaxed rules (allow long lines, etc)
456
+ const result = await $`yamllint -d relaxed ${k8sDir}`.nothrow();
457
+ // yamllint warnings are fine (exit 1)
458
+ expect(result.exitCode).toBeLessThanOrEqual(1);
459
+ },
460
+ LINT_TIMEOUT,
461
+ );
462
+
463
+ test(
464
+ "yamllint validates docker-compose",
465
+ async () => {
466
+ if (!linters.yamllint) {
467
+ console.log("Skipping: yamllint not available");
468
+ return;
469
+ }
470
+
471
+ const projectDir = join(tempDir, "yaml-docker");
472
+ await mkdir(projectDir, { recursive: true });
473
+
474
+ const context = {
475
+ name: "yaml-docker",
476
+ projectDir,
477
+ backendDir: "backend",
478
+ frontendDir: "frontend",
479
+ backend: "node-hono",
480
+ frontend: "react-vite",
481
+ database: "postgres-drizzle",
482
+ };
483
+ await generateModule("infra", "docker-compose", projectDir, context);
484
+
485
+ const composeFile = join(projectDir, "docker-compose.yml");
486
+
487
+ const result = await $`yamllint -d relaxed ${composeFile}`.nothrow();
488
+ expect(result.exitCode).toBeLessThanOrEqual(1);
489
+ },
490
+ LINT_TIMEOUT,
491
+ );
492
+ });
493
+
494
+ describe("Pulumi Linting", () => {
495
+ let tempDir: string;
496
+
497
+ beforeAll(async () => {
498
+ tempDir = await mkdtemp(join(tmpdir(), "arbiter-lint-pulumi-"));
499
+ });
500
+
501
+ afterAll(async () => {
502
+ await rm(tempDir, { recursive: true, force: true });
503
+ });
504
+
505
+ test(
506
+ "pulumi typescript compiles",
507
+ async () => {
508
+ const projectDir = join(tempDir, "pulumi");
509
+ await mkdir(projectDir, { recursive: true });
510
+
511
+ const context = { name: "pulumi", projectDir };
512
+ await generateModule("infra", "pulumi", projectDir, context);
513
+
514
+ const pulumiDir = join(projectDir, "infra/pulumi");
515
+
516
+ // Install dependencies and typecheck
517
+ await $`cd ${pulumiDir} && bun install`.quiet();
518
+ const result = await $`cd ${pulumiDir} && bunx tsc --noEmit`.nothrow();
519
+
520
+ // TypeScript should compile without errors
521
+ expect(result.exitCode).toBe(0);
522
+ },
523
+ LINT_TIMEOUT,
524
+ );
525
+ });
526
+
527
+ describe("Build System Linting", () => {
528
+ let tempDir: string;
529
+
530
+ beforeAll(async () => {
531
+ tempDir = await mkdtemp(join(tmpdir(), "arbiter-lint-build-"));
532
+ });
533
+
534
+ afterAll(async () => {
535
+ await rm(tempDir, { recursive: true, force: true });
536
+ });
537
+
538
+ test(
539
+ "bazel configs are valid with buildifier",
540
+ async () => {
541
+ const buildifierAvailable = await commandExists("buildifier");
542
+ if (!buildifierAvailable) {
543
+ console.log("Skipping: buildifier not available");
544
+ return;
545
+ }
546
+
547
+ const projectDir = join(tempDir, "bazel");
548
+ await mkdir(projectDir, { recursive: true });
549
+ await mkdir(join(projectDir, "backend"), { recursive: true });
550
+
551
+ const context = {
552
+ name: "bazel-test",
553
+ projectDir,
554
+ backendDir: "backend",
555
+ backend: "go-chi",
556
+ frontend: "none",
557
+ };
558
+ await generateModule("build", "bazel", projectDir, context);
559
+
560
+ // Validate WORKSPACE.bazel
561
+ const workspaceResult =
562
+ await $`buildifier --lint=warn --mode=check ${join(projectDir, "WORKSPACE.bazel")}`.nothrow();
563
+ // buildifier returns 0 for valid, 4 for lint warnings (acceptable)
564
+ expect(workspaceResult.exitCode === 0 || workspaceResult.exitCode === 4).toBe(true);
565
+
566
+ // Validate BUILD.bazel files
567
+ const rootBuildResult =
568
+ await $`buildifier --lint=warn --mode=check ${join(projectDir, "BUILD.bazel")}`.nothrow();
569
+ expect(rootBuildResult.exitCode === 0 || rootBuildResult.exitCode === 4).toBe(true);
570
+
571
+ const backendBuildResult =
572
+ await $`buildifier --lint=warn --mode=check ${join(projectDir, "backend/BUILD.bazel")}`.nothrow();
573
+ expect(backendBuildResult.exitCode === 0 || backendBuildResult.exitCode === 4).toBe(true);
574
+ },
575
+ LINT_TIMEOUT,
576
+ );
577
+
578
+ test(
579
+ "nx.json is valid JSON with correct schema",
580
+ async () => {
581
+ const projectDir = join(tempDir, "nx");
582
+ await mkdir(projectDir, { recursive: true });
583
+ await mkdir(join(projectDir, "backend"), { recursive: true });
584
+
585
+ const context = {
586
+ name: "nx-test",
587
+ projectDir,
588
+ backendDir: "backend",
589
+ backend: "node-hono",
590
+ frontend: "none",
591
+ };
592
+ await generateModule("build", "nx", projectDir, context);
593
+
594
+ // Read and parse nx.json
595
+ const nxJsonPath = join(projectDir, "nx.json");
596
+ const nxJsonContent = await Bun.file(nxJsonPath).text();
597
+
598
+ let nxJson: any;
599
+ expect(() => {
600
+ nxJson = JSON.parse(nxJsonContent);
601
+ }).not.toThrow();
602
+
603
+ // Validate required fields
604
+ expect(nxJson.$schema).toBeDefined();
605
+ expect(nxJson.targetDefaults).toBeDefined();
606
+ expect(nxJson.targetDefaults.build).toBeDefined();
607
+ expect(nxJson.targetDefaults.test).toBeDefined();
608
+
609
+ // Validate project.json
610
+ const projectJsonPath = join(projectDir, "backend/project.json");
611
+ const projectJsonContent = await Bun.file(projectJsonPath).text();
612
+
613
+ let projectJson: any;
614
+ expect(() => {
615
+ projectJson = JSON.parse(projectJsonContent);
616
+ }).not.toThrow();
617
+
618
+ expect(projectJson.name).toBeDefined();
619
+ expect(projectJson.targets).toBeDefined();
620
+ expect(projectJson.targets.build).toBeDefined();
621
+ },
622
+ LINT_TIMEOUT,
623
+ );
624
+
625
+ test(
626
+ "turbo.json is valid JSON with correct schema",
627
+ async () => {
628
+ const projectDir = join(tempDir, "turborepo");
629
+ await mkdir(projectDir, { recursive: true });
630
+ await mkdir(join(projectDir, "backend"), { recursive: true });
631
+ await mkdir(join(projectDir, "frontend"), { recursive: true });
632
+
633
+ const context = {
634
+ name: "turbo-test",
635
+ projectDir,
636
+ backendDir: "backend",
637
+ frontendDir: "frontend",
638
+ backend: "node-hono",
639
+ frontend: "react-vite",
640
+ };
641
+ await generateModule("build", "turborepo", projectDir, context);
642
+
643
+ // Read and parse turbo.json
644
+ const turboJsonPath = join(projectDir, "turbo.json");
645
+ const turboJsonContent = await Bun.file(turboJsonPath).text();
646
+
647
+ let turboJson: any;
648
+ expect(() => {
649
+ turboJson = JSON.parse(turboJsonContent);
650
+ }).not.toThrow();
651
+
652
+ // Validate required fields
653
+ expect(turboJson.$schema).toBeDefined();
654
+ expect(turboJson.tasks).toBeDefined();
655
+ expect(turboJson.tasks.build).toBeDefined();
656
+ expect(turboJson.tasks.test).toBeDefined();
657
+ expect(turboJson.tasks.lint).toBeDefined();
658
+
659
+ // Validate backend turbo.json
660
+ const backendTurboPath = join(projectDir, "backend/turbo.json");
661
+ const backendTurboContent = await Bun.file(backendTurboPath).text();
662
+
663
+ let backendTurbo: any;
664
+ expect(() => {
665
+ backendTurbo = JSON.parse(backendTurboContent);
666
+ }).not.toThrow();
667
+
668
+ expect(backendTurbo.extends).toContain("//");
669
+ expect(backendTurbo.tasks).toBeDefined();
670
+
671
+ // Validate frontend turbo.json
672
+ const frontendTurboPath = join(projectDir, "frontend/turbo.json");
673
+ const frontendTurboContent = await Bun.file(frontendTurboPath).text();
674
+
675
+ let frontendTurbo: any;
676
+ expect(() => {
677
+ frontendTurbo = JSON.parse(frontendTurboContent);
678
+ }).not.toThrow();
679
+
680
+ expect(frontendTurbo.extends).toContain("//");
681
+ },
682
+ LINT_TIMEOUT,
683
+ );
684
+
685
+ test(
686
+ "turbo CLI validates config",
687
+ async () => {
688
+ const turboAvailable = await commandExists("turbo");
689
+ if (!turboAvailable) {
690
+ console.log("Skipping: turbo not available");
691
+ return;
692
+ }
693
+
694
+ const projectDir = join(tempDir, "turbo-cli");
695
+ await mkdir(projectDir, { recursive: true });
696
+
697
+ const context = {
698
+ name: "turbo-cli-test",
699
+ projectDir,
700
+ backend: "none",
701
+ frontend: "none",
702
+ };
703
+ await generateModule("build", "turborepo", projectDir, context);
704
+
705
+ // Create minimal package.json for turbo
706
+ await Bun.write(
707
+ join(projectDir, "package.json"),
708
+ JSON.stringify({ name: "turbo-cli-test", private: true, workspaces: [] }, null, 2),
709
+ );
710
+
711
+ // turbo --dry-run should parse config without errors
712
+ const result = await $`cd ${projectDir} && turbo build --dry-run=json 2>&1`.nothrow();
713
+ // turbo may return non-zero if no tasks match, but shouldn't crash on invalid config
714
+ // Exit code 1 is acceptable (no matching packages), but segfault/crash would be different
715
+ expect(result.exitCode).toBeLessThanOrEqual(1);
716
+ },
717
+ LINT_TIMEOUT,
718
+ );
719
+
720
+ test(
721
+ "nx CLI validates config",
722
+ async () => {
723
+ const nxAvailable = await commandExists("nx");
724
+ if (!nxAvailable) {
725
+ console.log("Skipping: nx not available");
726
+ return;
727
+ }
728
+
729
+ const projectDir = join(tempDir, "nx-cli");
730
+ await mkdir(projectDir, { recursive: true });
731
+
732
+ const context = {
733
+ name: "nx-cli-test",
734
+ projectDir,
735
+ backend: "none",
736
+ frontend: "none",
737
+ };
738
+ await generateModule("build", "nx", projectDir, context);
739
+
740
+ // Create minimal package.json
741
+ await Bun.write(
742
+ join(projectDir, "package.json"),
743
+ JSON.stringify(
744
+ {
745
+ name: "nx-cli-test",
746
+ private: true,
747
+ devDependencies: { nx: "^17.0.0" },
748
+ },
749
+ null,
750
+ 2,
751
+ ),
752
+ );
753
+
754
+ // nx show projects should work with valid config
755
+ // First install nx
756
+ await $`cd ${projectDir} && bun install`.quiet().nothrow();
757
+
758
+ const result = await $`cd ${projectDir} && bunx nx show projects 2>&1`.nothrow();
759
+ // May return empty or error if no projects, but shouldn't crash on config
760
+ expect(result.exitCode).toBeLessThanOrEqual(1);
761
+ },
762
+ LINT_TIMEOUT,
763
+ );
764
+ });
765
+
766
+ describe("Cloud Provider Configs", () => {
767
+ let tempDir: string;
768
+
769
+ beforeAll(async () => {
770
+ tempDir = await mkdtemp(join(tmpdir(), "arbiter-lint-cloud-"));
771
+ });
772
+
773
+ afterAll(async () => {
774
+ await rm(tempDir, { recursive: true, force: true });
775
+ });
776
+
777
+ test(
778
+ "aws terraform files are valid",
779
+ async () => {
780
+ if (!linters.terraform) {
781
+ console.log("Skipping: terraform not available");
782
+ return;
783
+ }
784
+
785
+ const projectDir = join(tempDir, "aws");
786
+ await mkdir(projectDir, { recursive: true });
787
+
788
+ const context = { name: "aws-test", projectDir };
789
+ // AWS module is standalone (includes its own terraform block)
790
+ await generateModule("cloud", "aws", projectDir, context);
791
+
792
+ const tfDir = join(projectDir, "infra/terraform");
793
+
794
+ const initResult = await $`cd ${tfDir} && terraform init -backend=false`.nothrow();
795
+ expect(initResult.exitCode).toBe(0);
796
+
797
+ const validateResult = await $`cd ${tfDir} && terraform validate`.nothrow();
798
+ expect(validateResult.exitCode).toBe(0);
799
+ },
800
+ LINT_TIMEOUT,
801
+ );
802
+
803
+ test(
804
+ "gcp terraform files are valid",
805
+ async () => {
806
+ if (!linters.terraform) {
807
+ console.log("Skipping: terraform not available");
808
+ return;
809
+ }
810
+
811
+ const projectDir = join(tempDir, "gcp");
812
+ await mkdir(projectDir, { recursive: true });
813
+
814
+ const context = { name: "gcp-test", projectDir };
815
+ // GCP module is standalone (includes its own terraform block)
816
+ await generateModule("cloud", "gcp", projectDir, context);
817
+
818
+ const tfDir = join(projectDir, "infra/terraform");
819
+
820
+ const initResult = await $`cd ${tfDir} && terraform init -backend=false`.nothrow();
821
+ expect(initResult.exitCode).toBe(0);
822
+
823
+ const validateResult = await $`cd ${tfDir} && terraform validate`.nothrow();
824
+ expect(validateResult.exitCode).toBe(0);
825
+ },
826
+ LINT_TIMEOUT,
827
+ );
828
+
829
+ test(
830
+ "azure terraform files are valid",
831
+ async () => {
832
+ if (!linters.terraform) {
833
+ console.log("Skipping: terraform not available");
834
+ return;
835
+ }
836
+
837
+ const projectDir = join(tempDir, "azure");
838
+ await mkdir(projectDir, { recursive: true });
839
+
840
+ const context = { name: "azure-test", projectDir };
841
+ // Azure module is standalone (includes its own terraform block)
842
+ await generateModule("cloud", "azure", projectDir, context);
843
+
844
+ const tfDir = join(projectDir, "infra/terraform");
845
+
846
+ const initResult = await $`cd ${tfDir} && terraform init -backend=false`.nothrow();
847
+ expect(initResult.exitCode).toBe(0);
848
+
849
+ const validateResult = await $`cd ${tfDir} && terraform validate`.nothrow();
850
+ expect(validateResult.exitCode).toBe(0);
851
+ },
852
+ LINT_TIMEOUT,
853
+ );
854
+ });