@striae-org/striae 3.2.2 → 4.0.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 (82) hide show
  1. package/.env.example +1 -1
  2. package/app/components/actions/case-export/core-export.ts +5 -2
  3. package/app/components/actions/case-export/download-handlers.ts +51 -3
  4. package/app/components/actions/case-import/confirmation-import.ts +65 -40
  5. package/app/components/actions/case-import/confirmation-package.ts +86 -0
  6. package/app/components/actions/case-import/image-operations.ts +20 -49
  7. package/app/components/actions/case-import/index.ts +1 -0
  8. package/app/components/actions/case-import/orchestrator.ts +13 -3
  9. package/app/components/actions/case-import/storage-operations.ts +54 -89
  10. package/app/components/actions/case-import/validation.ts +7 -111
  11. package/app/components/actions/case-import/zip-processing.ts +44 -2
  12. package/app/components/actions/case-manage.ts +15 -27
  13. package/app/components/actions/confirm-export.ts +44 -13
  14. package/app/components/actions/generate-pdf.ts +3 -7
  15. package/app/components/actions/image-manage.ts +63 -129
  16. package/app/components/button/button.module.css +12 -8
  17. package/app/components/form/form-button.tsx +1 -1
  18. package/app/components/form/form.module.css +9 -0
  19. package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +163 -49
  20. package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +365 -88
  21. package/app/components/sidebar/case-export/case-export.tsx +13 -60
  22. package/app/components/sidebar/case-import/case-import.tsx +18 -6
  23. package/app/components/sidebar/case-import/hooks/useFilePreview.ts +6 -4
  24. package/app/components/sidebar/case-import/utils/file-validation.ts +57 -2
  25. package/app/components/sidebar/cases/case-sidebar.tsx +122 -52
  26. package/app/components/sidebar/cases/cases.module.css +101 -18
  27. package/app/components/sidebar/notes/notes.module.css +33 -13
  28. package/app/components/sidebar/sidebar.module.css +0 -2
  29. package/app/components/user/delete-account.tsx +7 -7
  30. package/app/components/user/manage-profile.tsx +1 -1
  31. package/app/components/user/mfa-phone-update.tsx +15 -12
  32. package/app/config-example/config.json +2 -8
  33. package/app/hooks/useInactivityTimeout.ts +2 -5
  34. package/app/root.tsx +96 -65
  35. package/app/routes/auth/login.tsx +132 -11
  36. package/app/routes/auth/route.ts +4 -3
  37. package/app/routes/striae/striae.tsx +4 -8
  38. package/app/services/audit/audit-api-client.ts +40 -0
  39. package/app/services/audit/audit-worker-client.ts +14 -17
  40. package/app/styles/root.module.css +13 -101
  41. package/app/tailwind.css +9 -2
  42. package/app/utils/SHA256.ts +5 -1
  43. package/app/utils/auth.ts +5 -32
  44. package/app/utils/confirmation-signature.ts +5 -1
  45. package/app/utils/data-api-client.ts +43 -0
  46. package/app/utils/data-operations.ts +59 -75
  47. package/app/utils/export-verification.ts +353 -0
  48. package/app/utils/image-api-client.ts +130 -0
  49. package/app/utils/pdf-api-client.ts +43 -0
  50. package/app/utils/permissions.ts +10 -23
  51. package/app/utils/signature-utils.ts +74 -4
  52. package/app/utils/user-api-client.ts +90 -0
  53. package/functions/api/_shared/firebase-auth.ts +255 -0
  54. package/functions/api/audit/[[path]].ts +150 -0
  55. package/functions/api/data/[[path]].ts +141 -0
  56. package/functions/api/image/[[path]].ts +127 -0
  57. package/functions/api/pdf/[[path]].ts +110 -0
  58. package/functions/api/user/[[path]].ts +196 -0
  59. package/package.json +8 -4
  60. package/public/favicon.ico +0 -0
  61. package/public/icon-256.png +0 -0
  62. package/public/icon-512.png +0 -0
  63. package/public/manifest.json +39 -0
  64. package/public/shortcut.png +0 -0
  65. package/public/social-image.png +0 -0
  66. package/react-router.config.ts +5 -0
  67. package/scripts/deploy-all.sh +22 -8
  68. package/scripts/deploy-config.sh +143 -148
  69. package/scripts/deploy-pages-secrets.sh +231 -0
  70. package/scripts/deploy-worker-secrets.sh +1 -1
  71. package/workers/audit-worker/wrangler.jsonc.example +1 -8
  72. package/workers/data-worker/wrangler.jsonc.example +1 -8
  73. package/workers/image-worker/wrangler.jsonc.example +1 -8
  74. package/workers/keys-worker/wrangler.jsonc.example +2 -9
  75. package/workers/pdf-worker/scripts/generate-assets.js +94 -0
  76. package/workers/pdf-worker/src/assets/icon-256.png +0 -0
  77. package/workers/pdf-worker/wrangler.jsonc.example +1 -8
  78. package/workers/user-worker/src/user-worker.example.ts +121 -41
  79. package/workers/user-worker/wrangler.jsonc.example +1 -8
  80. package/wrangler.toml.example +1 -1
  81. package/app/styles/legal-pages.module.css +0 -113
  82. package/public/favicon.svg +0 -9
@@ -0,0 +1,110 @@
1
+ import { verifyFirebaseIdentityFromRequest } from '../_shared/firebase-auth';
2
+
3
+ interface PdfProxyContext {
4
+ request: Request;
5
+ env: Env;
6
+ }
7
+
8
+ const SUPPORTED_METHODS = new Set(['POST', 'OPTIONS']);
9
+
10
+ function textResponse(message: string, status: number): Response {
11
+ return new Response(message, {
12
+ status,
13
+ headers: {
14
+ 'Cache-Control': 'no-store',
15
+ 'Content-Type': 'text/plain; charset=utf-8'
16
+ }
17
+ });
18
+ }
19
+
20
+ function normalizeWorkerBaseUrl(workerDomain: string): string {
21
+ if (typeof workerDomain !== 'string' || workerDomain.trim().length === 0) {
22
+ throw new Error('Invalid worker domain');
23
+ }
24
+
25
+ const trimmedDomain = workerDomain.trim().replace(/\/+$/, '');
26
+ if (trimmedDomain.startsWith('http://') || trimmedDomain.startsWith('https://')) {
27
+ return trimmedDomain;
28
+ }
29
+
30
+ return `https://${trimmedDomain}`;
31
+ }
32
+
33
+ function extractProxyPath(url: URL): string | null {
34
+ const routePrefix = '/api/pdf';
35
+ if (!url.pathname.startsWith(routePrefix)) {
36
+ return null;
37
+ }
38
+
39
+ const remainder = url.pathname.slice(routePrefix.length);
40
+ return remainder.length > 0 ? remainder : '/';
41
+ }
42
+
43
+ export const onRequest = async ({ request, env }: PdfProxyContext): Promise<Response> => {
44
+ if (!SUPPORTED_METHODS.has(request.method)) {
45
+ return textResponse('Method not allowed', 405);
46
+ }
47
+
48
+ if (request.method === 'OPTIONS') {
49
+ return new Response(null, {
50
+ status: 204,
51
+ headers: {
52
+ 'Allow': 'POST, OPTIONS',
53
+ 'Cache-Control': 'no-store'
54
+ }
55
+ });
56
+ }
57
+
58
+ const identity = await verifyFirebaseIdentityFromRequest(request, env);
59
+ if (!identity) {
60
+ return textResponse('Unauthorized', 401);
61
+ }
62
+
63
+ const requestUrl = new URL(request.url);
64
+ const proxyPath = extractProxyPath(requestUrl);
65
+ if (!proxyPath) {
66
+ return textResponse('Not Found', 404);
67
+ }
68
+
69
+ if (!env.PDF_WORKER_DOMAIN || !env.PDF_WORKER_AUTH) {
70
+ return textResponse('PDF service not configured', 502);
71
+ }
72
+
73
+ const pdfWorkerBaseUrl = normalizeWorkerBaseUrl(env.PDF_WORKER_DOMAIN);
74
+ const upstreamUrl = `${pdfWorkerBaseUrl}${proxyPath}${requestUrl.search}`;
75
+
76
+ const upstreamHeaders = new Headers();
77
+ const contentTypeHeader = request.headers.get('Content-Type');
78
+ if (contentTypeHeader) {
79
+ upstreamHeaders.set('Content-Type', contentTypeHeader);
80
+ }
81
+
82
+ const acceptHeader = request.headers.get('Accept');
83
+ if (acceptHeader) {
84
+ upstreamHeaders.set('Accept', acceptHeader);
85
+ }
86
+
87
+ upstreamHeaders.set('X-Custom-Auth-Key', env.PDF_WORKER_AUTH);
88
+
89
+ let upstreamResponse: Response;
90
+ try {
91
+ upstreamResponse = await fetch(upstreamUrl, {
92
+ method: request.method,
93
+ headers: upstreamHeaders,
94
+ body: request.body
95
+ });
96
+ } catch {
97
+ return textResponse('Upstream PDF service unavailable', 502);
98
+ }
99
+
100
+ const responseHeaders = new Headers(upstreamResponse.headers);
101
+ if (!responseHeaders.has('Cache-Control')) {
102
+ responseHeaders.set('Cache-Control', 'no-store');
103
+ }
104
+
105
+ return new Response(upstreamResponse.body, {
106
+ status: upstreamResponse.status,
107
+ statusText: upstreamResponse.statusText,
108
+ headers: responseHeaders
109
+ });
110
+ };
@@ -0,0 +1,196 @@
1
+ import { verifyFirebaseIdentityFromRequest } from '../_shared/firebase-auth';
2
+
3
+ interface UserProxyContext {
4
+ request: Request;
5
+ env: Env;
6
+ }
7
+
8
+ const SUPPORTED_METHODS = new Set(['GET', 'PUT', 'DELETE', 'OPTIONS']);
9
+ const USER_EXISTS_PATH_PREFIX = '/exists/';
10
+
11
+ function textResponse(message: string, status: number): Response {
12
+ return new Response(message, {
13
+ status,
14
+ headers: {
15
+ 'Cache-Control': 'no-store',
16
+ 'Content-Type': 'text/plain; charset=utf-8'
17
+ }
18
+ });
19
+ }
20
+
21
+ function jsonResponse(payload: Record<string, unknown>, status: number = 200): Response {
22
+ return new Response(JSON.stringify(payload), {
23
+ status,
24
+ headers: {
25
+ 'Cache-Control': 'no-store',
26
+ 'Content-Type': 'application/json; charset=utf-8'
27
+ }
28
+ });
29
+ }
30
+
31
+ function normalizeWorkerBaseUrl(workerDomain: string): string {
32
+ if (typeof workerDomain !== 'string' || workerDomain.trim().length === 0) {
33
+ throw new Error('Invalid worker domain');
34
+ }
35
+
36
+ const trimmedDomain = workerDomain.trim().replace(/\/+$/, '');
37
+ if (trimmedDomain.startsWith('http://') || trimmedDomain.startsWith('https://')) {
38
+ return trimmedDomain;
39
+ }
40
+
41
+ return `https://${trimmedDomain}`;
42
+ }
43
+
44
+ function extractProxyPath(url: URL): string | null {
45
+ const routePrefix = '/api/user';
46
+ if (!url.pathname.startsWith(routePrefix)) {
47
+ return null;
48
+ }
49
+
50
+ const remainder = url.pathname.slice(routePrefix.length);
51
+ return remainder.length > 0 ? remainder : '/';
52
+ }
53
+
54
+ function extractUserIdFromProxyPath(proxyPath: string): string | null {
55
+ const firstSegment = proxyPath.split('/').filter(Boolean)[0];
56
+ if (!firstSegment) {
57
+ return null;
58
+ }
59
+
60
+ try {
61
+ return decodeURIComponent(firstSegment);
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ function extractExistenceCheckUserId(proxyPath: string): string | null {
68
+ if (!proxyPath.startsWith(USER_EXISTS_PATH_PREFIX)) {
69
+ return null;
70
+ }
71
+
72
+ const remainder = proxyPath.slice(USER_EXISTS_PATH_PREFIX.length);
73
+ const firstSegment = remainder.split('/').filter(Boolean)[0];
74
+ if (!firstSegment) {
75
+ return null;
76
+ }
77
+
78
+ try {
79
+ return decodeURIComponent(firstSegment);
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ export const onRequest = async ({ request, env }: UserProxyContext): Promise<Response> => {
86
+ if (!SUPPORTED_METHODS.has(request.method)) {
87
+ return textResponse('Method not allowed', 405);
88
+ }
89
+
90
+ if (request.method === 'OPTIONS') {
91
+ return new Response(null, {
92
+ status: 204,
93
+ headers: {
94
+ 'Allow': 'GET, PUT, DELETE, OPTIONS',
95
+ 'Cache-Control': 'no-store'
96
+ }
97
+ });
98
+ }
99
+
100
+ const identity = await verifyFirebaseIdentityFromRequest(request, env);
101
+ if (!identity) {
102
+ return textResponse('Unauthorized', 401);
103
+ }
104
+
105
+ const requestUrl = new URL(request.url);
106
+ const proxyPath = extractProxyPath(requestUrl);
107
+ if (!proxyPath) {
108
+ return textResponse('Not Found', 404);
109
+ }
110
+
111
+ if (!env.USER_WORKER_DOMAIN || !env.USER_DB_AUTH) {
112
+ return textResponse('User service not configured', 502);
113
+ }
114
+
115
+ const userWorkerBaseUrl = normalizeWorkerBaseUrl(env.USER_WORKER_DOMAIN);
116
+
117
+ const existenceCheckUserId = extractExistenceCheckUserId(proxyPath);
118
+ if (existenceCheckUserId !== null) {
119
+ if (request.method !== 'GET') {
120
+ return textResponse('Method not allowed', 405);
121
+ }
122
+
123
+ let existenceResponse: Response;
124
+ try {
125
+ existenceResponse = await fetch(
126
+ `${userWorkerBaseUrl}/${encodeURIComponent(existenceCheckUserId)}`,
127
+ {
128
+ method: 'GET',
129
+ headers: {
130
+ 'Accept': 'application/json',
131
+ 'X-Custom-Auth-Key': env.USER_DB_AUTH
132
+ }
133
+ }
134
+ );
135
+ } catch {
136
+ return textResponse('Upstream user service unavailable', 502);
137
+ }
138
+
139
+ if (existenceResponse.status === 404) {
140
+ return jsonResponse({ exists: false });
141
+ }
142
+
143
+ if (!existenceResponse.ok) {
144
+ return textResponse('Upstream user service unavailable', 502);
145
+ }
146
+
147
+ return jsonResponse({ exists: true });
148
+ }
149
+
150
+ const requestedUserId = extractUserIdFromProxyPath(proxyPath);
151
+ if (!requestedUserId) {
152
+ return textResponse('Missing user identifier', 400);
153
+ }
154
+
155
+ if (requestedUserId !== identity.uid) {
156
+ return textResponse('Forbidden', 403);
157
+ }
158
+ const upstreamUrl = `${userWorkerBaseUrl}${proxyPath}${requestUrl.search}`;
159
+
160
+ const upstreamHeaders = new Headers();
161
+ const contentTypeHeader = request.headers.get('Content-Type');
162
+ if (contentTypeHeader) {
163
+ upstreamHeaders.set('Content-Type', contentTypeHeader);
164
+ }
165
+
166
+ const acceptHeader = request.headers.get('Accept');
167
+ if (acceptHeader) {
168
+ upstreamHeaders.set('Accept', acceptHeader);
169
+ }
170
+
171
+ upstreamHeaders.set('X-Custom-Auth-Key', env.USER_DB_AUTH);
172
+
173
+ const shouldForwardBody = request.method !== 'GET' && request.method !== 'HEAD';
174
+
175
+ let upstreamResponse: Response;
176
+ try {
177
+ upstreamResponse = await fetch(upstreamUrl, {
178
+ method: request.method,
179
+ headers: upstreamHeaders,
180
+ body: shouldForwardBody ? request.body : undefined
181
+ });
182
+ } catch {
183
+ return textResponse('Upstream user service unavailable', 502);
184
+ }
185
+
186
+ const responseHeaders = new Headers(upstreamResponse.headers);
187
+ if (!responseHeaders.has('Cache-Control')) {
188
+ responseHeaders.set('Cache-Control', 'no-store');
189
+ }
190
+
191
+ return new Response(upstreamResponse.body, {
192
+ status: upstreamResponse.status,
193
+ statusText: upstreamResponse.statusText,
194
+ headers: responseHeaders
195
+ });
196
+ };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@striae-org/striae",
3
- "version": "3.2.2",
3
+ "version": "4.0.0",
4
4
  "private": false,
5
- "description": "Striae is a cloud-native forensic annotation application for firearms identification, built with React Router and Cloudflare Workers.",
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
7
  "homepage": "https://www.striae.org",
8
8
  "repository": {
@@ -24,6 +24,8 @@
24
24
  "annotation",
25
25
  "react",
26
26
  "cloudflare-workers",
27
+ "authenticated",
28
+ "confirmations",
27
29
  "chain-of-custody",
28
30
  "audit-trail"
29
31
  ],
@@ -44,6 +46,7 @@
44
46
  "app/entry.server.tsx",
45
47
  "app/root.tsx",
46
48
  "app/tailwind.css",
49
+ "react-router.config.ts",
47
50
  "functions/",
48
51
  "public/",
49
52
  "scripts/",
@@ -51,8 +54,8 @@
51
54
  "workers/*/src/*.example.ts",
52
55
  "workers/*/src/*.example.js",
53
56
  "workers/*/src/*.ts",
54
- "workers/*/src/assets/*.ts",
55
- "workers/*/src/formats/*.ts",
57
+ "workers/pdf-worker/scripts/*.js",
58
+ "workers/pdf-worker/src/assets/icon-256.png",
56
59
  "!workers/*/src/*worker.ts",
57
60
  "workers/pdf-worker/src/assets/generated-assets.ts",
58
61
  "workers/pdf-worker/src/formats/format-striae.ts",
@@ -98,6 +101,7 @@
98
101
  "install-workers": "bash ./scripts/install-workers.sh",
99
102
  "deploy-workers": "npm run deploy-workers:audit && npm run deploy-workers:data && npm run deploy-workers:image && npm run deploy-workers:keys && npm run deploy-workers:pdf && npm run deploy-workers:user",
100
103
  "deploy-workers:secrets": "bash ./scripts/deploy-worker-secrets.sh",
104
+ "deploy-pages:secrets": "bash ./scripts/deploy-pages-secrets.sh",
101
105
  "deploy-pages": "bash ./scripts/deploy-pages.sh",
102
106
  "deploy-workers:audit": "cd workers/audit-worker && npm run deploy",
103
107
  "deploy-workers:data": "cd workers/data-worker && npm run deploy",
Binary file
Binary file
Binary file
@@ -0,0 +1,39 @@
1
+ {
2
+ "id": "/",
3
+ "name": "Striae: A Firearms Examiner's Comparison Companion",
4
+ "short_name": "Striae",
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
+ "start_url": "/",
7
+ "scope": "/",
8
+ "display": "standalone",
9
+ "orientation": "any",
10
+ "lang": "en-US",
11
+ "dir": "ltr",
12
+ "theme_color": "#377087",
13
+ "background_color": "#f5f5f5",
14
+ "categories": [
15
+ "business",
16
+ "productivity",
17
+ "utilities"
18
+ ],
19
+ "icons": [
20
+ {
21
+ "src": "shortcut.png",
22
+ "sizes": "64x64",
23
+ "type": "image/png",
24
+ "purpose": "any"
25
+ },
26
+ {
27
+ "src": "icon-256.png",
28
+ "sizes": "256x256",
29
+ "type": "image/png",
30
+ "purpose": "any"
31
+ },
32
+ {
33
+ "src": "icon-512.png",
34
+ "sizes": "512x512",
35
+ "type": "image/png",
36
+ "purpose": "any"
37
+ }
38
+ ]
39
+ }
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ import type { Config } from "@react-router/dev/config";
2
+
3
+ export default {
4
+ ssr: true,
5
+ } satisfies Config;
@@ -8,7 +8,8 @@
8
8
  # 2. Worker dependencies installation
9
9
  # 3. Workers (all 7 workers)
10
10
  # 4. Worker secrets/environment variables
11
- # 5. Pages (frontend)
11
+ # 5. Pages secrets/environment variables
12
+ # 6. Pages (frontend)
12
13
 
13
14
  set -e
14
15
  set -o pipefail
@@ -66,6 +67,7 @@ require_command wrangler
66
67
  assert_file_exists "$SCRIPT_DIR/deploy-config.sh"
67
68
  assert_file_exists "$SCRIPT_DIR/install-workers.sh"
68
69
  assert_file_exists "$SCRIPT_DIR/deploy-worker-secrets.sh"
70
+ assert_file_exists "$SCRIPT_DIR/deploy-pages-secrets.sh"
69
71
  assert_file_exists "package.json"
70
72
 
71
73
  if [ ! -f ".env" ] && [ ! -f ".env.example" ]; then
@@ -77,7 +79,7 @@ echo -e "${GREEN}✅ Preflight checks passed${NC}"
77
79
  echo ""
78
80
 
79
81
  # Step 1: Configuration Setup
80
- echo -e "${PURPLE}Step 1/5: Configuration Setup${NC}"
82
+ echo -e "${PURPLE}Step 1/6: Configuration Setup${NC}"
81
83
  echo "------------------------------"
82
84
  echo -e "${YELLOW}⚙️ Setting up configuration files and replacing placeholders...${NC}"
83
85
  if ! bash "$SCRIPT_DIR/deploy-config.sh"; then
@@ -90,7 +92,7 @@ run_config_checkpoint
90
92
  echo ""
91
93
 
92
94
  # Step 2: Install Worker Dependencies
93
- echo -e "${PURPLE}Step 2/5: Installing Worker Dependencies${NC}"
95
+ echo -e "${PURPLE}Step 2/6: Installing Worker Dependencies${NC}"
94
96
  echo "----------------------------------------"
95
97
  echo -e "${YELLOW}📦 Installing npm dependencies for all workers...${NC}"
96
98
  if ! bash "$SCRIPT_DIR/install-workers.sh"; then
@@ -101,7 +103,7 @@ echo -e "${GREEN}✅ All worker dependencies installed successfully${NC}"
101
103
  echo ""
102
104
 
103
105
  # Step 3: Deploy Workers
104
- echo -e "${PURPLE}Step 3/5: Deploying Workers${NC}"
106
+ echo -e "${PURPLE}Step 3/6: Deploying Workers${NC}"
105
107
  echo "----------------------------"
106
108
  echo -e "${YELLOW}🔧 Deploying all 7 Cloudflare Workers...${NC}"
107
109
  if ! npm run deploy-workers; then
@@ -112,7 +114,7 @@ echo -e "${GREEN}✅ All workers deployed successfully${NC}"
112
114
  echo ""
113
115
 
114
116
  # Step 4: Deploy Worker Secrets
115
- echo -e "${PURPLE}Step 4/5: Deploying Worker Secrets${NC}"
117
+ echo -e "${PURPLE}Step 4/6: Deploying Worker Secrets${NC}"
116
118
  echo "-----------------------------------"
117
119
  echo -e "${YELLOW}🔐 Deploying worker environment variables...${NC}"
118
120
  if ! bash "$SCRIPT_DIR/deploy-worker-secrets.sh"; then
@@ -122,8 +124,19 @@ fi
122
124
  echo -e "${GREEN}✅ Worker secrets deployed successfully${NC}"
123
125
  echo ""
124
126
 
125
- # Step 5: Deploy Pages
126
- echo -e "${PURPLE}Step 5/5: Deploying Pages${NC}"
127
+ # Step 5: Deploy Pages Secrets
128
+ echo -e "${PURPLE}Step 5/6: Deploying Pages Secrets${NC}"
129
+ echo "----------------------------------"
130
+ echo -e "${YELLOW}🔐 Deploying Pages environment variables...${NC}"
131
+ if ! bash "$SCRIPT_DIR/deploy-pages-secrets.sh"; then
132
+ echo -e "${RED}❌ Pages secrets deployment failed!${NC}"
133
+ exit 1
134
+ fi
135
+ echo -e "${GREEN}✅ Pages secrets deployed successfully${NC}"
136
+ echo ""
137
+
138
+ # Step 6: Deploy Pages
139
+ echo -e "${PURPLE}Step 6/6: Deploying Pages${NC}"
127
140
  echo "--------------------------"
128
141
  echo -e "${YELLOW}🌐 Building and deploying Pages...${NC}"
129
142
  if ! npm run deploy-pages; then
@@ -142,11 +155,12 @@ echo -e "${BLUE}Deployed Components:${NC}"
142
155
  echo " ✅ Worker dependencies (npm install)"
143
156
  echo " ✅ 7 Cloudflare Workers"
144
157
  echo " ✅ Worker environment variables"
158
+ echo " ✅ Pages environment variables"
145
159
  echo " ✅ Cloudflare Pages frontend"
146
160
  echo ""
147
161
  echo -e "${BLUE}Next Steps:${NC}"
148
162
  echo " 1. Test your application endpoints"
149
163
  echo " 2. Verify all services are working"
150
- echo " 3. Configure custom domain (optional)"
164
+ echo " 3. Verify worker and Pages secrets are set as expected"
151
165
  echo ""
152
166
  echo -e "${GREEN}✨ Your Striae application is now fully deployed!${NC}"