@moifort/stack 0.0.0-stage → 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 (263) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +26 -2
  3. package/bin/stack.ts +109 -0
  4. package/hooks/shutdown-simulators-after-push.sh +19 -0
  5. package/package.json +40 -4
  6. package/routines/daily-health.md +78 -0
  7. package/routines/deprecations.md +23 -0
  8. package/routines/sentry-autofix.md +76 -0
  9. package/routines/stack-upgrade.md +45 -0
  10. package/rules/00-collaboration.md +81 -0
  11. package/rules/01-workflow.md +81 -0
  12. package/rules/02-commits.md +71 -0
  13. package/rules/03-code-review.md +25 -0
  14. package/rules/04-push.md +75 -0
  15. package/rules/05-release.md +40 -0
  16. package/rules/06-changelog.md +48 -0
  17. package/rules/07-appstore.md +77 -0
  18. package/rules/08-copy.md +52 -0
  19. package/rules/09-backend.md +132 -0
  20. package/rules/10-ios.md +206 -0
  21. package/rules/11-ops.md +114 -0
  22. package/rules/12-ci.md +32 -0
  23. package/rules/13-ai.md +71 -0
  24. package/rules/14-infra.md +90 -0
  25. package/rules/15-tooling.md +81 -0
  26. package/rules/16-routines.md +69 -0
  27. package/rules/17-landing.md +76 -0
  28. package/skills/appstore/SKILL.md +53 -0
  29. package/skills/domain/SKILL.md +136 -0
  30. package/skills/graphql/SKILL.md +83 -0
  31. package/skills/ios-feature/SKILL.md +94 -0
  32. package/skills/stack-install/SKILL.md +35 -0
  33. package/skills/stack-upgrade/SKILL.md +27 -0
  34. package/src/catalogue.ts +33 -0
  35. package/src/config.ts +20 -0
  36. package/src/init.ts +63 -0
  37. package/src/migrations.ts +72 -0
  38. package/src/migrations.unit.test.ts +44 -0
  39. package/src/package.ts +8 -0
  40. package/src/render.ts +13 -0
  41. package/src/render.unit.test.ts +17 -0
  42. package/src/sync.int.test.ts +103 -0
  43. package/src/sync.ts +161 -0
  44. package/src/templates.ts +121 -0
  45. package/src/validate-content.ts +65 -0
  46. package/templates/backend/.env.example +10 -0
  47. package/templates/backend/.github/workflows/deploy.yml +151 -0
  48. package/templates/backend/.github/workflows/schema.yml +59 -0
  49. package/templates/backend/.github/workflows/test-feat.yml +40 -0
  50. package/templates/backend/.github/workflows/test-int.yml +40 -0
  51. package/templates/backend/.github/workflows/test-unit.yml +64 -0
  52. package/templates/backend/docs/api-evolution.md +67 -0
  53. package/templates/backend/firebase.json +21 -0
  54. package/templates/backend/firestore.indexes.json +4 -0
  55. package/templates/backend/firestore.rules +11 -0
  56. package/templates/backend/infra/auth.tf +54 -0
  57. package/templates/backend/infra/backend.tf +6 -0
  58. package/templates/backend/infra/cicd.tf +63 -0
  59. package/templates/backend/infra/firebase.tf +26 -0
  60. package/templates/backend/infra/firestore.tf +53 -0
  61. package/templates/backend/infra/function.tf +171 -0
  62. package/templates/backend/infra/main.tf +26 -0
  63. package/templates/backend/infra/observer.tf +18 -0
  64. package/templates/backend/infra/outputs.tf +30 -0
  65. package/templates/backend/infra/project.tf +64 -0
  66. package/templates/backend/infra/scheduler.tf +28 -0
  67. package/templates/backend/infra/secrets.tf +44 -0
  68. package/templates/backend/infra/state.tf +13 -0
  69. package/templates/backend/infra/terraform.tfvars.example +16 -0
  70. package/templates/backend/infra/variables.tf +104 -0
  71. package/templates/backend/infra/versions.tf +30 -0
  72. package/templates/backend/nitro.config.ts +40 -0
  73. package/templates/backend/package.json +51 -0
  74. package/templates/backend/scripts/deprecations/inspector-rule.ts +29 -0
  75. package/templates/backend/scripts/deprecations/status.ts +50 -0
  76. package/templates/backend/scripts/deprecations/status.unit.test.ts +57 -0
  77. package/templates/backend/scripts/deprecations/usage.ts +79 -0
  78. package/templates/backend/scripts/deprecations/usage.unit.test.ts +64 -0
  79. package/templates/backend/scripts/deprecations.ts +224 -0
  80. package/templates/backend/scripts/export-graphql-schema.ts +11 -0
  81. package/templates/backend/scripts/generate-changelog-asset.ts +27 -0
  82. package/templates/backend/scripts/install-terraform.sh +61 -0
  83. package/templates/backend/scripts/tf +11 -0
  84. package/templates/backend/server/domain/changelog/business-rules.ts +60 -0
  85. package/templates/backend/server/domain/changelog/business-rules.unit.test.ts +88 -0
  86. package/templates/backend/server/domain/changelog/infrastructure/graphql/queries.ts +17 -0
  87. package/templates/backend/server/domain/changelog/infrastructure/graphql/types.ts +23 -0
  88. package/templates/backend/server/domain/changelog/primitives.ts +19 -0
  89. package/templates/backend/server/domain/changelog/query.ts +13 -0
  90. package/templates/backend/server/domain/changelog/query.unit.test.ts +19 -0
  91. package/templates/backend/server/domain/changelog/types.ts +9 -0
  92. package/templates/backend/server/domain/shared/graphql/builder.ts +52 -0
  93. package/templates/backend/server/domain/shared/graphql/errors.ts +19 -0
  94. package/templates/backend/server/domain/shared/graphql/scalars.ts +51 -0
  95. package/templates/backend/server/domain/shared/graphql/schema.ts +21 -0
  96. package/templates/backend/server/domain/shared/language.ts +26 -0
  97. package/templates/backend/server/domain/shared/page.ts +25 -0
  98. package/templates/backend/server/domain/shared/page.unit.test.ts +51 -0
  99. package/templates/backend/server/domain/shared/primitives.ts +50 -0
  100. package/templates/backend/server/domain/shared/primitives.unit.test.ts +25 -0
  101. package/templates/backend/server/domain/shared/types.ts +14 -0
  102. package/templates/backend/server/domain/user/command.ts +16 -0
  103. package/templates/backend/server/domain/user/infrastructure/graphql/inputs.ts +12 -0
  104. package/templates/backend/server/domain/user/infrastructure/graphql/mutations.ts +40 -0
  105. package/templates/backend/server/domain/user/infrastructure/graphql/queries.ts +13 -0
  106. package/templates/backend/server/domain/user/infrastructure/graphql/types.ts +35 -0
  107. package/templates/backend/server/domain/user/infrastructure/repository.ts +59 -0
  108. package/templates/backend/server/domain/user/mutations.feat.test.ts +42 -0
  109. package/templates/backend/server/domain/user/query.int.test.ts +49 -0
  110. package/templates/backend/server/domain/user/query.ts +34 -0
  111. package/templates/backend/server/domain/user/types.ts +20 -0
  112. package/templates/backend/server/domain/user/use-case.ts +18 -0
  113. package/templates/backend/server/middleware/auth.ts +54 -0
  114. package/templates/backend/server/plugins/00-sentry.ts +74 -0
  115. package/templates/backend/server/plugins/01-request-log.ts +28 -0
  116. package/templates/backend/server/plugins/02-graphql.ts +69 -0
  117. package/templates/backend/server/plugins/03-boot-time.ts +21 -0
  118. package/templates/backend/server/routes/admin/migrate.post.ts +9 -0
  119. package/templates/backend/server/routes/app-config.get.ts +15 -0
  120. package/templates/backend/server/routes/graphql.ts +52 -0
  121. package/templates/backend/server/system/app-support.ts +17 -0
  122. package/templates/backend/server/system/config/index.ts +18 -0
  123. package/templates/backend/server/system/config/primitives.ts +20 -0
  124. package/templates/backend/server/system/config/primitives.unit.test.ts +13 -0
  125. package/templates/backend/server/system/config/types.ts +8 -0
  126. package/templates/backend/server/system/firebase.ts +19 -0
  127. package/templates/backend/server/system/identity.ts +18 -0
  128. package/templates/backend/server/system/logger.ts +119 -0
  129. package/templates/backend/server/system/logger.unit.test.ts +109 -0
  130. package/templates/backend/server/system/migration/migrations/index.ts +6 -0
  131. package/templates/backend/server/system/migration/primitives.ts +18 -0
  132. package/templates/backend/server/system/migration/runner.ts +52 -0
  133. package/templates/backend/server/system/migration/types.ts +16 -0
  134. package/templates/backend/server/system/read-meter.ts +100 -0
  135. package/templates/backend/server/system/read-meter.unit.test.ts +97 -0
  136. package/templates/backend/server/system/request-cache.ts +139 -0
  137. package/templates/backend/server/system/request-cache.unit.test.ts +166 -0
  138. package/templates/backend/server/system/secret-box.ts +49 -0
  139. package/templates/backend/server/system/secret-box.unit.test.ts +46 -0
  140. package/templates/backend/server/test/bdd.ts +12 -0
  141. package/templates/backend/server/test/fake-firestore.ts +483 -0
  142. package/templates/backend/server/utils/apollo.ts +13 -0
  143. package/templates/backend/server/utils/fetch.ts +5 -0
  144. package/templates/backend/server/utils/firestore.ts +91 -0
  145. package/templates/backend/server/utils/input.ts +36 -0
  146. package/templates/backend/server/utils/input.unit.test.ts +43 -0
  147. package/templates/backend/server/utils/slug.ts +21 -0
  148. package/templates/backend/tsconfig.json +16 -0
  149. package/templates/base/.github/workflows/renovate.yml +17 -0
  150. package/templates/base/CHANGELOG.fr.md +3 -0
  151. package/templates/base/CHANGELOG.md +7 -0
  152. package/templates/base/CLAUDE.md +34 -0
  153. package/templates/base/README.md +12 -0
  154. package/templates/base/biome.json +47 -0
  155. package/templates/base/bunfig.toml +2 -0
  156. package/templates/base/docs/adr/0001-built-on-the-polyforms-stack.md +21 -0
  157. package/templates/base/docs/data-recovery.md +116 -0
  158. package/templates/base/docs/ideas.md +4 -0
  159. package/templates/base/project-context.md +43 -0
  160. package/templates/base/renovate.json +37 -0
  161. package/templates/ios/.github/workflows/build-ios.yml +168 -0
  162. package/templates/ios/.github/workflows/ios-upload.yml +308 -0
  163. package/templates/ios/.github/workflows/release-ios.yml +178 -0
  164. package/templates/ios/.github/workflows/testflight-ios.yml +57 -0
  165. package/templates/ios/infra/Gemfile +5 -0
  166. package/templates/ios/infra/fastlane/Appfile +1 -0
  167. package/templates/ios/infra/fastlane/Fastfile +54 -0
  168. package/templates/ios/infra/fastlane/metadata/copyright.txt +1 -0
  169. package/templates/ios/infra/fastlane/metadata/en-US/description.txt +0 -0
  170. package/templates/ios/infra/fastlane/metadata/en-US/keywords.txt +0 -0
  171. package/templates/ios/infra/fastlane/metadata/en-US/name.txt +1 -0
  172. package/templates/ios/infra/fastlane/metadata/en-US/privacy_url.txt +1 -0
  173. package/templates/ios/infra/fastlane/metadata/en-US/promotional_text.txt +0 -0
  174. package/templates/ios/infra/fastlane/metadata/en-US/subtitle.txt +1 -0
  175. package/templates/ios/infra/fastlane/metadata/en-US/support_url.txt +1 -0
  176. package/templates/ios/infra/fastlane/metadata/fr-FR/description.txt +0 -0
  177. package/templates/ios/infra/fastlane/metadata/fr-FR/keywords.txt +0 -0
  178. package/templates/ios/infra/fastlane/metadata/fr-FR/name.txt +1 -0
  179. package/templates/ios/infra/fastlane/metadata/fr-FR/privacy_url.txt +1 -0
  180. package/templates/ios/infra/fastlane/metadata/fr-FR/promotional_text.txt +0 -0
  181. package/templates/ios/infra/fastlane/metadata/fr-FR/subtitle.txt +1 -0
  182. package/templates/ios/infra/fastlane/metadata/fr-FR/support_url.txt +1 -0
  183. package/templates/ios/infra/fastlane/metadata/primary_category.txt +1 -0
  184. package/templates/ios/infra/ios.tf +28 -0
  185. package/templates/ios/ios/ExportOptions.plist +26 -0
  186. package/templates/ios/ios/apollo-codegen-config.json +16 -0
  187. package/templates/ios/ios/project.yml +94 -0
  188. package/templates/ios/ios/{{APP_NAME}}/App.swift +46 -0
  189. package/templates/ios/ios/{{APP_NAME}}/Assets.xcassets/AppIcon.appiconset/AppIcon.png +0 -0
  190. package/templates/ios/ios/{{APP_NAME}}/Assets.xcassets/AppIcon.appiconset/Contents.json +14 -0
  191. package/templates/ios/ios/{{APP_NAME}}/Assets.xcassets/Contents.json +6 -0
  192. package/templates/ios/ios/{{APP_NAME}}/Assets.xcassets/LaunchBackground.colorset/Contents.json +38 -0
  193. package/templates/ios/ios/{{APP_NAME}}/ContentView.swift +17 -0
  194. package/templates/ios/ios/{{APP_NAME}}/Features/Auth/AppleNonce.swift +33 -0
  195. package/templates/ios/ios/{{APP_NAME}}/Features/Auth/AuthRoot.swift +74 -0
  196. package/templates/ios/ios/{{APP_NAME}}/Features/Auth/AuthSession.swift +86 -0
  197. package/templates/ios/ios/{{APP_NAME}}/Features/Auth/LoginScreen.swift +83 -0
  198. package/templates/ios/ios/{{APP_NAME}}/Features/Home/HomeScreen.swift +21 -0
  199. package/templates/ios/ios/{{APP_NAME}}/Features/Home/components/pages/HomePage.swift +40 -0
  200. package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/AccountEnvironment.swift +7 -0
  201. package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/GraphQL/Onboarding.graphql +16 -0
  202. package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/OnboardingAPI.swift +28 -0
  203. package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/OnboardingClient.swift +26 -0
  204. package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/OnboardingGate.swift +50 -0
  205. package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/OnboardingScreen.swift +69 -0
  206. package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/components/pages/FirstNamePage.swift +66 -0
  207. package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/components/pages/WelcomePage.swift +37 -0
  208. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/ChangelogScreen.swift +25 -0
  209. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/ChangelogViewModel.swift +27 -0
  210. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/GraphQL/Settings.graphql +11 -0
  211. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/ProfileSettingsScreen.swift +30 -0
  212. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/ProfileSettingsViewModel.swift +51 -0
  213. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/SettingsAPI.swift +37 -0
  214. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/SettingsClient.swift +40 -0
  215. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/SettingsHomeScreen.swift +39 -0
  216. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/organisms/DeleteAccountButton.swift +30 -0
  217. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/organisms/SettingsRow.swift +56 -0
  218. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/organisms/SignOutButton.swift +23 -0
  219. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/pages/ChangelogPage.swift +59 -0
  220. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/pages/FeedbackSheet.swift +118 -0
  221. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/pages/ProfileSettingsPage.swift +90 -0
  222. package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/pages/SettingsHomePage.swift +70 -0
  223. package/templates/ios/ios/{{APP_NAME}}/Info.plist +14 -0
  224. package/templates/ios/ios/{{APP_NAME}}/Localizable.xcstrings +1030 -0
  225. package/templates/ios/ios/{{APP_NAME}}/PrivacyInfo.xcprivacy +122 -0
  226. package/templates/ios/ios/{{APP_NAME}}/Shared/APIClient.swift +23 -0
  227. package/templates/ios/ios/{{APP_NAME}}/Shared/AcceptLanguageInterceptor.swift +20 -0
  228. package/templates/ios/ios/{{APP_NAME}}/Shared/Components/atoms/AsyncButton.swift +73 -0
  229. package/templates/ios/ios/{{APP_NAME}}/Shared/Components/atoms/AsyncToolbarButton.swift +69 -0
  230. package/templates/ios/ios/{{APP_NAME}}/Shared/Components/atoms/ToolbarIconButton.swift +46 -0
  231. package/templates/ios/ios/{{APP_NAME}}/Shared/Components/molecules/EmptyState.swift +132 -0
  232. package/templates/ios/ios/{{APP_NAME}}/Shared/Components/molecules/LabeledInfoRow.swift +29 -0
  233. package/templates/ios/ios/{{APP_NAME}}/Shared/Components/styles/CaptionLabelStyle.swift +45 -0
  234. package/templates/ios/ios/{{APP_NAME}}/Shared/Components/styles/Copyable.swift +32 -0
  235. package/templates/ios/ios/{{APP_NAME}}/Shared/Components/styles/EdgeToEdgeSeparator.swift +14 -0
  236. package/templates/ios/ios/{{APP_NAME}}/Shared/Components/styles/RowLabelStyle.swift +58 -0
  237. package/templates/ios/ios/{{APP_NAME}}/Shared/CursorPage.swift +26 -0
  238. package/templates/ios/ios/{{APP_NAME}}/Shared/DataChanges.swift +19 -0
  239. package/templates/ios/ios/{{APP_NAME}}/Shared/ErrorPresenter.swift +62 -0
  240. package/templates/ios/ios/{{APP_NAME}}/Shared/ErrorReporting.swift +35 -0
  241. package/templates/ios/ios/{{APP_NAME}}/Shared/FeedbackReporting.swift +42 -0
  242. package/templates/ios/ios/{{APP_NAME}}/Shared/FirebaseTokenInterceptor.swift +49 -0
  243. package/templates/ios/ios/{{APP_NAME}}/Shared/GraphQLClient.swift +54 -0
  244. package/templates/ios/ios/{{APP_NAME}}/Shared/GraphQLHelpers.swift +128 -0
  245. package/templates/ios/ios/{{APP_NAME}}/Shared/GraphQLLoggingInterceptor.swift +28 -0
  246. package/templates/ios/ios/{{APP_NAME}}/Shared/LoadGate.swift +30 -0
  247. package/templates/ios/ios/{{APP_NAME}}/Shared/Secrets.swift.example +9 -0
  248. package/templates/ios/ios/{{APP_NAME}}/Shared/Services/AppSupportGate.swift +50 -0
  249. package/templates/ios/ios/{{APP_NAME}}/Shared/Shell/StartupLoadingView.swift +23 -0
  250. package/templates/ios/ios/{{APP_NAME}}/Shared/Shell/UpdateRequiredView.swift +24 -0
  251. package/templates/ios/ios/{{APP_NAME}}/Shared/Showcase.swift +13 -0
  252. package/templates/ios/ios/{{APP_NAME}}/Shared/SnapshotCache.swift +78 -0
  253. package/templates/ios/ios/{{APP_NAME}}/Shared/UITestEnvironment.swift +70 -0
  254. package/templates/ios/ios/{{APP_NAME}}/{{APP_NAME}}.entitlements +10 -0
  255. package/templates/ios/ios/{{APP_NAME}}Tests/Onboarding/OnboardingGateTests.swift +38 -0
  256. package/templates/ios/ios/{{APP_NAME}}Tests/Settings/SettingsViewModelTests.swift +63 -0
  257. package/templates/ios/ios/{{APP_NAME}}Tests/Support/TestDoubles.swift +35 -0
  258. package/templates/ios/package.merge.json +10 -0
  259. package/templates/ios/scripts/apollo-codegen.ts +31 -0
  260. package/templates/ios/scripts/check-ios-components.ts +72 -0
  261. package/templates/ios/scripts/check-strings.ts +103 -0
  262. package/templates/ios/scripts/install-device.sh +29 -0
  263. package/templates/ios/scripts/testflight.ts +213 -0
@@ -0,0 +1,10 @@
1
+ # Bearer token guarding /admin/* (migrations, scheduled routes). Any string locally.
2
+ NITRO_ADMIN_TOKEN=your-admin-token
3
+ # Sentry DSN of the <app>-node project. Blank disables reporting; never set in dev.
4
+ NITRO_SENTRY_DSN=
5
+ # Sentry user auth token, for the local error triage tooling only (read + resolve
6
+ # issues through the REST API). Not a runtime setting.
7
+ SENTRY_AUTH_TOKEN=
8
+ # Dev-only: the Firebase UID the auth bypass signs requests as, so Apollo Sandbox
9
+ # works without a token.
10
+ NITRO_DEV_USER_ID=
@@ -0,0 +1,151 @@
1
+ name: Deploy
2
+
3
+ # Every push to main redeploys, except one that touches only the landing page,
4
+ # the docs or the captures: none of them reaches the backend, and the Pages
5
+ # workflow publishes the site on its own. The backend is never applied from a
6
+ # workstation: the Terraform state lives in GCS and this job is its only writer.
7
+ # A pull request labelled `staging` deploys its branch to the staging project.
8
+ on:
9
+ push:
10
+ branches: [main]
11
+ paths-ignore:
12
+ - 'site/**'
13
+ - 'docs/**'
14
+ - 'screenshots/**'
15
+ - 'README.md'
16
+ - 'CLAUDE.md'
17
+ pull_request:
18
+ types: [labeled, synchronize]
19
+ workflow_dispatch:
20
+
21
+ permissions:
22
+ contents: read
23
+ id-token: write # required for Workload Identity Federation
24
+
25
+ # The Terraform state takes a lock, so two applies at once do not interleave.
26
+ # Queue the runs instead of racing them, and never cancel one mid-apply.
27
+ concurrency:
28
+ group: deploy-${{ github.event_name == 'pull_request' && 'staging' || 'production' }}
29
+ cancel-in-progress: false
30
+
31
+ jobs:
32
+ deploy:
33
+ name: 🚀 Backend (${{ github.event_name == 'pull_request' && 'staging' || 'production' }})
34
+ if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'staging')
35
+ runs-on: ubuntu-latest
36
+ timeout-minutes: 20
37
+ env:
38
+ # One workspace per environment: the staging project is the same Terraform
39
+ # with its own state prefix and variables.
40
+ TF_WORKSPACE: ${{ github.event_name == 'pull_request' && 'staging' || 'default' }}
41
+ PROJECT_ID: ${{ github.event_name == 'pull_request' && vars.GCP_STAGING_PROJECT_ID || vars.GCP_PROJECT_ID }}
42
+ steps:
43
+ - uses: actions/checkout@v7
44
+ with:
45
+ fetch-depth: 0 # sentry-cli reads the commit history to attach it to the release
46
+
47
+ # A missing secret reaches Terraform as an empty string, not as null: an unset
48
+ # ADMIN_TOKEN would leave /admin/* unguarded. Fail before anything is applied.
49
+ - name: Check the deploy secrets are set
50
+ env:
51
+ GCP_WIF_PROVIDER: ${{ secrets.GCP_WIF_PROVIDER }}
52
+ GCP_DEPLOY_SA: ${{ secrets.GCP_DEPLOY_SA }}
53
+ GCP_BILLING_ACCOUNT: ${{ secrets.GCP_BILLING_ACCOUNT }}
54
+ ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }}
55
+ GCP_ORG_ID: ${{ vars.GCP_ORG_ID }}
56
+ run: |
57
+ missing=0
58
+ for name in GCP_WIF_PROVIDER GCP_DEPLOY_SA GCP_BILLING_ACCOUNT ADMIN_TOKEN GCP_ORG_ID PROJECT_ID; do
59
+ if [ -z "${!name}" ]; then
60
+ echo "::error::$name is not set"
61
+ missing=1
62
+ fi
63
+ done
64
+ exit "$missing"
65
+
66
+ - uses: oven-sh/setup-bun@v2
67
+ - uses: hashicorp/setup-terraform@v4
68
+ with:
69
+ terraform_version: v1.16.6
70
+
71
+ - name: Authenticate to Google Cloud (workload identity)
72
+ uses: google-github-actions/auth@v3
73
+ with:
74
+ workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }}
75
+ service_account: ${{ secrets.GCP_DEPLOY_SA }}
76
+
77
+ - name: Build the Nitro bundle (firebase preset)
78
+ env:
79
+ # Baked into the bundle, so every error Sentry receives says which
80
+ # deploy produced it and which source maps decode it.
81
+ SENTRY_RELEASE: {{APP_SLUG}}-node@${{ github.sha }}
82
+ run: |
83
+ bun install --frozen-lockfile
84
+ bun run generate:assets
85
+ bunx nitro prepare
86
+ bun run generate:graphql
87
+ bun run build
88
+
89
+ # The update screen sends every build below the floor to the App Store: a floor
90
+ # above the build on sale would block users with nothing to install.
91
+ - name: Check the build floor is on the App Store
92
+ env:
93
+ ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
94
+ ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
95
+ ASC_KEY_P8: ${{ secrets.ASC_KEY_P8 }}
96
+ run: bun scripts/deprecations.ts check-floor
97
+
98
+ - name: Upload the source maps to Sentry
99
+ env:
100
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
101
+ SENTRY_ORG: polyforms
102
+ SENTRY_PROJECT: {{APP_SLUG}}-node
103
+ SENTRY_RELEASE: {{APP_SLUG}}-node@${{ github.sha }}
104
+ run: |
105
+ set -euo pipefail
106
+ if [ -n "$SENTRY_AUTH_TOKEN" ]; then
107
+ curl -sL https://sentry.io/get-cli/ | bash
108
+ sentry-cli releases new "$SENTRY_RELEASE"
109
+ sentry-cli sourcemaps inject .output/server
110
+ sentry-cli sourcemaps upload --release "$SENTRY_RELEASE" .output/server
111
+ sentry-cli releases set-commits "$SENTRY_RELEASE" --local --ignore-missing
112
+ sentry-cli releases finalize "$SENTRY_RELEASE"
113
+ else
114
+ echo "::warning::SENTRY_AUTH_TOKEN is unset, deploying without source maps"
115
+ fi
116
+ # The maps themselves never ship: Terraform zips this directory as-is.
117
+ find .output/server -name '*.map' -delete
118
+
119
+ - name: Materialize terraform.tfvars from GitHub secrets
120
+ run: |
121
+ set -euo pipefail
122
+ printf '%s' "${{ secrets.APPLE_PRIVATE_KEY_P8 }}" > infra/apple.p8
123
+ chmod 600 infra/apple.p8
124
+ cat > infra/terraform.tfvars <<EOF
125
+ project_id = "${PROJECT_ID}"
126
+ billing_account_id = "${{ secrets.GCP_BILLING_ACCOUNT }}"
127
+ org_id = "${{ vars.GCP_ORG_ID }}"
128
+ region = "europe-west3"
129
+ firestore_location = "eur3"
130
+ apple_team_id = "${{ secrets.APPLE_TEAM_ID }}"
131
+ apple_services_id = "${{ secrets.APPLE_SERVICES_ID }}"
132
+ apple_key_id = "${{ secrets.APPLE_KEY_ID }}"
133
+ apple_private_key_path = "${{ secrets.APPLE_PRIVATE_KEY_P8 != '' && './apple.p8' || '' }}"
134
+ sentry_dsn = "${{ secrets.SENTRY_DSN }}"
135
+ admin_token = "${{ secrets.ADMIN_TOKEN }}"
136
+ observer_service_account = "${{ vars.OBSERVER_SERVICE_ACCOUNT }}"
137
+ EOF
138
+
139
+ - name: Terraform init + apply
140
+ working-directory: infra
141
+ run: |
142
+ terraform init -input=false
143
+ terraform apply -auto-approve -input=false
144
+
145
+ - name: Run Firestore migrations
146
+ working-directory: infra
147
+ run: |
148
+ URL=$(terraform output -raw function_url)
149
+ curl -fsS -X POST \
150
+ -H "Authorization: Bearer ${{ secrets.ADMIN_TOKEN }}" \
151
+ "${URL}/admin/migrate"
@@ -0,0 +1,59 @@
1
+ name: Schema
2
+
3
+ # A field or an argument leaves the GraphQL schema only after it was deprecated:
4
+ # builds on the App Store may still ask for anything else. See docs/api-evolution.md.
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ paths-ignore:
9
+ - 'site/**'
10
+ - 'docs/**'
11
+ - 'screenshots/**'
12
+ - 'README.md'
13
+ - 'CLAUDE.md'
14
+ pull_request:
15
+ branches: [main]
16
+ paths-ignore:
17
+ - 'site/**'
18
+ - 'docs/**'
19
+ - 'screenshots/**'
20
+ - 'README.md'
21
+ - 'CLAUDE.md'
22
+
23
+ # A newer push to the same branch, or to the same pull request, makes this run
24
+ # pointless: it is cancelled rather than left to use a runner for nothing. Keyed on
25
+ # the workflow file and the ref, since the three test workflows share one name.
26
+ concurrency:
27
+ group: ${{ github.workflow_ref }}
28
+ cancel-in-progress: true
29
+
30
+ permissions:
31
+ contents: read
32
+
33
+ jobs:
34
+ breaking:
35
+ name: 🧬 No breaking change without deprecation
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/checkout@v7
39
+ with:
40
+ fetch-depth: 0 # the diff reads the schema at the base commit
41
+ - uses: oven-sh/setup-bun@v2
42
+ - run: bun install --frozen-lockfile
43
+ # The base is what main held before this push, or the target of the pull request.
44
+ # A first push has no before (all zeros): there is nothing to compare against.
45
+ - name: Compare the schema with its base
46
+ env:
47
+ BEFORE: ${{ github.event.before }}
48
+ BASE_REF: ${{ github.base_ref }}
49
+ run: |
50
+ set -euo pipefail
51
+ if [ -n "$BASE_REF" ]; then
52
+ base="origin/$BASE_REF"
53
+ elif [ -z "${BEFORE//0/}" ]; then
54
+ echo "No previous commit to compare with"
55
+ exit 0
56
+ else
57
+ base="$BEFORE"
58
+ fi
59
+ SCHEMA_BASE="$base" bun run schema:check
@@ -0,0 +1,40 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths-ignore:
7
+ - 'site/**'
8
+ - 'docs/**'
9
+ - 'screenshots/**'
10
+ - 'README.md'
11
+ - 'CLAUDE.md'
12
+ pull_request:
13
+ branches: [main]
14
+ paths-ignore:
15
+ - 'site/**'
16
+ - 'docs/**'
17
+ - 'screenshots/**'
18
+ - 'README.md'
19
+ - 'CLAUDE.md'
20
+
21
+ # A newer push to the same branch, or to the same pull request, makes this run
22
+ # pointless: it is cancelled rather than left to use a runner for nothing. Keyed on
23
+ # the workflow file and the ref, since the three test workflows share one name.
24
+ concurrency:
25
+ group: ${{ github.workflow_ref }}
26
+ cancel-in-progress: true
27
+
28
+ permissions:
29
+ contents: read
30
+
31
+ jobs:
32
+ feat:
33
+ name: 🎯 Feature
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v7
37
+ - uses: oven-sh/setup-bun@v2
38
+ - run: bun install --frozen-lockfile
39
+ - run: bunx nitro prepare
40
+ - run: bun test .feat.test
@@ -0,0 +1,40 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths-ignore:
7
+ - 'site/**'
8
+ - 'docs/**'
9
+ - 'screenshots/**'
10
+ - 'README.md'
11
+ - 'CLAUDE.md'
12
+ pull_request:
13
+ branches: [main]
14
+ paths-ignore:
15
+ - 'site/**'
16
+ - 'docs/**'
17
+ - 'screenshots/**'
18
+ - 'README.md'
19
+ - 'CLAUDE.md'
20
+
21
+ # A newer push to the same branch, or to the same pull request, makes this run
22
+ # pointless: it is cancelled rather than left to use a runner for nothing. Keyed on
23
+ # the workflow file and the ref, since the three test workflows share one name.
24
+ concurrency:
25
+ group: ${{ github.workflow_ref }}
26
+ cancel-in-progress: true
27
+
28
+ permissions:
29
+ contents: read
30
+
31
+ jobs:
32
+ int:
33
+ name: 🔗 Integration
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v7
37
+ - uses: oven-sh/setup-bun@v2
38
+ - run: bun install --frozen-lockfile
39
+ - run: bunx nitro prepare
40
+ - run: bun test .int.test
@@ -0,0 +1,64 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths-ignore:
7
+ - 'site/**'
8
+ - 'docs/**'
9
+ - 'screenshots/**'
10
+ - 'README.md'
11
+ - 'CLAUDE.md'
12
+ pull_request:
13
+ branches: [main]
14
+ paths-ignore:
15
+ - 'site/**'
16
+ - 'docs/**'
17
+ - 'screenshots/**'
18
+ - 'README.md'
19
+ - 'CLAUDE.md'
20
+
21
+ # A newer push to the same branch, or to the same pull request, makes this run
22
+ # pointless: it is cancelled rather than left to use a runner for nothing. Keyed on
23
+ # the workflow file and the ref, since the three test workflows share one name.
24
+ concurrency:
25
+ group: ${{ github.workflow_ref }}
26
+ cancel-in-progress: true
27
+
28
+ permissions:
29
+ contents: read
30
+
31
+ jobs:
32
+ unit:
33
+ name: 🧪 Unit
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v7
37
+ - uses: oven-sh/setup-bun@v2
38
+ - run: bun install --frozen-lockfile
39
+ - run: bunx nitro prepare
40
+ - run: bun tsc --noEmit
41
+ - run: bunx biome check
42
+ - run: bun test .unit.test
43
+
44
+ # Guard: the Cloud Function build on GCP runs `npm install` (strict peer deps)
45
+ # on the package.json Nitro generates in .output/server. bun is lenient about
46
+ # peer deps, so an incompatible bump passes locally and only fails at
47
+ # `terraform apply`. This reproduces that resolution here so it fails fast.
48
+ deploy-deps:
49
+ name: 📦 Prod deps (npm strict)
50
+ runs-on: ubuntu-latest
51
+ steps:
52
+ - uses: actions/checkout@v7
53
+ - uses: oven-sh/setup-bun@v2
54
+ - uses: actions/setup-node@v7
55
+ with:
56
+ node-version: 24
57
+ - run: bun install --frozen-lockfile
58
+ - run: bun run build
59
+ - name: Validate generated function deps under npm (strict peer deps)
60
+ working-directory: .output/server
61
+ # --dry-run builds the full dependency tree (so ERESOLVE peer-dep
62
+ # conflicts still surface) without the reify/write step, which avoids an
63
+ # unrelated npm arborist crash ("fsTop") on the runner.
64
+ run: npm install --dry-run --ignore-scripts
@@ -0,0 +1,67 @@
1
+ # API evolution — deprecation and forced update
2
+
3
+ The GraphQL schema is not versioned. Builds of the app stay installed for weeks after a
4
+ release, so the schema only ever grows, and what the app stops using leaves it in two steps:
5
+ first deprecated, then removed once a build without it has been on the App Store for two
6
+ weeks — and in that same commit, the builds that still ask for it are sent to update.
7
+
8
+ The design is [the deprecation routine spec](superpowers/specs/2026-09-27-deprecation-routine-design.md).
9
+
10
+ ## Moving parts
11
+
12
+ | Piece | File |
13
+ |---|---|
14
+ | Build floor and App Store URL | `server/system/app-support.ts` (`MINIMUM_SUPPORTED_IOS_BUILD`) |
15
+ | Public config route (`GET /app-config`) | `server/routes/app-config.get.ts`, whitelisted in `server/middleware/auth.ts` |
16
+ | Launch and foreground check, fail-open, skipped in Debug | `ios/{{APP_NAME}}/Shared/Services/AppSupportGate.swift` |
17
+ | Blocking screen | `ios/{{APP_NAME}}/Shared/Components/UpdateRequiredView.swift`, wired in `AuthRoot.swift` |
18
+ | What can go, and the floor to set | `scripts/deprecations.ts` (`bun run deprecations`) |
19
+ | No breaking change without deprecation | `bun run schema:check`, the Schema workflow |
20
+ | The floor never outruns the App Store | `bun scripts/deprecations.ts check-floor`, in the Deploy workflow |
21
+
22
+ The comparison key is the build number (`CFBundleVersion`), which CI sets to
23
+ `git rev-list --count HEAD`: it names a commit and only grows, nightly or App Store alike.
24
+ Never the marketing version.
25
+
26
+ ## What breaks an installed build
27
+
28
+ Removing or renaming a field, an argument or an input field; changing a type; making an
29
+ argument or an input field required; making a non-null output field nullable. Adding a field,
30
+ a query, a mutation, an optional argument or an enum value does not — the app reads an enum
31
+ value it does not know as a neutral one (`Book+GraphQL.swift`).
32
+
33
+ ## The routine
34
+
35
+ 1. **Deprecate, in the commit where the app stops asking.** The iOS operations
36
+ (`ios/**/*.graphql`) stop selecting the field or argument, and the server marks it with
37
+ `deprecationReason`, naming what replaces it. The resolver keeps working.
38
+ 2. **At every App Store release, ask what can go:** `bun run deprecations` (with
39
+ `ASC_KEY_ID`, `ASC_ISSUER_ID` and `ASC_KEY_PATH` set to the team key). For each deprecated
40
+ element it finds, in git, the first build whose operations no longer select it, and
41
+ compares it with the build on sale:
42
+ - **removable** — a build without it has been on the App Store for fourteen days, counted
43
+ from the `ios-v<version>` tag; or no build ever asked for it;
44
+ - **in grace until `<date>`** — on sale, not for fourteen days yet;
45
+ - **waiting for a release** — the App Store still sells a build that asks for it;
46
+ - **still asked by the app** — an operation at `HEAD` selects it;
47
+ - **check by hand** — an input field or an enum value, which the operations cannot show:
48
+ Swift fills inputs through variables, and enum values travel in responses.
49
+ 3. **Remove, in one commit:** the removable elements — schema, resolver, the code and tests
50
+ only they used — and `MINIMUM_SUPPORTED_IOS_BUILD` set to the floor the script proposes.
51
+ The Deploy workflow ships both together; an older build shows the update screen at its next
52
+ launch or return to the foreground.
53
+
54
+ ## Guards
55
+
56
+ - `bun run schema:check` compares `shared/schema.graphql` with `origin/main` (or
57
+ `SCHEMA_BASE`) through `graphql-inspector`. It fails on any breaking change except the
58
+ removal of a field or an argument that was deprecated — the second through
59
+ `scripts/deprecations/inspector-rule.ts`, since the built-in rule spares fields only.
60
+ - `check-floor` fails the deploy when the floor is above the build on sale: readers would be
61
+ blocked with no update to install. A floor of `1` passes without calling Apple.
62
+
63
+ ## Fail-open
64
+
65
+ If `/app-config` is unreachable or undecodable, the app stays usable. A build that slips past
66
+ the gate meets GraphQL errors until its next check — never lock readers out because the check
67
+ itself failed.
@@ -0,0 +1,21 @@
1
+ {
2
+ "firestore": {
3
+ "rules": "firestore.rules",
4
+ "indexes": "firestore.indexes.json"
5
+ },
6
+ "functions": [
7
+ {
8
+ "source": ".output/server",
9
+ "codebase": "server",
10
+ "runtime": "nodejs22",
11
+ "ignore": ["node_modules"]
12
+ }
13
+ ],
14
+ "emulators": {
15
+ "auth": { "port": 9099 },
16
+ "functions": { "port": 5001 },
17
+ "firestore": { "port": 8080 },
18
+ "ui": { "enabled": true, "port": 4000 },
19
+ "singleProjectMode": true
20
+ }
21
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "indexes": [],
3
+ "fieldOverrides": []
4
+ }
@@ -0,0 +1,11 @@
1
+ rules_version = '2';
2
+
3
+ // All Firestore access goes through Cloud Functions (Application Default
4
+ // Credentials bypass these rules). Direct client access is denied.
5
+ service cloud.firestore {
6
+ match /databases/{database}/documents {
7
+ match /{document=**} {
8
+ allow read, write: if false;
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,54 @@
1
+ locals {
2
+ apple_sign_in_configured = var.apple_services_id != "" && var.apple_key_id != "" && var.apple_private_key_path != ""
3
+ }
4
+
5
+ resource "google_identity_platform_config" "this" {
6
+ provider = google-beta
7
+ project = google_project.this.project_id
8
+ autodelete_anonymous_users = false
9
+
10
+ sign_in {
11
+ allow_duplicate_emails = false
12
+
13
+ email {
14
+ enabled = false
15
+ password_required = false
16
+ }
17
+
18
+ anonymous {
19
+ enabled = false
20
+ }
21
+ }
22
+
23
+ depends_on = [google_firebase_project.this]
24
+ }
25
+
26
+ # Apple Sign-In, configured as a built-in (default supported) provider.
27
+ # The iOS SDK calls OAuthProvider.appleCredential(...) which targets the
28
+ # provider id "apple.com", so a custom OIDC config (oidc.apple.com) would
29
+ # never be matched and produces "identity provider configuration is not
30
+ # found" at runtime.
31
+ #
32
+ # Identity Platform accepts the Apple key material as a JSON-encoded
33
+ # client_secret; it generates the OAuth JWT internally on each sign-in.
34
+ resource "google_identity_platform_default_supported_idp_config" "apple" {
35
+ # Skipped entirely when the Apple credentials are blank. The provider cannot be
36
+ # half-configured: Identity Platform rejects an empty client_id, and a resource
37
+ # created with placeholder key material would fail at the first sign-in rather
38
+ # than at apply time, which is the worse place to find out.
39
+ count = local.apple_sign_in_configured ? 1 : 0
40
+
41
+ provider = google-beta
42
+ project = google_project.this.project_id
43
+ enabled = true
44
+ idp_id = "apple.com"
45
+ client_id = var.apple_services_id
46
+
47
+ client_secret = jsonencode({
48
+ teamId = var.apple_team_id
49
+ keyId = var.apple_key_id
50
+ privateKey = file(var.apple_private_key_path)
51
+ })
52
+
53
+ depends_on = [google_identity_platform_config.this]
54
+ }
@@ -0,0 +1,6 @@
1
+ terraform {
2
+ backend "gcs" {
3
+ bucket = "{{APP_SLUG}}-polyforms-tfstate"
4
+ prefix = "{{APP_SLUG}}"
5
+ }
6
+ }
@@ -0,0 +1,63 @@
1
+ # Workload Identity Federation: lets the GitHub Actions deploy workflow
2
+ # authenticate to GCP without a long-lived service account key. The flow is:
3
+ #
4
+ # 1. GitHub Actions exchanges its OIDC token at the WIF provider below.
5
+ # 2. GCP returns a short-lived token that impersonates the deploy SA.
6
+ # 3. Terraform runs apply against this project as that SA.
7
+ #
8
+ # Outputs (deploy_sa_email + wif_provider) are surfaced through outputs.tf
9
+ # so they can be copied into GitHub secrets after a fresh bootstrap.
10
+
11
+ resource "google_service_account" "deploy" {
12
+ project = google_project.this.project_id
13
+ account_id = "{{APP_SLUG}}-deploy"
14
+ display_name = "{{APP_NAME}} GitHub Actions deploy"
15
+ }
16
+
17
+ # Project-scoped owner: the deploy workflow runs `terraform apply`, which
18
+ # touches every resource in this project (project services, IAM, Cloud Run,
19
+ # Functions, Firestore, Secret Manager, GCS, org policy). Owner is the
20
+ # simplest contract that survives future resource additions; the impact is
21
+ # bounded because the binding is on a single project, not the org.
22
+ resource "google_project_iam_member" "deploy_owner" {
23
+ project = google_project.this.project_id
24
+ role = "roles/owner"
25
+ member = "serviceAccount:${google_service_account.deploy.email}"
26
+ }
27
+
28
+ resource "google_iam_workload_identity_pool" "github" {
29
+ project = google_project.this.project_id
30
+ workload_identity_pool_id = "github"
31
+ display_name = "GitHub Actions"
32
+ description = "OIDC federation for GitHub-hosted runners"
33
+
34
+ depends_on = [google_project_service.apis]
35
+ }
36
+
37
+ resource "google_iam_workload_identity_pool_provider" "github" {
38
+ project = google_project.this.project_id
39
+ workload_identity_pool_id = google_iam_workload_identity_pool.github.workload_identity_pool_id
40
+ workload_identity_pool_provider_id = "github-actions"
41
+ display_name = "GitHub OIDC"
42
+
43
+ attribute_mapping = {
44
+ "google.subject" = "assertion.sub"
45
+ "attribute.actor" = "assertion.actor"
46
+ "attribute.repository" = "assertion.repository"
47
+ "attribute.ref" = "assertion.ref"
48
+ }
49
+
50
+ # Mandatory since 2023: scope the provider to a single repository so that
51
+ # a leaked workflow on another repo cannot impersonate the deploy SA.
52
+ attribute_condition = "assertion.repository == '${var.github_repo}'"
53
+
54
+ oidc {
55
+ issuer_uri = "https://token.actions.githubusercontent.com"
56
+ }
57
+ }
58
+
59
+ resource "google_service_account_iam_member" "deploy_wif_user" {
60
+ service_account_id = google_service_account.deploy.name
61
+ role = "roles/iam.workloadIdentityUser"
62
+ member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/${var.github_repo}"
63
+ }
@@ -0,0 +1,26 @@
1
+ resource "google_firebase_project" "this" {
2
+ provider = google-beta
3
+ project = google_project.this.project_id
4
+
5
+ depends_on = [google_project_service.apis]
6
+ }
7
+
8
+ resource "google_firestore_database" "default" {
9
+ provider = google-beta
10
+ project = google_project.this.project_id
11
+ name = "(default)"
12
+ location_id = var.firestore_location
13
+ type = "FIRESTORE_NATIVE"
14
+ concurrency_mode = "OPTIMISTIC"
15
+ app_engine_integration_mode = "DISABLED"
16
+
17
+ # A console click or a stray script cannot delete the database: protection must be
18
+ # turned off first, on purpose.
19
+ delete_protection_state = "DELETE_PROTECTION_ENABLED"
20
+
21
+ # Keeps every document version for seven days, readable and restorable to the minute,
22
+ # so a failed migration or a bug that overwrites books can be undone.
23
+ point_in_time_recovery_enablement = "POINT_IN_TIME_RECOVERY_ENABLED"
24
+
25
+ depends_on = [google_firebase_project.this]
26
+ }