@striae-org/striae 4.0.0 → 4.0.1

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.
package/README.md CHANGED
@@ -6,7 +6,7 @@ This npm package publishes the Striae application source and deployment scaffold
6
6
 
7
7
  ## Live Project
8
8
 
9
- - Application: [https://www.striae.org](https://www.striae.org)
9
+ - Application: [https://striae.app](https://striae.app)
10
10
  - Source repository: [https://github.com/striae-org/striae](https://github.com/striae-org/striae)
11
11
  - Releases: [https://github.com/striae-org/striae/releases](https://github.com/striae-org/striae/releases)
12
12
  - Security policy: [https://github.com/striae-org/striae/security/policy](https://github.com/striae-org/striae/security/policy)
@@ -975,7 +975,7 @@ forensic procedures and maintain proper documentation.`;
975
975
  const footer = `
976
976
 
977
977
  Generated by Striae - A Firearms Examiner's Comparison Companion
978
- https://www.striae.org`;
978
+ https://striae.app`;
979
979
 
980
980
  return protectForensicData ? baseContent + forensicAddition + footer : baseContent + footer;
981
981
  }
@@ -244,8 +244,9 @@ export const getImageUrl = async (user: User, fileData: FileData, caseNumber: st
244
244
  try {
245
245
  const { accountHash } = await getImageConfig();
246
246
  const imageDeliveryUrl = `https://imagedelivery.net/${accountHash}/${fileData.id}/${DEFAULT_VARIANT}`;
247
+ const encodedImageDeliveryUrl = encodeURIComponent(imageDeliveryUrl);
247
248
 
248
- const workerResponse = await fetchImageApi(user, `/${imageDeliveryUrl}`, {
249
+ const workerResponse = await fetchImageApi(user, `/${encodedImageDeliveryUrl}`, {
249
250
  method: 'GET',
250
251
  headers: {
251
252
  'Accept': 'text/plain'
@@ -283,7 +283,7 @@ export const EmailActionHandler = ({ mode, oobCode, continueUrl, lang }: EmailAc
283
283
  <Link
284
284
  viewTransition
285
285
  prefetch="intent"
286
- to="https://striae.org"
286
+ to="https://striae.app"
287
287
  className={styles.logoLink}
288
288
  >
289
289
  <div className={styles.logo} />
@@ -78,7 +78,7 @@ export const EmailVerification = ({
78
78
  <Link
79
79
  viewTransition
80
80
  prefetch="intent"
81
- to="https://striae.org"
81
+ to="https://striae.app"
82
82
  className={styles.logoLink}>
83
83
  <div className={styles.logo} />
84
84
  </Link>
@@ -29,7 +29,7 @@ import { evaluatePasswordPolicy } from '~/utils/password-policy';
29
29
  import { buildActionCodeSettings } from '~/utils/auth-action-settings';
30
30
  import { userHasMFA } from '~/utils/mfa';
31
31
 
32
- const APP_CANONICAL_ORIGIN = 'https://app.striae.org';
32
+ const APP_CANONICAL_ORIGIN = 'https://striae.app';
33
33
  const SOCIAL_IMAGE_PATH = '/social-image.png';
34
34
  const SOCIAL_IMAGE_ALT = 'Striae forensic annotation and comparison workspace';
35
35
  const LOGIN_PATH_ALIASES = new Set(['/auth', '/auth/', '/auth/login', '/auth/login/']);
@@ -51,8 +51,8 @@ const getCanonicalPath = (pathname: string): string => {
51
51
  const getAuthMetaContent = (mode: string | null, hasActionCode: boolean): AuthMetaContent => {
52
52
  if (!mode && !hasActionCode) {
53
53
  return {
54
- title: 'Striae | Secure Login for Firearms Examiners',
55
- description: 'Sign in to Striae to access your forensic annotation workspace, case files, and comparison tools.',
54
+ title: 'Striae: A Firearms Examiner\'s Comparison Companion',
55
+ description: 'Sign in to Striae to access your comparison annotation workspace, case files, and review tools.',
56
56
  robots: 'index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1',
57
57
  };
58
58
  }
@@ -582,7 +582,7 @@ export const Login = () => {
582
582
  <Link
583
583
  viewTransition
584
584
  prefetch="intent"
585
- to="https://striae.org"
585
+ to="https://striae.app"
586
586
  className={styles.logoLink}>
587
587
  <div className={styles.logo} />
588
588
  </Link>
@@ -120,7 +120,7 @@ export const PasswordReset = ({ isModal, onBack }: PasswordResetProps) => {
120
120
  <Link
121
121
  viewTransition
122
122
  prefetch="intent"
123
- to="https://striae.org"
123
+ to="https://striae.app"
124
124
  >
125
125
  <div className={styles.logo} />
126
126
  </Link>
@@ -37,7 +37,23 @@ function extractProxyPath(url: URL): string | null {
37
37
  }
38
38
 
39
39
  const remainder = url.pathname.slice(routePrefix.length);
40
- return remainder.length > 0 ? remainder : '/';
40
+ if (remainder.length === 0) {
41
+ return '/';
42
+ }
43
+
44
+ const normalizedRemainder = remainder.startsWith('/') ? remainder : `/${remainder}`;
45
+ const encodedPath = normalizedRemainder.slice(1);
46
+
47
+ try {
48
+ const decodedPath = decodeURIComponent(encodedPath);
49
+ if (decodedPath.length > 0) {
50
+ return decodedPath.startsWith('/') ? decodedPath : `/${decodedPath}`;
51
+ }
52
+ } catch {
53
+ // Keep legacy behavior for non-encoded paths.
54
+ }
55
+
56
+ return normalizedRemainder;
41
57
  }
42
58
 
43
59
  function resolveImageWorkerToken(env: Env): string {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@striae-org/striae",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "private": false,
5
5
  "description": "Striae is a specialized, cloud-native platform designed to streamline forensic firearms identification by providing an intuitive environment for digital comparison image annotation, authenticated confirmations, and automated report generation.",
6
6
  "license": "Apache-2.0",
7
- "homepage": "https://www.striae.org",
7
+ "homepage": "https://striae.app",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/striae-org/striae.git"
@@ -1,7 +1,6 @@
1
- Contact: mailto:info@striae.org
1
+ Contact: mailto:security@striae.org
2
2
  Contact: https://github.com/striae-org/striae/security/advisories/new
3
3
  Expires: 2035-08-15T00:00:00.000Z
4
- Encryption: https://www.striae.org/.well-known/publickey.info@striae.org.asc
5
4
  Preferred-Languages: en
6
- Canonical: https://www.striae.org/.well-known/security.txt
7
- Policy: https://www.striae.org/security
5
+ Canonical: https://striae.app/.well-known/security.txt
6
+ Policy: https://striae.org/security
@@ -181,6 +181,40 @@ normalize_domain_value() {
181
181
  printf '%s' "$domain"
182
182
  }
183
183
 
184
+ normalize_worker_label_value() {
185
+ local label="$1"
186
+
187
+ label=$(normalize_domain_value "$label")
188
+ label="${label#.}"
189
+ label="${label%.}"
190
+ label=$(printf '%s' "$label" | tr '[:upper:]' '[:lower:]')
191
+
192
+ printf '%s' "$label"
193
+ }
194
+
195
+ normalize_worker_subdomain_value() {
196
+ local subdomain="$1"
197
+
198
+ subdomain=$(normalize_domain_value "$subdomain")
199
+ subdomain="${subdomain#.}"
200
+ subdomain="${subdomain%.}"
201
+ subdomain=$(printf '%s' "$subdomain" | tr '[:upper:]' '[:lower:]')
202
+
203
+ printf '%s' "$subdomain"
204
+ }
205
+
206
+ is_valid_worker_label() {
207
+ local label="$1"
208
+
209
+ [[ "$label" =~ ^[a-z0-9-]+$ ]]
210
+ }
211
+
212
+ is_valid_worker_subdomain() {
213
+ local subdomain="$1"
214
+
215
+ [[ "$subdomain" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$ ]]
216
+ }
217
+
184
218
  strip_carriage_returns() {
185
219
  printf '%s' "$1" | tr -d '\r'
186
220
  }
@@ -245,54 +279,53 @@ generate_worker_subdomain_label() {
245
279
  node -e "const { randomInt } = require('crypto'); const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'; let value = ''; for (let index = 0; index < 10; index += 1) { value += alphabet[randomInt(alphabet.length)]; } process.stdout.write(value);" 2>/dev/null
246
280
  }
247
281
 
248
- worker_name_var_for_domain_var() {
249
- case "$1" in
250
- KEYS_WORKER_DOMAIN)
251
- printf '%s' "KEYS_WORKER_NAME"
252
- ;;
253
- USER_WORKER_DOMAIN)
254
- printf '%s' "USER_WORKER_NAME"
255
- ;;
256
- DATA_WORKER_DOMAIN)
257
- printf '%s' "DATA_WORKER_NAME"
258
- ;;
259
- AUDIT_WORKER_DOMAIN)
260
- printf '%s' "AUDIT_WORKER_NAME"
261
- ;;
262
- IMAGES_WORKER_DOMAIN)
263
- printf '%s' "IMAGES_WORKER_NAME"
264
- ;;
265
- PDF_WORKER_DOMAIN)
266
- printf '%s' "PDF_WORKER_NAME"
267
- ;;
268
- *)
269
- printf '%s' ""
270
- ;;
271
- esac
272
- }
273
-
274
282
  compose_worker_domain() {
275
283
  local worker_name=$1
276
284
  local worker_subdomain=$2
277
285
 
278
- worker_name=$(normalize_domain_value "$worker_name")
279
- worker_subdomain=$(normalize_domain_value "$worker_subdomain")
280
- worker_name="${worker_name#.}"
281
- worker_name="${worker_name%.}"
282
- worker_subdomain="${worker_subdomain#.}"
283
- worker_subdomain="${worker_subdomain%.}"
286
+ worker_name=$(normalize_worker_label_value "$worker_name")
287
+ worker_subdomain=$(normalize_worker_subdomain_value "$worker_subdomain")
284
288
 
285
289
  if [ -z "$worker_name" ] || [ -z "$worker_subdomain" ]; then
286
290
  return 1
287
291
  fi
288
292
 
289
- if [[ "$worker_name" == *.* ]] || [[ "$worker_name" == */* ]] || [[ "$worker_subdomain" == */* ]]; then
293
+ if ! is_valid_worker_label "$worker_name" || ! is_valid_worker_subdomain "$worker_subdomain"; then
290
294
  return 1
291
295
  fi
292
296
 
293
297
  printf '%s.%s' "$worker_name" "$worker_subdomain"
294
298
  }
295
299
 
300
+ infer_worker_subdomain_from_domain() {
301
+ local worker_name=$1
302
+ local worker_domain=$2
303
+ local worker_subdomain=""
304
+
305
+ worker_name=$(normalize_worker_label_value "$worker_name")
306
+ worker_domain=$(normalize_domain_value "$worker_domain")
307
+ worker_domain=$(printf '%s' "$worker_domain" | tr '[:upper:]' '[:lower:]')
308
+
309
+ if [ -z "$worker_name" ] || [ -z "$worker_domain" ] || is_placeholder "$worker_name" || is_placeholder "$worker_domain"; then
310
+ printf '%s' ""
311
+ return 0
312
+ fi
313
+
314
+ case "$worker_domain" in
315
+ "$worker_name".*)
316
+ worker_subdomain="${worker_domain#${worker_name}.}"
317
+ worker_subdomain=$(normalize_worker_subdomain_value "$worker_subdomain")
318
+
319
+ if is_valid_worker_subdomain "$worker_subdomain"; then
320
+ printf '%s' "$worker_subdomain"
321
+ return 0
322
+ fi
323
+ ;;
324
+ esac
325
+
326
+ printf '%s' ""
327
+ }
328
+
296
329
  write_env_var() {
297
330
  local var_name=$1
298
331
  local var_value=$2
@@ -975,7 +1008,7 @@ prompt_for_secrets() {
975
1008
 
976
1009
  current_value=$(strip_carriage_returns "$current_value")
977
1010
 
978
- if [ "$var_name" = "PAGES_CUSTOM_DOMAIN" ] || [[ "$var_name" == *_WORKER_DOMAIN ]]; then
1011
+ if [ "$var_name" = "PAGES_CUSTOM_DOMAIN" ]; then
979
1012
  current_value=$(resolve_existing_domain_value "$var_name" "$current_value")
980
1013
  fi
981
1014
 
@@ -1039,107 +1072,6 @@ prompt_for_secrets() {
1039
1072
  done
1040
1073
  fi
1041
1074
  fi
1042
- elif [[ "$var_name" == *_WORKER_DOMAIN ]]; then
1043
- local worker_name_var
1044
- local worker_name_current=""
1045
- local worker_name_input=""
1046
- local worker_subdomain_input=""
1047
- local inferred_subdomain=""
1048
- local domain_choice=""
1049
- local composed_domain=""
1050
-
1051
- worker_name_var=$(worker_name_var_for_domain_var "$var_name")
1052
- worker_name_current=$(strip_carriage_returns "${!worker_name_var}")
1053
-
1054
- if [ -n "$worker_name_current" ] && ! is_placeholder "$worker_name_current" && [ -n "$current_value" ] && ! is_placeholder "$current_value"; then
1055
- case "$current_value" in
1056
- "$worker_name_current".*)
1057
- inferred_subdomain="${current_value#${worker_name_current}.}"
1058
- ;;
1059
- esac
1060
- fi
1061
-
1062
- echo -e "${BLUE}$var_name${NC}"
1063
- echo -e "${YELLOW}$description${NC}"
1064
-
1065
- while true; do
1066
- if [ "$update_env" != "true" ] && [ -n "$current_value" ] && ! is_placeholder "$current_value"; then
1067
- echo -e "${GREEN}Current value: $current_value${NC}"
1068
- read -p "Press Enter to keep current, or type 'y' to rebuild from worker-name and worker-subdomain: " domain_choice
1069
- domain_choice=$(strip_carriage_returns "$domain_choice")
1070
-
1071
- if [ -z "$domain_choice" ]; then
1072
- new_value=""
1073
- break
1074
- fi
1075
-
1076
- if [ "$domain_choice" != "y" ] && [ "$domain_choice" != "Y" ]; then
1077
- echo -e "${RED}❌ Please press Enter to keep current or type 'y' to rebuild.${NC}"
1078
- continue
1079
- fi
1080
- fi
1081
-
1082
- if [ -n "$worker_name_current" ] && ! is_placeholder "$worker_name_current"; then
1083
- read -p "Prompt: worker-name [$worker_name_current]: " worker_name_input
1084
- worker_name_input=$(strip_carriage_returns "$worker_name_input")
1085
- if [ -z "$worker_name_input" ]; then
1086
- worker_name_input="$worker_name_current"
1087
- fi
1088
- else
1089
- read -p "Prompt: worker-name: " worker_name_input
1090
- worker_name_input=$(strip_carriage_returns "$worker_name_input")
1091
- fi
1092
-
1093
- if [ -z "$worker_name_input" ] || is_placeholder "$worker_name_input"; then
1094
- echo -e "${RED}❌ worker-name is required and cannot be a placeholder.${NC}"
1095
- continue
1096
- fi
1097
-
1098
- worker_name_input=$(normalize_domain_value "$worker_name_input")
1099
- worker_name_input="${worker_name_input#.}"
1100
- worker_name_input="${worker_name_input%.}"
1101
-
1102
- if [[ "$worker_name_input" == *.* ]] || [[ "$worker_name_input" == */* ]]; then
1103
- echo -e "${RED}❌ worker-name must be a single hostname label (for example: striae-dev-data).${NC}"
1104
- continue
1105
- fi
1106
-
1107
- if [ -n "$inferred_subdomain" ]; then
1108
- read -p "Prompt: worker-subdomain [$inferred_subdomain]: " worker_subdomain_input
1109
- worker_subdomain_input=$(strip_carriage_returns "$worker_subdomain_input")
1110
- if [ -z "$worker_subdomain_input" ]; then
1111
- worker_subdomain_input="$inferred_subdomain"
1112
- fi
1113
- else
1114
- read -p "Prompt: worker-subdomain: " worker_subdomain_input
1115
- worker_subdomain_input=$(strip_carriage_returns "$worker_subdomain_input")
1116
- fi
1117
-
1118
- if [ -z "$worker_subdomain_input" ] || is_placeholder "$worker_subdomain_input"; then
1119
- echo -e "${RED}❌ worker-subdomain is required and cannot be a placeholder.${NC}"
1120
- continue
1121
- fi
1122
-
1123
- worker_subdomain_input=$(normalize_domain_value "$worker_subdomain_input")
1124
- worker_subdomain_input="${worker_subdomain_input#.}"
1125
- worker_subdomain_input="${worker_subdomain_input%.}"
1126
-
1127
- composed_domain=$(compose_worker_domain "$worker_name_input" "$worker_subdomain_input" || echo "")
1128
- if [ -z "$composed_domain" ]; then
1129
- echo -e "${RED}❌ Invalid worker-name/worker-subdomain combination.${NC}"
1130
- continue
1131
- fi
1132
-
1133
- if [ -n "$worker_name_var" ]; then
1134
- write_env_var "$worker_name_var" "$worker_name_input"
1135
- export "$worker_name_var=$worker_name_input"
1136
- worker_name_current="$worker_name_input"
1137
- fi
1138
-
1139
- new_value="$composed_domain"
1140
- echo -e "${GREEN}Resulting worker domain: $new_value${NC}"
1141
- break
1142
- done
1143
1075
  else
1144
1076
  # Normal prompt for other variables
1145
1077
  echo -e "${BLUE}$var_name${NC}"
@@ -1175,12 +1107,22 @@ prompt_for_secrets() {
1175
1107
  continue
1176
1108
  fi
1177
1109
 
1110
+ if [[ "$var_name" == *_WORKER_NAME ]]; then
1111
+ new_value=$(normalize_worker_label_value "$new_value")
1112
+
1113
+ if [ -z "$new_value" ] || ! is_valid_worker_label "$new_value"; then
1114
+ echo -e "${RED}❌ $var_name must use only lowercase letters, numbers, and dashes.${NC}"
1115
+ new_value=""
1116
+ continue
1117
+ fi
1118
+ fi
1119
+
1178
1120
  break
1179
1121
  done
1180
1122
  fi
1181
1123
 
1182
1124
  if [ -n "$new_value" ]; then
1183
- if [ "$var_name" = "PAGES_CUSTOM_DOMAIN" ] || [[ "$var_name" == *_WORKER_DOMAIN ]]; then
1125
+ if [ "$var_name" = "PAGES_CUSTOM_DOMAIN" ]; then
1184
1126
  new_value=$(normalize_domain_value "$new_value")
1185
1127
  fi
1186
1128
 
@@ -1190,6 +1132,10 @@ prompt_for_secrets() {
1190
1132
  export "$var_name=$new_value"
1191
1133
  echo -e "${GREEN}✅ $var_name updated${NC}"
1192
1134
  elif [ -n "$current_value" ]; then
1135
+ if [[ "$var_name" == *_WORKER_NAME ]]; then
1136
+ current_value=$(normalize_worker_label_value "$current_value")
1137
+ fi
1138
+
1193
1139
  # Keep values aligned with .env.example ordering and remove stale duplicates.
1194
1140
  write_env_var "$var_name" "$current_value"
1195
1141
  export "$var_name=$current_value"
@@ -1197,6 +1143,31 @@ prompt_for_secrets() {
1197
1143
  fi
1198
1144
  echo ""
1199
1145
  }
1146
+
1147
+ set_worker_domain_from_shared_subdomain() {
1148
+ local worker_name_var=$1
1149
+ local worker_domain_var=$2
1150
+ local worker_name_value="${!worker_name_var}"
1151
+ local composed_domain=""
1152
+
1153
+ worker_name_value=$(normalize_worker_label_value "$worker_name_value")
1154
+
1155
+ if [ -z "$worker_name_value" ] || ! is_valid_worker_label "$worker_name_value"; then
1156
+ echo -e "${RED}❌ $worker_name_var must use only lowercase letters, numbers, and dashes.${NC}"
1157
+ exit 1
1158
+ fi
1159
+
1160
+ composed_domain=$(compose_worker_domain "$worker_name_value" "$shared_worker_subdomain" || echo "")
1161
+
1162
+ if [ -z "$composed_domain" ]; then
1163
+ echo -e "${RED}❌ Could not build $worker_domain_var from $worker_name_var and shared worker-subdomain.${NC}"
1164
+ exit 1
1165
+ fi
1166
+
1167
+ write_env_var "$worker_domain_var" "$composed_domain"
1168
+ export "$worker_domain_var=$composed_domain"
1169
+ echo -e "${GREEN}✅ $worker_domain_var set to $composed_domain${NC}"
1170
+ }
1200
1171
 
1201
1172
  echo -e "${BLUE}📊 CLOUDFLARE CORE CONFIGURATION${NC}"
1202
1173
  echo "=================================="
@@ -1225,18 +1196,81 @@ prompt_for_secrets() {
1225
1196
 
1226
1197
  echo -e "${BLUE}🔑 WORKER NAMES & DOMAINS${NC}"
1227
1198
  echo "========================="
1199
+ echo -e "${YELLOW}Worker names are lowercased automatically and must use only letters, numbers, and dashes.${NC}"
1200
+ echo -e "${YELLOW}Enter one shared worker-subdomain as a hostname (for example: team-name.workers.dev).${NC}"
1201
+ echo -e "${YELLOW}Each worker domain is generated as {worker-name}.{worker-subdomain}.${NC}"
1202
+
1203
+ local shared_worker_subdomain=""
1204
+ local shared_worker_subdomain_default=""
1205
+ local shared_worker_subdomain_input=""
1206
+
1207
+ shared_worker_subdomain_default=$(infer_worker_subdomain_from_domain "$KEYS_WORKER_NAME" "$KEYS_WORKER_DOMAIN")
1208
+ if [ -z "$shared_worker_subdomain_default" ]; then
1209
+ shared_worker_subdomain_default=$(infer_worker_subdomain_from_domain "$USER_WORKER_NAME" "$USER_WORKER_DOMAIN")
1210
+ fi
1211
+ if [ -z "$shared_worker_subdomain_default" ]; then
1212
+ shared_worker_subdomain_default=$(infer_worker_subdomain_from_domain "$DATA_WORKER_NAME" "$DATA_WORKER_DOMAIN")
1213
+ fi
1214
+ if [ -z "$shared_worker_subdomain_default" ]; then
1215
+ shared_worker_subdomain_default=$(infer_worker_subdomain_from_domain "$AUDIT_WORKER_NAME" "$AUDIT_WORKER_DOMAIN")
1216
+ fi
1217
+ if [ -z "$shared_worker_subdomain_default" ]; then
1218
+ shared_worker_subdomain_default=$(infer_worker_subdomain_from_domain "$IMAGES_WORKER_NAME" "$IMAGES_WORKER_DOMAIN")
1219
+ fi
1220
+ if [ -z "$shared_worker_subdomain_default" ]; then
1221
+ shared_worker_subdomain_default=$(infer_worker_subdomain_from_domain "$PDF_WORKER_NAME" "$PDF_WORKER_DOMAIN")
1222
+ fi
1223
+
1224
+ while true; do
1225
+ echo -e "${BLUE}WORKER_SUBDOMAIN${NC}"
1226
+
1227
+ if [ "$update_env" != "true" ] && [ -n "$shared_worker_subdomain_default" ] && ! is_placeholder "$shared_worker_subdomain_default"; then
1228
+ echo -e "${GREEN}Current value: $shared_worker_subdomain_default${NC}"
1229
+ read -p "New value (or press Enter to keep current): " shared_worker_subdomain_input
1230
+ shared_worker_subdomain_input=$(strip_carriage_returns "$shared_worker_subdomain_input")
1231
+
1232
+ if [ -z "$shared_worker_subdomain_input" ]; then
1233
+ shared_worker_subdomain="$shared_worker_subdomain_default"
1234
+ else
1235
+ shared_worker_subdomain="$shared_worker_subdomain_input"
1236
+ fi
1237
+ else
1238
+ read -p "Enter shared worker-subdomain (e.g., team-name.workers.dev): " shared_worker_subdomain_input
1239
+ shared_worker_subdomain_input=$(strip_carriage_returns "$shared_worker_subdomain_input")
1240
+ shared_worker_subdomain="$shared_worker_subdomain_input"
1241
+ fi
1242
+
1243
+ if [ -z "$shared_worker_subdomain" ] || is_placeholder "$shared_worker_subdomain"; then
1244
+ echo -e "${RED}❌ shared worker-subdomain is required and cannot be a placeholder.${NC}"
1245
+ continue
1246
+ fi
1247
+
1248
+ shared_worker_subdomain=$(normalize_worker_subdomain_value "$shared_worker_subdomain")
1249
+
1250
+ if [ -z "$shared_worker_subdomain" ] || ! is_valid_worker_subdomain "$shared_worker_subdomain"; then
1251
+ echo -e "${RED}❌ shared worker-subdomain must be a valid hostname like team-name.workers.dev (letters, numbers, dashes, and dots).${NC}"
1252
+ continue
1253
+ fi
1254
+
1255
+ echo -e "${GREEN}✅ Shared worker-subdomain set to: $shared_worker_subdomain${NC}"
1256
+ echo ""
1257
+ break
1258
+ done
1259
+
1228
1260
  prompt_for_var "KEYS_WORKER_NAME" "Keys worker name"
1229
- prompt_for_var "KEYS_WORKER_DOMAIN" "Keys worker domain (format: {worker-name}.{worker-subdomain})"
1230
1261
  prompt_for_var "USER_WORKER_NAME" "User worker name"
1231
- prompt_for_var "USER_WORKER_DOMAIN" "User worker domain (format: {worker-name}.{worker-subdomain})"
1232
1262
  prompt_for_var "DATA_WORKER_NAME" "Data worker name"
1233
- prompt_for_var "DATA_WORKER_DOMAIN" "Data worker domain (format: {worker-name}.{worker-subdomain})"
1234
1263
  prompt_for_var "AUDIT_WORKER_NAME" "Audit worker name"
1235
- prompt_for_var "AUDIT_WORKER_DOMAIN" "Audit worker domain (format: {worker-name}.{worker-subdomain})"
1236
1264
  prompt_for_var "IMAGES_WORKER_NAME" "Images worker name"
1237
- prompt_for_var "IMAGES_WORKER_DOMAIN" "Images worker domain (format: {worker-name}.{worker-subdomain})"
1238
1265
  prompt_for_var "PDF_WORKER_NAME" "PDF worker name"
1239
- prompt_for_var "PDF_WORKER_DOMAIN" "PDF worker domain (format: {worker-name}.{worker-subdomain})"
1266
+
1267
+ set_worker_domain_from_shared_subdomain "KEYS_WORKER_NAME" "KEYS_WORKER_DOMAIN"
1268
+ set_worker_domain_from_shared_subdomain "USER_WORKER_NAME" "USER_WORKER_DOMAIN"
1269
+ set_worker_domain_from_shared_subdomain "DATA_WORKER_NAME" "DATA_WORKER_DOMAIN"
1270
+ set_worker_domain_from_shared_subdomain "AUDIT_WORKER_NAME" "AUDIT_WORKER_DOMAIN"
1271
+ set_worker_domain_from_shared_subdomain "IMAGES_WORKER_NAME" "IMAGES_WORKER_DOMAIN"
1272
+ set_worker_domain_from_shared_subdomain "PDF_WORKER_NAME" "PDF_WORKER_DOMAIN"
1273
+ echo ""
1240
1274
 
1241
1275
  echo -e "${BLUE}🗄️ STORAGE CONFIGURATION${NC}"
1242
1276
  echo "========================="