@striae-org/striae 5.3.1 → 5.4.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 (100) hide show
  1. package/.env.example +3 -0
  2. package/app/components/actions/generate-pdf.ts +22 -0
  3. package/app/components/auth/auth.module.css +531 -0
  4. package/app/components/auth/mfa-enrollment.tsx +132 -79
  5. package/app/components/auth/mfa-totp-enrollment.tsx +231 -0
  6. package/app/components/auth/mfa-verification.tsx +155 -33
  7. package/app/components/{sidebar/cases/cases-modal.tsx → navbar/case-modals/all-cases-modal.tsx} +4 -4
  8. package/app/components/navbar/case-modals/archive-case-modal.tsx +9 -10
  9. package/app/components/navbar/case-modals/case-modal-shared.module.css +88 -0
  10. package/app/components/navbar/case-modals/delete-case-modal.tsx +9 -10
  11. package/app/components/navbar/case-modals/export-case-modal.tsx +9 -10
  12. package/app/components/navbar/case-modals/export-confirmations-modal.tsx +9 -10
  13. package/app/components/navbar/case-modals/open-case-modal.tsx +4 -4
  14. package/app/components/navbar/case-modals/rename-case-modal.tsx +9 -10
  15. package/app/components/navbar/navbar.tsx +1 -1
  16. package/app/components/sidebar/files/delete-files-modal.tsx +3 -3
  17. package/app/components/sidebar/files/files-modal.module.css +29 -0
  18. package/app/components/sidebar/notes/{class-details-fields.tsx → class-details/class-details-fields.tsx} +1 -1
  19. package/app/components/sidebar/notes/{class-details-modal.tsx → class-details/class-details-modal.tsx} +1 -1
  20. package/app/components/sidebar/notes/{class-details-sections.tsx → class-details/class-details-sections.tsx} +1 -1
  21. package/app/components/sidebar/notes/notes-editor-form.tsx +2 -2
  22. package/app/components/sidebar/notes/notes-editor-modal.tsx +6 -6
  23. package/app/components/sidebar/notes/notes.module.css +52 -0
  24. package/app/components/toolbar/toolbar-color-selector.tsx +8 -8
  25. package/app/components/toolbar/toolbar.module.css +181 -2
  26. package/app/components/user/delete-account.tsx +7 -7
  27. package/app/components/user/inactivity-warning.tsx +6 -6
  28. package/app/components/user/manage-profile.tsx +18 -1
  29. package/app/components/user/mfa-enrolled-factors.tsx +117 -0
  30. package/app/components/user/mfa-phone-update.tsx +8 -4
  31. package/app/components/user/mfa-totp-section.tsx +446 -0
  32. package/app/components/user/user.module.css +665 -0
  33. package/app/routes/striae/striae.tsx +1 -1
  34. package/app/services/audit/audit.service.ts +1 -1
  35. package/app/services/audit/builders/audit-event-builders-user-security.ts +4 -2
  36. package/app/services/firebase/errors.ts +2 -0
  37. package/app/utils/auth/mfa.ts +35 -1
  38. package/functions/api/image/[[path]].ts +19 -3
  39. package/package.json +16 -21
  40. package/scripts/deploy-all.sh +166 -0
  41. package/scripts/deploy-config/modules/env-utils.sh +322 -0
  42. package/scripts/deploy-config/modules/keys.sh +404 -0
  43. package/scripts/deploy-config/modules/prompt.sh +375 -0
  44. package/scripts/deploy-config/modules/scaffolding.sh +310 -0
  45. package/scripts/deploy-config/modules/validation.sh +354 -0
  46. package/scripts/deploy-config.sh +236 -0
  47. package/scripts/deploy-pages-secrets.sh +231 -0
  48. package/scripts/deploy-pages.sh +34 -0
  49. package/scripts/deploy-primershear-emails.sh +167 -0
  50. package/scripts/deploy-worker-secrets.sh +385 -0
  51. package/scripts/dev.cjs +23 -0
  52. package/scripts/enable-totp-mfa.mjs +57 -0
  53. package/scripts/install-workers.sh +87 -0
  54. package/scripts/run-eslint.cjs +43 -0
  55. package/scripts/update-compatibility-dates.cjs +124 -0
  56. package/scripts/update-markdown-versions.cjs +43 -0
  57. package/workers/audit-worker/package.json +1 -1
  58. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  59. package/workers/data-worker/package.json +1 -1
  60. package/workers/data-worker/wrangler.jsonc.example +1 -1
  61. package/workers/image-worker/package.json +1 -1
  62. package/workers/image-worker/src/image-worker.example.ts +36 -2
  63. package/workers/image-worker/wrangler.jsonc.example +1 -1
  64. package/workers/pdf-worker/package.json +1 -1
  65. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  66. package/workers/user-worker/package.json +1 -1
  67. package/workers/user-worker/wrangler.jsonc.example +1 -1
  68. package/wrangler.toml.example +1 -1
  69. package/app/components/auth/mfa-enrollment.module.css +0 -276
  70. package/app/components/auth/mfa-verification.module.css +0 -259
  71. package/app/components/navbar/case-modals/archive-case-modal.module.css +0 -34
  72. package/app/components/navbar/case-modals/delete-case-modal.module.css +0 -9
  73. package/app/components/navbar/case-modals/export-case-modal.module.css +0 -27
  74. package/app/components/navbar/case-modals/export-confirmations-modal.module.css +0 -24
  75. package/app/components/navbar/case-modals/open-case-modal.module.css +0 -82
  76. package/app/components/navbar/case-modals/rename-case-modal.module.css +0 -9
  77. package/app/components/sidebar/files/delete-files-modal.module.css +0 -26
  78. package/app/components/sidebar/notes/notes-editor-modal.module.css +0 -49
  79. package/app/components/toolbar/toolbar-color-selector.module.css +0 -171
  80. package/app/components/user/delete-account.module.css +0 -277
  81. package/app/components/user/inactivity-warning.module.css +0 -148
  82. package/app/components/user/manage-profile.module.css +0 -192
  83. package/app/routes/auth/login.module.css +0 -523
  84. package/app/routes/auth/login.tsx +0 -705
  85. /package/app/components/{sidebar → navbar}/case-import/case-import.module.css +0 -0
  86. /package/app/components/{sidebar → navbar}/case-import/case-import.tsx +0 -0
  87. /package/app/components/{sidebar → navbar}/case-import/components/CasePreviewSection.tsx +0 -0
  88. /package/app/components/{sidebar → navbar}/case-import/components/ConfirmationDialog.tsx +0 -0
  89. /package/app/components/{sidebar → navbar}/case-import/components/ConfirmationPreviewSection.tsx +0 -0
  90. /package/app/components/{sidebar → navbar}/case-import/components/ExistingCaseSection.tsx +0 -0
  91. /package/app/components/{sidebar → navbar}/case-import/components/FileSelector.tsx +0 -0
  92. /package/app/components/{sidebar → navbar}/case-import/components/ProgressSection.tsx +0 -0
  93. /package/app/components/{sidebar → navbar}/case-import/hooks/useFilePreview.ts +0 -0
  94. /package/app/components/{sidebar → navbar}/case-import/hooks/useImportExecution.ts +0 -0
  95. /package/app/components/{sidebar → navbar}/case-import/hooks/useImportState.ts +0 -0
  96. /package/app/components/{sidebar → navbar}/case-import/index.ts +0 -0
  97. /package/app/components/{sidebar → navbar}/case-import/utils/file-validation.ts +0 -0
  98. /package/app/components/{sidebar/cases/cases-modal.module.css → navbar/case-modals/all-cases-modal.module.css} +0 -0
  99. /package/app/components/sidebar/notes/{class-details-shared.ts → class-details/class-details-shared.ts} +0 -0
  100. /package/app/components/sidebar/notes/{use-class-details-state.ts → class-details/use-class-details-state.ts} +0 -0
@@ -0,0 +1,310 @@
1
+ #!/bin/bash
2
+
3
+ copy_example_configs() {
4
+ echo -e "\n${BLUE}📋 Copying example configuration files...${NC}"
5
+
6
+ # Copy app configuration files
7
+ echo -e "${YELLOW} Copying app configuration files...${NC}"
8
+
9
+ # Copy app config-example directory to config (always sync non-admin files)
10
+ if [ -d "app/config-example" ]; then
11
+ local admin_service_backup=""
12
+ local copied_config_files=0
13
+ local skipped_existing_files=0
14
+
15
+ if [ -f "app/config/admin-service.json" ]; then
16
+ admin_service_backup=$(mktemp)
17
+ cp "app/config/admin-service.json" "$admin_service_backup"
18
+ fi
19
+
20
+ if [ "$update_env" = "true" ]; then
21
+ rm -rf app/config
22
+ fi
23
+
24
+ mkdir -p app/config
25
+
26
+ while IFS= read -r source_file; do
27
+ local relative_path
28
+ local destination_file
29
+ relative_path="${source_file#app/config-example/}"
30
+ destination_file="app/config/$relative_path"
31
+
32
+ mkdir -p "$(dirname "$destination_file")"
33
+
34
+ if [ "$update_env" = "true" ] || [ ! -f "$destination_file" ]; then
35
+ cp "$source_file" "$destination_file"
36
+ copied_config_files=$((copied_config_files + 1))
37
+ else
38
+ skipped_existing_files=$((skipped_existing_files + 1))
39
+ fi
40
+ done < <(find app/config-example -type f ! -name "admin-service.json")
41
+
42
+ # Ensure example credentials are never copied from config-example.
43
+ rm -f app/config/admin-service.json
44
+
45
+ if [ -n "$admin_service_backup" ] && [ -f "$admin_service_backup" ]; then
46
+ cp "$admin_service_backup" "app/config/admin-service.json"
47
+ rm -f "$admin_service_backup"
48
+ echo -e "${GREEN} ✅ app: preserved existing admin-service.json${NC}"
49
+ else
50
+ echo -e "${YELLOW} ⚠️ app: skipped copying admin-service.json (provide your own credentials file)${NC}"
51
+ fi
52
+
53
+ if [ "$update_env" = "true" ]; then
54
+ echo -e "${GREEN} ✅ app: config directory reset from config-example (excluding admin-service.json)${NC}"
55
+ else
56
+ echo -e "${GREEN} ✅ app: synced missing files from config-example (excluding admin-service.json)${NC}"
57
+ fi
58
+
59
+ if [ "$skipped_existing_files" -gt 0 ]; then
60
+ echo -e "${YELLOW} ℹ️ app: kept $skipped_existing_files existing config file(s)${NC}"
61
+ fi
62
+
63
+ echo -e "${GREEN} ✅ app: copied $copied_config_files config file(s) from config-example${NC}"
64
+ fi
65
+
66
+ # Copy auth route template files
67
+ echo -e "${YELLOW} Copying auth route template files...${NC}"
68
+
69
+ if [ -f "app/routes/auth/login.example.tsx" ] && { [ "$update_env" = "true" ] || [ ! -f "app/routes/auth/login.tsx" ]; }; then
70
+ cp app/routes/auth/login.example.tsx app/routes/auth/login.tsx
71
+ echo -e "${GREEN} ✅ auth: login.tsx created from example${NC}"
72
+ elif [ -f "app/routes/auth/login.tsx" ]; then
73
+ echo -e "${YELLOW} ⚠️ auth: login.tsx already exists, skipping copy${NC}"
74
+ fi
75
+
76
+ if [ -f "app/routes/auth/login.module.example.css" ] && { [ "$update_env" = "true" ] || [ ! -f "app/routes/auth/login.module.css" ]; }; then
77
+ cp app/routes/auth/login.module.example.css app/routes/auth/login.module.css
78
+ echo -e "${GREEN} ✅ auth: login.module.css created from example${NC}"
79
+ elif [ -f "app/routes/auth/login.module.css" ]; then
80
+ echo -e "${YELLOW} ⚠️ auth: login.module.css already exists, skipping copy${NC}"
81
+ fi
82
+
83
+ # Navigate to each worker directory and copy the example file
84
+ echo -e "${YELLOW} Copying worker configuration files...${NC}"
85
+
86
+ cd workers/user-worker
87
+ if [ -f "wrangler.jsonc.example" ] && { [ "$update_env" = "true" ] || [ ! -f "wrangler.jsonc" ]; }; then
88
+ cp wrangler.jsonc.example wrangler.jsonc
89
+ echo -e "${GREEN} ✅ user-worker: wrangler.jsonc created from example${NC}"
90
+ elif [ -f "wrangler.jsonc" ]; then
91
+ echo -e "${YELLOW} ⚠️ user-worker: wrangler.jsonc already exists, skipping copy${NC}"
92
+ fi
93
+
94
+ cd ../data-worker
95
+ if [ -f "wrangler.jsonc.example" ] && { [ "$update_env" = "true" ] || [ ! -f "wrangler.jsonc" ]; }; then
96
+ cp wrangler.jsonc.example wrangler.jsonc
97
+ echo -e "${GREEN} ✅ data-worker: wrangler.jsonc created from example${NC}"
98
+ elif [ -f "wrangler.jsonc" ]; then
99
+ echo -e "${YELLOW} ⚠️ data-worker: wrangler.jsonc already exists, skipping copy${NC}"
100
+ fi
101
+
102
+ cd ../audit-worker
103
+ if [ -f "wrangler.jsonc.example" ] && { [ "$update_env" = "true" ] || [ ! -f "wrangler.jsonc" ]; }; then
104
+ cp wrangler.jsonc.example wrangler.jsonc
105
+ echo -e "${GREEN} ✅ audit-worker: wrangler.jsonc created from example${NC}"
106
+ elif [ -f "wrangler.jsonc" ]; then
107
+ echo -e "${YELLOW} ⚠️ audit-worker: wrangler.jsonc already exists, skipping copy${NC}"
108
+ fi
109
+
110
+ cd ../image-worker
111
+ if [ -f "wrangler.jsonc.example" ] && { [ "$update_env" = "true" ] || [ ! -f "wrangler.jsonc" ]; }; then
112
+ cp wrangler.jsonc.example wrangler.jsonc
113
+ echo -e "${GREEN} ✅ image-worker: wrangler.jsonc created from example${NC}"
114
+ elif [ -f "wrangler.jsonc" ]; then
115
+ echo -e "${YELLOW} ⚠️ image-worker: wrangler.jsonc already exists, skipping copy${NC}"
116
+ fi
117
+
118
+ cd ../pdf-worker
119
+ if [ -f "wrangler.jsonc.example" ] && { [ "$update_env" = "true" ] || [ ! -f "wrangler.jsonc" ]; }; then
120
+ cp wrangler.jsonc.example wrangler.jsonc
121
+ echo -e "${GREEN} ✅ pdf-worker: wrangler.jsonc created from example${NC}"
122
+ elif [ -f "wrangler.jsonc" ]; then
123
+ echo -e "${YELLOW} ⚠️ pdf-worker: wrangler.jsonc already exists, skipping copy${NC}"
124
+ fi
125
+
126
+ # Return to project root
127
+ cd ../..
128
+
129
+ # Copy worker source template files
130
+ echo -e "${YELLOW} Copying worker source template files...${NC}"
131
+
132
+ if [ -f "workers/user-worker/src/user-worker.example.ts" ] && { [ "$update_env" = "true" ] || [ ! -f "workers/user-worker/src/user-worker.ts" ]; }; then
133
+ cp workers/user-worker/src/user-worker.example.ts workers/user-worker/src/user-worker.ts
134
+ echo -e "${GREEN} ✅ user-worker: user-worker.ts created from example${NC}"
135
+ elif [ -f "workers/user-worker/src/user-worker.ts" ]; then
136
+ echo -e "${YELLOW} ⚠️ user-worker: user-worker.ts already exists, skipping copy${NC}"
137
+ fi
138
+
139
+ if [ -f "workers/data-worker/src/data-worker.example.ts" ] && { [ "$update_env" = "true" ] || [ ! -f "workers/data-worker/src/data-worker.ts" ]; }; then
140
+ cp workers/data-worker/src/data-worker.example.ts workers/data-worker/src/data-worker.ts
141
+ echo -e "${GREEN} ✅ data-worker: data-worker.ts created from example${NC}"
142
+ elif [ -f "workers/data-worker/src/data-worker.ts" ]; then
143
+ echo -e "${YELLOW} ⚠️ data-worker: data-worker.ts already exists, skipping copy${NC}"
144
+ fi
145
+
146
+ if [ -f "workers/audit-worker/src/audit-worker.example.ts" ] && { [ "$update_env" = "true" ] || [ ! -f "workers/audit-worker/src/audit-worker.ts" ]; }; then
147
+ cp workers/audit-worker/src/audit-worker.example.ts workers/audit-worker/src/audit-worker.ts
148
+ echo -e "${GREEN} ✅ audit-worker: audit-worker.ts created from example${NC}"
149
+ elif [ -f "workers/audit-worker/src/audit-worker.ts" ]; then
150
+ echo -e "${YELLOW} ⚠️ audit-worker: audit-worker.ts already exists, skipping copy${NC}"
151
+ fi
152
+
153
+ if [ -f "workers/image-worker/src/image-worker.example.ts" ] && { [ "$update_env" = "true" ] || [ ! -f "workers/image-worker/src/image-worker.ts" ]; }; then
154
+ cp workers/image-worker/src/image-worker.example.ts workers/image-worker/src/image-worker.ts
155
+ echo -e "${GREEN} ✅ image-worker: image-worker.ts created from example${NC}"
156
+ elif [ -f "workers/image-worker/src/image-worker.ts" ]; then
157
+ echo -e "${YELLOW} ⚠️ image-worker: image-worker.ts already exists, skipping copy${NC}"
158
+ fi
159
+
160
+ if [ -f "workers/pdf-worker/src/pdf-worker.example.ts" ] && { [ "$update_env" = "true" ] || [ ! -f "workers/pdf-worker/src/pdf-worker.ts" ]; }; then
161
+ cp workers/pdf-worker/src/pdf-worker.example.ts workers/pdf-worker/src/pdf-worker.ts
162
+ echo -e "${GREEN} ✅ pdf-worker: pdf-worker.ts created from example${NC}"
163
+ elif [ -f "workers/pdf-worker/src/pdf-worker.ts" ]; then
164
+ echo -e "${YELLOW} ⚠️ pdf-worker: pdf-worker.ts already exists, skipping copy${NC}"
165
+ fi
166
+
167
+ # Copy main wrangler.toml from example
168
+ if [ -f "wrangler.toml.example" ] && { [ "$update_env" = "true" ] || [ ! -f "wrangler.toml" ]; }; then
169
+ cp wrangler.toml.example wrangler.toml
170
+ echo -e "${GREEN} ✅ root: wrangler.toml created from example${NC}"
171
+ elif [ -f "wrangler.toml" ]; then
172
+ echo -e "${YELLOW} ⚠️ root: wrangler.toml already exists, skipping copy${NC}"
173
+ fi
174
+
175
+ echo -e "${GREEN}✅ Configuration file copying completed${NC}"
176
+ }
177
+
178
+ update_wrangler_configs() {
179
+ echo -e "\n${BLUE}🔧 Updating wrangler configuration files...${NC}"
180
+
181
+ local normalized_account_id
182
+ local escaped_account_id
183
+ local normalized_pages_custom_domain
184
+ local escaped_pages_custom_domain
185
+
186
+ normalized_account_id=$(printf '%s' "$ACCOUNT_ID" | tr -d '\r' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
187
+ ACCOUNT_ID="$normalized_account_id"
188
+ export ACCOUNT_ID
189
+ write_env_var "ACCOUNT_ID" "$ACCOUNT_ID"
190
+ escaped_account_id=$(escape_for_sed_replacement "$ACCOUNT_ID")
191
+
192
+ normalized_pages_custom_domain=$(normalize_domain_value "$PAGES_CUSTOM_DOMAIN")
193
+ PAGES_CUSTOM_DOMAIN="$normalized_pages_custom_domain"
194
+ export PAGES_CUSTOM_DOMAIN
195
+ write_env_var "PAGES_CUSTOM_DOMAIN" "$PAGES_CUSTOM_DOMAIN"
196
+ escaped_pages_custom_domain=$(escape_for_sed_replacement "$PAGES_CUSTOM_DOMAIN")
197
+
198
+ # Audit Worker
199
+ if [ -f "workers/audit-worker/wrangler.jsonc" ]; then
200
+ echo -e "${YELLOW} Updating audit-worker/wrangler.jsonc...${NC}"
201
+ sed -i "s/\"AUDIT_WORKER_NAME\"/\"$AUDIT_WORKER_NAME\"/g" workers/audit-worker/wrangler.jsonc
202
+ sed -i "s/\"ACCOUNT_ID\"/\"$escaped_account_id\"/g" workers/audit-worker/wrangler.jsonc
203
+ sed -i "s/\"AUDIT_BUCKET_NAME\"/\"$AUDIT_BUCKET_NAME\"/g" workers/audit-worker/wrangler.jsonc
204
+ echo -e "${GREEN} ✅ audit-worker configuration updated${NC}"
205
+ fi
206
+
207
+ if [ -f "workers/audit-worker/src/audit-worker.ts" ]; then
208
+ echo -e "${YELLOW} Updating audit-worker source placeholders...${NC}"
209
+ sed -i "s|'Access-Control-Allow-Origin': '[^']*'|'Access-Control-Allow-Origin': 'https://$escaped_pages_custom_domain'|g" workers/audit-worker/src/audit-worker.ts
210
+ echo -e "${GREEN} ✅ audit-worker source placeholders updated${NC}"
211
+ fi
212
+
213
+ if [ -f "workers/data-worker/wrangler.jsonc" ]; then
214
+ echo -e "${YELLOW} Updating data-worker/wrangler.jsonc...${NC}"
215
+ sed -i "s/\"DATA_WORKER_NAME\"/\"$DATA_WORKER_NAME\"/g" workers/data-worker/wrangler.jsonc
216
+ sed -i "s/\"ACCOUNT_ID\"/\"$escaped_account_id\"/g" workers/data-worker/wrangler.jsonc
217
+ sed -i "s/\"DATA_BUCKET_NAME\"/\"$DATA_BUCKET_NAME\"/g" workers/data-worker/wrangler.jsonc
218
+ echo -e "${GREEN} ✅ data-worker configuration updated${NC}"
219
+ fi
220
+
221
+ if [ -f "workers/data-worker/src/data-worker.ts" ]; then
222
+ echo -e "${YELLOW} Updating data-worker source placeholders...${NC}"
223
+ sed -i "s|'Access-Control-Allow-Origin': '[^']*'|'Access-Control-Allow-Origin': 'https://$escaped_pages_custom_domain'|g" workers/data-worker/src/data-worker.ts
224
+ echo -e "${GREEN} ✅ data-worker source placeholders updated${NC}"
225
+ fi
226
+
227
+ if [ -f "workers/image-worker/wrangler.jsonc" ]; then
228
+ echo -e "${YELLOW} Updating image-worker/wrangler.jsonc...${NC}"
229
+ sed -i "s/\"IMAGES_WORKER_NAME\"/\"$IMAGES_WORKER_NAME\"/g" workers/image-worker/wrangler.jsonc
230
+ sed -i "s/\"ACCOUNT_ID\"/\"$escaped_account_id\"/g" workers/image-worker/wrangler.jsonc
231
+ sed -i "s/\"FILES_BUCKET_NAME\"/\"$FILES_BUCKET_NAME\"/g" workers/image-worker/wrangler.jsonc
232
+ echo -e "${GREEN} ✅ image-worker configuration updated${NC}"
233
+ fi
234
+
235
+ if [ -f "workers/image-worker/src/image-worker.ts" ]; then
236
+ echo -e "${YELLOW} Updating image-worker source placeholders...${NC}"
237
+ sed -i "s|'Access-Control-Allow-Origin': '[^']*'|'Access-Control-Allow-Origin': 'https://$escaped_pages_custom_domain'|g" workers/image-worker/src/image-worker.ts
238
+ echo -e "${GREEN} ✅ image-worker source placeholders updated${NC}"
239
+ fi
240
+
241
+ if [ -f "workers/pdf-worker/wrangler.jsonc" ]; then
242
+ echo -e "${YELLOW} Updating pdf-worker/wrangler.jsonc...${NC}"
243
+ sed -i "s/\"PDF_WORKER_NAME\"/\"$PDF_WORKER_NAME\"/g" workers/pdf-worker/wrangler.jsonc
244
+ sed -i "s/\"ACCOUNT_ID\"/\"$escaped_account_id\"/g" workers/pdf-worker/wrangler.jsonc
245
+ echo -e "${GREEN} ✅ pdf-worker configuration updated${NC}"
246
+ fi
247
+
248
+ if [ -f "workers/pdf-worker/src/pdf-worker.ts" ]; then
249
+ echo -e "${YELLOW} Updating pdf-worker source placeholders...${NC}"
250
+ sed -i "s|'Access-Control-Allow-Origin': '[^']*'|'Access-Control-Allow-Origin': 'https://$escaped_pages_custom_domain'|g" workers/pdf-worker/src/pdf-worker.ts
251
+ echo -e "${GREEN} ✅ pdf-worker source placeholders updated${NC}"
252
+ fi
253
+
254
+ if [ -f "workers/user-worker/wrangler.jsonc" ]; then
255
+ echo -e "${YELLOW} Updating user-worker/wrangler.jsonc...${NC}"
256
+ sed -i "s/\"USER_WORKER_NAME\"/\"$USER_WORKER_NAME\"/g" workers/user-worker/wrangler.jsonc
257
+ sed -i "s/\"ACCOUNT_ID\"/\"$escaped_account_id\"/g" workers/user-worker/wrangler.jsonc
258
+ sed -i "s/\"KV_STORE_ID\"/\"$KV_STORE_ID\"/g" workers/user-worker/wrangler.jsonc
259
+ sed -i "s/\"DATA_BUCKET_NAME\"/\"$DATA_BUCKET_NAME\"/g" workers/user-worker/wrangler.jsonc
260
+ sed -i "s/\"FILES_BUCKET_NAME\"/\"$FILES_BUCKET_NAME\"/g" workers/user-worker/wrangler.jsonc
261
+ echo -e "${GREEN} ✅ user-worker configuration updated${NC}"
262
+ fi
263
+
264
+ if [ -f "workers/user-worker/src/user-worker.ts" ]; then
265
+ echo -e "${YELLOW} Updating user-worker source placeholders...${NC}"
266
+ sed -i "s|'Access-Control-Allow-Origin': '[^']*'|'Access-Control-Allow-Origin': 'https://$escaped_pages_custom_domain'|g" workers/user-worker/src/user-worker.ts
267
+ echo -e "${GREEN} ✅ user-worker source placeholders updated${NC}"
268
+ fi
269
+
270
+ if [ -f "wrangler.toml" ]; then
271
+ echo -e "${YELLOW} Updating wrangler.toml...${NC}"
272
+ sed -i "s/\"PAGES_PROJECT_NAME\"/\"$PAGES_PROJECT_NAME\"/g" wrangler.toml
273
+ echo -e "${GREEN} ✅ main wrangler.toml configuration updated${NC}"
274
+ fi
275
+
276
+ echo -e "${YELLOW} Updating app configuration files...${NC}"
277
+
278
+ if [ -f "app/config/config.json" ]; then
279
+ echo -e "${YELLOW} Updating app/config/config.json...${NC}"
280
+ local escaped_manifest_signing_key_id
281
+ local escaped_manifest_signing_public_key
282
+ local escaped_export_encryption_key_id
283
+ local escaped_export_encryption_public_key
284
+ escaped_manifest_signing_key_id=$(escape_for_sed_replacement "$MANIFEST_SIGNING_KEY_ID")
285
+ escaped_manifest_signing_public_key=$(escape_for_sed_replacement "$MANIFEST_SIGNING_PUBLIC_KEY")
286
+ escaped_export_encryption_key_id=$(escape_for_sed_replacement "$EXPORT_ENCRYPTION_KEY_ID")
287
+ escaped_export_encryption_public_key=$(escape_for_sed_replacement "$EXPORT_ENCRYPTION_PUBLIC_KEY")
288
+
289
+ sed -i "s|\"url\": \"[^\"]*\"|\"url\": \"https://$escaped_pages_custom_domain\"|g" app/config/config.json
290
+ sed -i "s|\"MANIFEST_SIGNING_KEY_ID\"|\"$escaped_manifest_signing_key_id\"|g" app/config/config.json
291
+ sed -i "s|\"MANIFEST_SIGNING_PUBLIC_KEY\"|\"$escaped_manifest_signing_public_key\"|g" app/config/config.json
292
+ sed -i "s|\"EXPORT_ENCRYPTION_KEY_ID\"|\"$escaped_export_encryption_key_id\"|g" app/config/config.json
293
+ sed -i "s|\"EXPORT_ENCRYPTION_PUBLIC_KEY\"|\"$escaped_export_encryption_public_key\"|g" app/config/config.json
294
+ echo -e "${GREEN} ✅ app config.json updated${NC}"
295
+ fi
296
+
297
+ if [ -f "app/config/firebase.ts" ]; then
298
+ echo -e "${YELLOW} Updating app/config/firebase.ts...${NC}"
299
+ sed -i "s|\"YOUR_FIREBASE_API_KEY\"|\"$API_KEY\"|g" app/config/firebase.ts
300
+ sed -i "s|\"YOUR_FIREBASE_AUTH_DOMAIN\"|\"$AUTH_DOMAIN\"|g" app/config/firebase.ts
301
+ sed -i "s|\"YOUR_FIREBASE_PROJECT_ID\"|\"$PROJECT_ID\"|g" app/config/firebase.ts
302
+ sed -i "s|\"YOUR_FIREBASE_STORAGE_BUCKET\"|\"$STORAGE_BUCKET\"|g" app/config/firebase.ts
303
+ sed -i "s|\"YOUR_FIREBASE_MESSAGING_SENDER_ID\"|\"$MESSAGING_SENDER_ID\"|g" app/config/firebase.ts
304
+ sed -i "s|\"YOUR_FIREBASE_APP_ID\"|\"$APP_ID\"|g" app/config/firebase.ts
305
+ sed -i "s|\"YOUR_FIREBASE_MEASUREMENT_ID\"|\"$MEASUREMENT_ID\"|g" app/config/firebase.ts
306
+ echo -e "${GREEN} ✅ app firebase.ts updated${NC}"
307
+ fi
308
+
309
+ echo -e "${GREEN}✅ All configuration files updated${NC}"
310
+ }
@@ -0,0 +1,354 @@
1
+ #!/bin/bash
2
+
3
+ validate_data_at_rest_encryption_settings() {
4
+ local enabled_normalized
5
+ enabled_normalized=$(printf '%s' "${DATA_AT_REST_ENCRYPTION_ENABLED:-false}" | tr '[:upper:]' '[:lower:]')
6
+
7
+ if [ "$enabled_normalized" != "1" ] && [ "$enabled_normalized" != "true" ] && [ "$enabled_normalized" != "yes" ] && [ "$enabled_normalized" != "on" ]; then
8
+ echo -e "${RED}❌ Error: DATA_AT_REST_ENCRYPTION_ENABLED must be true because data-at-rest encryption is mandatory${NC}"
9
+ exit 1
10
+ fi
11
+
12
+ local has_legacy_private_key=false
13
+ local has_registry_keys_json=false
14
+
15
+ if [ -n "$DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" ] && ! is_placeholder "$DATA_AT_REST_ENCRYPTION_PRIVATE_KEY"; then
16
+ has_legacy_private_key=true
17
+ fi
18
+
19
+ if [ -n "$DATA_AT_REST_ENCRYPTION_KEYS_JSON" ] && ! is_placeholder "$DATA_AT_REST_ENCRYPTION_KEYS_JSON"; then
20
+ has_registry_keys_json=true
21
+ fi
22
+
23
+ if [ "$has_legacy_private_key" != "true" ] && [ "$has_registry_keys_json" != "true" ]; then
24
+ echo -e "${RED}❌ Error: either DATA_AT_REST_ENCRYPTION_PRIVATE_KEY or DATA_AT_REST_ENCRYPTION_KEYS_JSON is required when data-at-rest encryption is enabled${NC}"
25
+ exit 1
26
+ fi
27
+
28
+ if [ -z "$DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_PUBLIC_KEY"; then
29
+ echo -e "${RED}❌ Error: DATA_AT_REST_ENCRYPTION_PUBLIC_KEY is required when data-at-rest encryption is enabled${NC}"
30
+ exit 1
31
+ fi
32
+
33
+ if [ -z "$DATA_AT_REST_ENCRYPTION_KEY_ID" ] || is_placeholder "$DATA_AT_REST_ENCRYPTION_KEY_ID"; then
34
+ echo -e "${RED}❌ Error: DATA_AT_REST_ENCRYPTION_KEY_ID is required when data-at-rest encryption is enabled${NC}"
35
+ exit 1
36
+ fi
37
+ }
38
+
39
+ validate_user_kv_encryption_settings() {
40
+ local has_legacy_private_key=false
41
+ local has_registry_keys_json=false
42
+ local write_endpoints_enabled_normalized
43
+
44
+ if [ -n "$USER_KV_ENCRYPTION_PRIVATE_KEY" ] && ! is_placeholder "$USER_KV_ENCRYPTION_PRIVATE_KEY"; then
45
+ has_legacy_private_key=true
46
+ fi
47
+
48
+ if [ -n "$USER_KV_ENCRYPTION_KEYS_JSON" ] && ! is_placeholder "$USER_KV_ENCRYPTION_KEYS_JSON"; then
49
+ has_registry_keys_json=true
50
+ fi
51
+
52
+ if [ "$has_legacy_private_key" != "true" ] && [ "$has_registry_keys_json" != "true" ]; then
53
+ echo -e "${RED}❌ Error: either USER_KV_ENCRYPTION_PRIVATE_KEY or USER_KV_ENCRYPTION_KEYS_JSON is required${NC}"
54
+ exit 1
55
+ fi
56
+
57
+ # Defaults to enabled to preserve current behavior unless explicitly set false for read-only deployments.
58
+ write_endpoints_enabled_normalized=$(printf '%s' "${USER_KV_WRITE_ENDPOINTS_ENABLED:-true}" | tr '[:upper:]' '[:lower:]')
59
+
60
+ if [ "$write_endpoints_enabled_normalized" = "1" ] || [ "$write_endpoints_enabled_normalized" = "true" ] || [ "$write_endpoints_enabled_normalized" = "yes" ] || [ "$write_endpoints_enabled_normalized" = "on" ]; then
61
+ if [ -z "$USER_KV_ENCRYPTION_PUBLIC_KEY" ] || is_placeholder "$USER_KV_ENCRYPTION_PUBLIC_KEY"; then
62
+ echo -e "${RED}❌ Error: USER_KV_ENCRYPTION_PUBLIC_KEY is required when USER_KV_WRITE_ENDPOINTS_ENABLED is true${NC}"
63
+ exit 1
64
+ fi
65
+
66
+ if [ -z "$USER_KV_ENCRYPTION_KEY_ID" ] || is_placeholder "$USER_KV_ENCRYPTION_KEY_ID"; then
67
+ echo -e "${RED}❌ Error: USER_KV_ENCRYPTION_KEY_ID is required when USER_KV_WRITE_ENDPOINTS_ENABLED is true${NC}"
68
+ exit 1
69
+ fi
70
+ fi
71
+ }
72
+
73
+ # Validate required variables
74
+ required_vars=(
75
+ # Core Cloudflare Configuration
76
+ "ACCOUNT_ID"
77
+
78
+ # Shared Authentication & Storage
79
+ "USER_DB_AUTH"
80
+ "R2_KEY_SECRET"
81
+ "IMAGES_API_TOKEN"
82
+
83
+ # Firebase Auth Configuration
84
+ "API_KEY"
85
+ "AUTH_DOMAIN"
86
+ "PROJECT_ID"
87
+ "STORAGE_BUCKET"
88
+ "MESSAGING_SENDER_ID"
89
+ "APP_ID"
90
+ "MEASUREMENT_ID"
91
+ "FIREBASE_SERVICE_ACCOUNT_EMAIL"
92
+ "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY"
93
+
94
+ # Pages Configuration
95
+ "PAGES_PROJECT_NAME"
96
+ "PAGES_CUSTOM_DOMAIN"
97
+
98
+ # Worker Names (required for config replacement)
99
+ "USER_WORKER_NAME"
100
+ "DATA_WORKER_NAME"
101
+ "AUDIT_WORKER_NAME"
102
+ "IMAGES_WORKER_NAME"
103
+ "PDF_WORKER_NAME"
104
+
105
+ # Worker Domains (required for proxy/env secrets and worker fallbacks)
106
+ "USER_WORKER_DOMAIN"
107
+ "DATA_WORKER_DOMAIN"
108
+ "AUDIT_WORKER_DOMAIN"
109
+ "IMAGES_WORKER_DOMAIN"
110
+ "PDF_WORKER_DOMAIN"
111
+
112
+ # Storage Configuration (required for config replacement)
113
+ "DATA_BUCKET_NAME"
114
+ "AUDIT_BUCKET_NAME"
115
+ "FILES_BUCKET_NAME"
116
+ "KV_STORE_ID"
117
+
118
+ # Worker-Specific Secrets (required for deployment)
119
+ "PDF_WORKER_AUTH"
120
+ "IMAGE_SIGNED_URL_SECRET"
121
+ "BROWSER_API_TOKEN"
122
+ "MANIFEST_SIGNING_PRIVATE_KEY"
123
+ "MANIFEST_SIGNING_KEY_ID"
124
+ "MANIFEST_SIGNING_PUBLIC_KEY"
125
+ "EXPORT_ENCRYPTION_PRIVATE_KEY"
126
+ "EXPORT_ENCRYPTION_KEY_ID"
127
+ "EXPORT_ENCRYPTION_PUBLIC_KEY"
128
+ )
129
+
130
+ validate_required_vars() {
131
+ echo -e "${YELLOW}🔍 Validating required environment variables...${NC}"
132
+ for var in "${required_vars[@]}"; do
133
+ if [ -z "${!var}" ] || is_placeholder "${!var}"; then
134
+ echo -e "${RED}❌ Error: $var is not set in .env file or is a placeholder${NC}"
135
+ exit 1
136
+ fi
137
+ done
138
+ echo -e "${GREEN}✅ All required variables found${NC}"
139
+ }
140
+
141
+ assert_file_exists() {
142
+ local file_path=$1
143
+
144
+ if [ ! -f "$file_path" ]; then
145
+ echo -e "${RED}❌ Error: required file is missing: $file_path${NC}"
146
+ exit 1
147
+ fi
148
+ }
149
+
150
+ assert_contains_literal() {
151
+ local file_path=$1
152
+ local literal=$2
153
+ local description=$3
154
+
155
+ if ! grep -Fq -- "$literal" "$file_path"; then
156
+ echo -e "${RED}❌ Error: ${description}${NC}"
157
+ echo -e "${YELLOW} Expected to find '$literal' in $file_path${NC}"
158
+ exit 1
159
+ fi
160
+ }
161
+
162
+ assert_no_match_in_file() {
163
+ local file_path=$1
164
+ local pattern=$2
165
+ local description=$3
166
+ local matches
167
+
168
+ matches=$(grep -En "$pattern" "$file_path" | head -n 3 || true)
169
+ if [ -n "$matches" ]; then
170
+ echo -e "${RED}❌ Error: ${description}${NC}"
171
+ echo -e "${YELLOW} First matching lines in $file_path:${NC}"
172
+ echo "$matches"
173
+ exit 1
174
+ fi
175
+ }
176
+
177
+ validate_json_file() {
178
+ local file_path=$1
179
+
180
+ if ! node -e "const fs=require('fs'); JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));" "$file_path" > /dev/null 2>&1; then
181
+ echo -e "${RED}❌ Error: invalid JSON in $file_path${NC}"
182
+ exit 1
183
+ fi
184
+ }
185
+
186
+ validate_domain_var() {
187
+ local var_name=$1
188
+ local value="${!var_name}"
189
+ local normalized
190
+
191
+ value=$(strip_carriage_returns "$value")
192
+ normalized=$(normalize_domain_value "$value")
193
+
194
+ if [ -z "$value" ] || is_placeholder "$value"; then
195
+ echo -e "${RED}❌ Error: $var_name is missing or placeholder${NC}"
196
+ exit 1
197
+ fi
198
+
199
+ if [ "$value" != "$normalized" ]; then
200
+ echo -e "${RED}❌ Error: $var_name must not include protocol, trailing slash, or surrounding whitespace${NC}"
201
+ echo -e "${YELLOW} Use '$normalized' instead${NC}"
202
+ exit 1
203
+ fi
204
+
205
+ if [[ "$value" == */* ]]; then
206
+ echo -e "${RED}❌ Error: $var_name must be a bare domain (no path segments)${NC}"
207
+ exit 1
208
+ fi
209
+ }
210
+
211
+ validate_env_value_formats() {
212
+ echo -e "${YELLOW}🔍 Validating environment value formats...${NC}"
213
+
214
+ validate_domain_var "PAGES_CUSTOM_DOMAIN"
215
+ validate_domain_var "USER_WORKER_DOMAIN"
216
+ validate_domain_var "DATA_WORKER_DOMAIN"
217
+ validate_domain_var "AUDIT_WORKER_DOMAIN"
218
+ validate_domain_var "IMAGES_WORKER_DOMAIN"
219
+ validate_domain_var "PDF_WORKER_DOMAIN"
220
+
221
+ if ! [[ "$KV_STORE_ID" =~ ^([0-9a-fA-F]{32}|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$ ]]; then
222
+ echo -e "${RED}❌ Error: KV_STORE_ID must be a 32-character hex namespace ID (or UUID format)${NC}"
223
+ exit 1
224
+ fi
225
+
226
+ if [[ "$ACCOUNT_ID" =~ [[:space:]] ]]; then
227
+ echo -e "${RED}❌ Error: ACCOUNT_ID must not contain whitespace${NC}"
228
+ exit 1
229
+ fi
230
+
231
+ echo -e "${GREEN}✅ Environment value formats look valid${NC}"
232
+ }
233
+
234
+ validate_env_file_entries() {
235
+ local var_name
236
+ local escaped_var_name
237
+ local count
238
+
239
+ echo -e "${YELLOW}🔍 Verifying required .env entries...${NC}"
240
+ for var_name in "${required_vars[@]}"; do
241
+ escaped_var_name=$(escape_for_sed_pattern "$var_name")
242
+ count=$(grep -c "^$escaped_var_name=" .env || true)
243
+
244
+ if [ "$count" -lt 1 ]; then
245
+ echo -e "${RED}❌ Error: missing .env entry for $var_name${NC}"
246
+ exit 1
247
+ fi
248
+ done
249
+ echo -e "${GREEN}✅ Required .env entries found${NC}"
250
+ }
251
+
252
+ validate_generated_configs() {
253
+ echo -e "${YELLOW}🔍 Running generated configuration checkpoint validations...${NC}"
254
+
255
+ local required_files=(
256
+ "wrangler.toml"
257
+ "app/config/config.json"
258
+ "app/config/firebase.ts"
259
+ "app/config/admin-service.json"
260
+ "app/routes/auth/login.tsx"
261
+ "app/routes/auth/login.module.css"
262
+ "workers/audit-worker/wrangler.jsonc"
263
+ "workers/data-worker/wrangler.jsonc"
264
+ "workers/image-worker/wrangler.jsonc"
265
+ "workers/pdf-worker/wrangler.jsonc"
266
+ "workers/user-worker/wrangler.jsonc"
267
+ "workers/audit-worker/src/audit-worker.ts"
268
+ "workers/data-worker/src/data-worker.ts"
269
+ "workers/image-worker/src/image-worker.ts"
270
+ "workers/pdf-worker/src/pdf-worker.ts"
271
+ "workers/user-worker/src/user-worker.ts"
272
+ )
273
+
274
+ local file_path
275
+ for file_path in "${required_files[@]}"; do
276
+ assert_file_exists "$file_path"
277
+ done
278
+
279
+ validate_json_file "app/config/config.json"
280
+ validate_json_file "app/config/admin-service.json"
281
+
282
+ assert_contains_literal "wrangler.toml" "\"$PAGES_PROJECT_NAME\"" "PAGES_PROJECT_NAME was not applied to wrangler.toml"
283
+
284
+ assert_contains_literal "workers/user-worker/wrangler.jsonc" "$USER_WORKER_NAME" "USER_WORKER_NAME was not applied"
285
+ assert_contains_literal "workers/data-worker/wrangler.jsonc" "$DATA_WORKER_NAME" "DATA_WORKER_NAME was not applied"
286
+ assert_contains_literal "workers/audit-worker/wrangler.jsonc" "$AUDIT_WORKER_NAME" "AUDIT_WORKER_NAME was not applied"
287
+ assert_contains_literal "workers/image-worker/wrangler.jsonc" "$IMAGES_WORKER_NAME" "IMAGES_WORKER_NAME was not applied"
288
+ assert_contains_literal "workers/pdf-worker/wrangler.jsonc" "$PDF_WORKER_NAME" "PDF_WORKER_NAME was not applied"
289
+
290
+ assert_contains_literal "workers/user-worker/wrangler.jsonc" "$ACCOUNT_ID" "ACCOUNT_ID missing in user worker config"
291
+ assert_contains_literal "workers/data-worker/wrangler.jsonc" "$ACCOUNT_ID" "ACCOUNT_ID missing in data worker config"
292
+ assert_contains_literal "workers/audit-worker/wrangler.jsonc" "$ACCOUNT_ID" "ACCOUNT_ID missing in audit worker config"
293
+ assert_contains_literal "workers/image-worker/wrangler.jsonc" "$ACCOUNT_ID" "ACCOUNT_ID missing in image worker config"
294
+ assert_contains_literal "workers/pdf-worker/wrangler.jsonc" "$ACCOUNT_ID" "ACCOUNT_ID missing in pdf worker config"
295
+
296
+ assert_contains_literal "workers/data-worker/wrangler.jsonc" "$DATA_BUCKET_NAME" "DATA_BUCKET_NAME missing in data worker config"
297
+ assert_contains_literal "workers/audit-worker/wrangler.jsonc" "$AUDIT_BUCKET_NAME" "AUDIT_BUCKET_NAME missing in audit worker config"
298
+ assert_contains_literal "workers/image-worker/wrangler.jsonc" "$FILES_BUCKET_NAME" "FILES_BUCKET_NAME missing in image worker config"
299
+ assert_contains_literal "workers/user-worker/wrangler.jsonc" "$KV_STORE_ID" "KV_STORE_ID missing in user worker config"
300
+ assert_contains_literal "workers/user-worker/wrangler.jsonc" "$DATA_BUCKET_NAME" "DATA_BUCKET_NAME missing in user worker config"
301
+ assert_contains_literal "workers/user-worker/wrangler.jsonc" "$FILES_BUCKET_NAME" "FILES_BUCKET_NAME missing in user worker config"
302
+
303
+ assert_contains_literal "app/config/config.json" "https://$PAGES_CUSTOM_DOMAIN" "PAGES_CUSTOM_DOMAIN missing in app/config/config.json"
304
+ assert_contains_literal "app/config/config.json" "$EXPORT_ENCRYPTION_KEY_ID" "EXPORT_ENCRYPTION_KEY_ID missing in app/config/config.json"
305
+ assert_contains_literal "app/config/config.json" "\"export_encryption_public_key\":" "export_encryption_public_key missing in app/config/config.json"
306
+
307
+ assert_contains_literal "app/config/firebase.ts" "$API_KEY" "API_KEY missing in app/config/firebase.ts"
308
+ assert_contains_literal "app/config/firebase.ts" "$AUTH_DOMAIN" "AUTH_DOMAIN missing in app/config/firebase.ts"
309
+ assert_contains_literal "app/config/firebase.ts" "$PROJECT_ID" "PROJECT_ID missing in app/config/firebase.ts"
310
+ assert_contains_literal "app/config/firebase.ts" "$STORAGE_BUCKET" "STORAGE_BUCKET missing in app/config/firebase.ts"
311
+ assert_contains_literal "app/config/firebase.ts" "$MESSAGING_SENDER_ID" "MESSAGING_SENDER_ID missing in app/config/firebase.ts"
312
+ assert_contains_literal "app/config/firebase.ts" "$APP_ID" "APP_ID missing in app/config/firebase.ts"
313
+ assert_contains_literal "app/config/firebase.ts" "$MEASUREMENT_ID" "MEASUREMENT_ID missing in app/config/firebase.ts"
314
+
315
+ assert_contains_literal "workers/audit-worker/src/audit-worker.ts" "https://$PAGES_CUSTOM_DOMAIN" "PAGES_CUSTOM_DOMAIN missing in audit-worker source"
316
+ assert_contains_literal "workers/data-worker/src/data-worker.ts" "https://$PAGES_CUSTOM_DOMAIN" "PAGES_CUSTOM_DOMAIN missing in data-worker source"
317
+ assert_contains_literal "workers/image-worker/src/image-worker.ts" "https://$PAGES_CUSTOM_DOMAIN" "PAGES_CUSTOM_DOMAIN missing in image-worker source"
318
+ assert_contains_literal "workers/pdf-worker/src/pdf-worker.ts" "https://$PAGES_CUSTOM_DOMAIN" "PAGES_CUSTOM_DOMAIN missing in pdf-worker source"
319
+ assert_contains_literal "workers/user-worker/src/user-worker.ts" "https://$PAGES_CUSTOM_DOMAIN" "PAGES_CUSTOM_DOMAIN missing in user-worker source"
320
+
321
+ local placeholder_pattern
322
+ placeholder_pattern="(\"(ACCOUNT_ID|PAGES_PROJECT_NAME|PAGES_CUSTOM_DOMAIN|USER_WORKER_NAME|DATA_WORKER_NAME|AUDIT_WORKER_NAME|IMAGES_WORKER_NAME|PDF_WORKER_NAME|USER_WORKER_DOMAIN|DATA_WORKER_DOMAIN|AUDIT_WORKER_DOMAIN|IMAGES_WORKER_DOMAIN|PDF_WORKER_DOMAIN|DATA_BUCKET_NAME|AUDIT_BUCKET_NAME|FILES_BUCKET_NAME|KV_STORE_ID|MANIFEST_SIGNING_KEY_ID|MANIFEST_SIGNING_PUBLIC_KEY|EXPORT_ENCRYPTION_KEY_ID|EXPORT_ENCRYPTION_PUBLIC_KEY|YOUR_FIREBASE_API_KEY|YOUR_FIREBASE_AUTH_DOMAIN|YOUR_FIREBASE_PROJECT_ID|YOUR_FIREBASE_STORAGE_BUCKET|YOUR_FIREBASE_MESSAGING_SENDER_ID|YOUR_FIREBASE_APP_ID|YOUR_FIREBASE_MEASUREMENT_ID)\"|'(PAGES_CUSTOM_DOMAIN|DATA_WORKER_DOMAIN|AUDIT_WORKER_DOMAIN|IMAGES_WORKER_DOMAIN)')"
323
+
324
+ local files_to_scan=(
325
+ "wrangler.toml"
326
+ "workers/audit-worker/wrangler.jsonc"
327
+ "workers/data-worker/wrangler.jsonc"
328
+ "workers/image-worker/wrangler.jsonc"
329
+ "workers/pdf-worker/wrangler.jsonc"
330
+ "workers/user-worker/wrangler.jsonc"
331
+ "workers/audit-worker/src/audit-worker.ts"
332
+ "workers/data-worker/src/data-worker.ts"
333
+ "workers/image-worker/src/image-worker.ts"
334
+ "workers/pdf-worker/src/pdf-worker.ts"
335
+ "workers/user-worker/src/user-worker.ts"
336
+ "app/config/config.json"
337
+ "app/config/firebase.ts"
338
+ )
339
+
340
+ for file_path in "${files_to_scan[@]}"; do
341
+ assert_no_match_in_file "$file_path" "$placeholder_pattern" "Unresolved placeholder token found after config update"
342
+ done
343
+
344
+ echo -e "${GREEN}✅ Generated configuration checkpoint validation passed${NC}"
345
+ }
346
+
347
+ run_validation_checkpoint() {
348
+ validate_required_vars
349
+ validate_env_value_formats
350
+ validate_env_file_entries
351
+ validate_data_at_rest_encryption_settings
352
+ validate_user_kv_encryption_settings
353
+ validate_generated_configs
354
+ }