@ucptools/validator 1.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.
- package/CLAUDE.md +109 -0
- package/CONTRIBUTING.md +113 -0
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/api/analyze-feed.js +140 -0
- package/api/badge.js +185 -0
- package/api/benchmark.js +177 -0
- package/api/directory-stats.ts +29 -0
- package/api/directory.ts +73 -0
- package/api/generate-compliance.js +143 -0
- package/api/generate-schema.js +457 -0
- package/api/generate.js +132 -0
- package/api/security-scan.js +133 -0
- package/api/simulate.js +187 -0
- package/api/tsconfig.json +10 -0
- package/api/validate.js +1351 -0
- package/apify-actor/.actor/actor.json +68 -0
- package/apify-actor/.actor/input_schema.json +32 -0
- package/apify-actor/APIFY-STORE-LISTING.md +412 -0
- package/apify-actor/Dockerfile +8 -0
- package/apify-actor/README.md +166 -0
- package/apify-actor/main.ts +111 -0
- package/apify-actor/package.json +17 -0
- package/apify-actor/src/main.js +199 -0
- package/docs/BRAND-IDENTITY.md +238 -0
- package/docs/BRAND-STYLE-GUIDE.md +356 -0
- package/drizzle/0000_black_king_cobra.sql +39 -0
- package/drizzle/meta/0000_snapshot.json +309 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +10 -0
- package/examples/full-profile.json +70 -0
- package/examples/minimal-profile.json +23 -0
- package/package.json +69 -0
- package/public/.well-known/ucp +25 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/brand.css +321 -0
- package/public/directory.html +701 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/guides/bigcommerce.html +743 -0
- package/public/guides/fastucp.html +838 -0
- package/public/guides/magento.html +779 -0
- package/public/guides/shopify.html +726 -0
- package/public/guides/squarespace.html +749 -0
- package/public/guides/wix.html +747 -0
- package/public/guides/woocommerce.html +733 -0
- package/public/index.html +3835 -0
- package/public/learn.html +396 -0
- package/public/logo.jpeg +0 -0
- package/public/og-image-icon.png +0 -0
- package/public/og-image.png +0 -0
- package/public/robots.txt +6 -0
- package/public/site.webmanifest +31 -0
- package/public/sitemap.xml +69 -0
- package/public/social/linkedin-banner-1128x191.png +0 -0
- package/public/social/temp.PNG +0 -0
- package/public/social/x-header-1500x500.png +0 -0
- package/public/verify.html +410 -0
- package/scripts/generate-favicons.js +44 -0
- package/scripts/generate-ico.js +23 -0
- package/scripts/generate-og-image.js +45 -0
- package/scripts/reset-db.ts +77 -0
- package/scripts/seed-db.ts +71 -0
- package/scripts/setup-benchmark-db.js +70 -0
- package/src/api/server.ts +266 -0
- package/src/cli/index.ts +302 -0
- package/src/compliance/compliance-generator.ts +452 -0
- package/src/compliance/index.ts +28 -0
- package/src/compliance/templates.ts +338 -0
- package/src/compliance/types.ts +170 -0
- package/src/db/index.ts +28 -0
- package/src/db/schema.ts +84 -0
- package/src/feed-analyzer/feed-analyzer.ts +726 -0
- package/src/feed-analyzer/index.ts +34 -0
- package/src/feed-analyzer/types.ts +354 -0
- package/src/generator/index.ts +7 -0
- package/src/generator/key-generator.ts +124 -0
- package/src/generator/profile-builder.ts +402 -0
- package/src/hosting/artifacts-generator.ts +679 -0
- package/src/hosting/index.ts +6 -0
- package/src/index.ts +105 -0
- package/src/security/index.ts +15 -0
- package/src/security/security-scanner.ts +604 -0
- package/src/security/types.ts +55 -0
- package/src/services/directory.ts +434 -0
- package/src/simulator/agent-simulator.ts +941 -0
- package/src/simulator/index.ts +7 -0
- package/src/simulator/types.ts +170 -0
- package/src/types/generator.ts +140 -0
- package/src/types/index.ts +7 -0
- package/src/types/ucp-profile.ts +140 -0
- package/src/types/validation.ts +89 -0
- package/src/validator/index.ts +194 -0
- package/src/validator/network-validator.ts +417 -0
- package/src/validator/rules-validator.ts +297 -0
- package/src/validator/sdk-validator.ts +330 -0
- package/src/validator/structural-validator.ts +476 -0
- package/tests/fixtures/non-compliant-profile.json +25 -0
- package/tests/fixtures/official-sample-profile.json +75 -0
- package/tests/integration/benchmark.test.ts +207 -0
- package/tests/integration/database.test.ts +163 -0
- package/tests/integration/directory-api.test.ts +268 -0
- package/tests/integration/simulate-api.test.ts +230 -0
- package/tests/integration/validate-api.test.ts +269 -0
- package/tests/setup.ts +15 -0
- package/tests/unit/agent-simulator.test.ts +575 -0
- package/tests/unit/compliance-generator.test.ts +374 -0
- package/tests/unit/directory-service.test.ts +272 -0
- package/tests/unit/feed-analyzer.test.ts +517 -0
- package/tests/unit/lint-suggestions.test.ts +423 -0
- package/tests/unit/official-samples.test.ts +211 -0
- package/tests/unit/pdf-report.test.ts +390 -0
- package/tests/unit/sdk-validator.test.ts +531 -0
- package/tests/unit/security-scanner.test.ts +410 -0
- package/tests/unit/validation.test.ts +390 -0
- package/tsconfig.json +20 -0
- package/vercel.json +34 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosting Artifacts Generator
|
|
3
|
+
* Generates installation artifacts for different hosting platforms
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
HostingConfig,
|
|
8
|
+
HostingMode,
|
|
9
|
+
HostingPlatform,
|
|
10
|
+
InstallArtifact,
|
|
11
|
+
} from '../types/generator.js';
|
|
12
|
+
|
|
13
|
+
const HOSTED_PROFILE_BASE_URL = 'https://profiles.ucp.tools';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate all installation artifacts for a hosting configuration
|
|
17
|
+
*/
|
|
18
|
+
export function generateHostingArtifacts(
|
|
19
|
+
config: HostingConfig,
|
|
20
|
+
profileJson: string
|
|
21
|
+
): InstallArtifact[] {
|
|
22
|
+
const artifacts: InstallArtifact[] = [];
|
|
23
|
+
|
|
24
|
+
// Always include the profile JSON
|
|
25
|
+
artifacts.push({
|
|
26
|
+
filename: 'ucp.json',
|
|
27
|
+
content: profileJson,
|
|
28
|
+
contentType: 'json',
|
|
29
|
+
description: 'UCP Business Profile JSON file',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Generate platform-specific artifacts
|
|
33
|
+
switch (config.mode) {
|
|
34
|
+
case 'static':
|
|
35
|
+
artifacts.push(...generateStaticArtifacts(config));
|
|
36
|
+
break;
|
|
37
|
+
case 'edge-worker':
|
|
38
|
+
artifacts.push(...generateEdgeWorkerArtifacts(config));
|
|
39
|
+
break;
|
|
40
|
+
case 'reverse-proxy':
|
|
41
|
+
artifacts.push(...generateReverseProxyArtifacts(config));
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Always include README
|
|
46
|
+
artifacts.push(generateReadme(config));
|
|
47
|
+
|
|
48
|
+
return artifacts;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate artifacts for static file hosting
|
|
53
|
+
*/
|
|
54
|
+
function generateStaticArtifacts(config: HostingConfig): InstallArtifact[] {
|
|
55
|
+
const artifacts: InstallArtifact[] = [];
|
|
56
|
+
const platform = config.platform || 'generic';
|
|
57
|
+
|
|
58
|
+
switch (platform) {
|
|
59
|
+
case 'nginx':
|
|
60
|
+
artifacts.push(generateNginxStaticConfig(config));
|
|
61
|
+
break;
|
|
62
|
+
case 'apache':
|
|
63
|
+
artifacts.push(generateApacheConfig(config));
|
|
64
|
+
break;
|
|
65
|
+
case 'vercel':
|
|
66
|
+
artifacts.push(generateVercelConfig(config));
|
|
67
|
+
break;
|
|
68
|
+
case 'netlify':
|
|
69
|
+
artifacts.push(generateNetlifyConfig(config));
|
|
70
|
+
break;
|
|
71
|
+
case 'cloudflare-pages':
|
|
72
|
+
artifacts.push(generateCloudflarePagesFunctions(config));
|
|
73
|
+
break;
|
|
74
|
+
case 's3-cloudfront':
|
|
75
|
+
artifacts.push(generateS3Instructions(config));
|
|
76
|
+
break;
|
|
77
|
+
default:
|
|
78
|
+
artifacts.push(generateGenericStaticInstructions(config));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return artifacts;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generate artifacts for edge worker hosting
|
|
86
|
+
*/
|
|
87
|
+
function generateEdgeWorkerArtifacts(config: HostingConfig): InstallArtifact[] {
|
|
88
|
+
const artifacts: InstallArtifact[] = [];
|
|
89
|
+
const platform = config.platform || 'cloudflare-worker';
|
|
90
|
+
|
|
91
|
+
if (platform === 'cloudflare-worker') {
|
|
92
|
+
artifacts.push(generateCloudflareWorker(config));
|
|
93
|
+
artifacts.push(generateWranglerConfig(config));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return artifacts;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate artifacts for reverse proxy hosting
|
|
101
|
+
*/
|
|
102
|
+
function generateReverseProxyArtifacts(config: HostingConfig): InstallArtifact[] {
|
|
103
|
+
const artifacts: InstallArtifact[] = [];
|
|
104
|
+
|
|
105
|
+
artifacts.push(generateNginxProxyConfig(config));
|
|
106
|
+
|
|
107
|
+
return artifacts;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Nginx static file configuration
|
|
112
|
+
*/
|
|
113
|
+
function generateNginxStaticConfig(config: HostingConfig): InstallArtifact {
|
|
114
|
+
const content = `# Nginx configuration for UCP profile at ${config.merchantDomain}
|
|
115
|
+
# Add this to your server block
|
|
116
|
+
|
|
117
|
+
location = /.well-known/ucp {
|
|
118
|
+
alias /var/www/${config.merchantDomain}/ucp.json;
|
|
119
|
+
default_type application/json;
|
|
120
|
+
|
|
121
|
+
# CORS headers (required for cross-origin discovery)
|
|
122
|
+
add_header Access-Control-Allow-Origin "*" always;
|
|
123
|
+
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
|
|
124
|
+
add_header Access-Control-Allow-Headers "Accept, Content-Type" always;
|
|
125
|
+
|
|
126
|
+
# Caching (1 hour recommended)
|
|
127
|
+
add_header Cache-Control "public, max-age=3600" always;
|
|
128
|
+
|
|
129
|
+
# Handle preflight requests
|
|
130
|
+
if ($request_method = OPTIONS) {
|
|
131
|
+
return 204;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
filename: 'nginx-ucp.conf',
|
|
138
|
+
content,
|
|
139
|
+
contentType: 'nginx',
|
|
140
|
+
description: 'Nginx configuration snippet for static UCP profile',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Apache configuration
|
|
146
|
+
*/
|
|
147
|
+
function generateApacheConfig(config: HostingConfig): InstallArtifact {
|
|
148
|
+
const content = `# Apache configuration for UCP profile at ${config.merchantDomain}
|
|
149
|
+
# Add this to your VirtualHost or .htaccess
|
|
150
|
+
|
|
151
|
+
<IfModule mod_rewrite.c>
|
|
152
|
+
RewriteEngine On
|
|
153
|
+
RewriteRule ^.well-known/ucp$ /ucp.json [L]
|
|
154
|
+
</IfModule>
|
|
155
|
+
|
|
156
|
+
<Files "ucp.json">
|
|
157
|
+
ForceType application/json
|
|
158
|
+
|
|
159
|
+
# CORS headers
|
|
160
|
+
Header set Access-Control-Allow-Origin "*"
|
|
161
|
+
Header set Access-Control-Allow-Methods "GET, OPTIONS"
|
|
162
|
+
Header set Access-Control-Allow-Headers "Accept, Content-Type"
|
|
163
|
+
|
|
164
|
+
# Caching
|
|
165
|
+
Header set Cache-Control "public, max-age=3600"
|
|
166
|
+
</Files>
|
|
167
|
+
|
|
168
|
+
<IfModule mod_headers.c>
|
|
169
|
+
# Handle preflight requests for .well-known/ucp
|
|
170
|
+
<LocationMatch "^/.well-known/ucp$">
|
|
171
|
+
Header always set Access-Control-Allow-Origin "*"
|
|
172
|
+
Header always set Access-Control-Allow-Methods "GET, OPTIONS"
|
|
173
|
+
</LocationMatch>
|
|
174
|
+
</IfModule>
|
|
175
|
+
`;
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
filename: '.htaccess',
|
|
179
|
+
content,
|
|
180
|
+
contentType: 'apache',
|
|
181
|
+
description: 'Apache configuration for UCP profile',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Vercel configuration
|
|
187
|
+
*/
|
|
188
|
+
function generateVercelConfig(config: HostingConfig): InstallArtifact {
|
|
189
|
+
const content = JSON.stringify({
|
|
190
|
+
rewrites: [
|
|
191
|
+
{ source: '/.well-known/ucp', destination: '/ucp.json' }
|
|
192
|
+
],
|
|
193
|
+
headers: [
|
|
194
|
+
{
|
|
195
|
+
source: '/.well-known/ucp',
|
|
196
|
+
headers: [
|
|
197
|
+
{ key: 'Content-Type', value: 'application/json' },
|
|
198
|
+
{ key: 'Access-Control-Allow-Origin', value: '*' },
|
|
199
|
+
{ key: 'Access-Control-Allow-Methods', value: 'GET, OPTIONS' },
|
|
200
|
+
{ key: 'Cache-Control', value: 'public, max-age=3600' }
|
|
201
|
+
]
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
source: '/ucp.json',
|
|
205
|
+
headers: [
|
|
206
|
+
{ key: 'Content-Type', value: 'application/json' },
|
|
207
|
+
{ key: 'Access-Control-Allow-Origin', value: '*' },
|
|
208
|
+
{ key: 'Cache-Control', value: 'public, max-age=3600' }
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
}, null, 2);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
filename: 'vercel.json',
|
|
216
|
+
content,
|
|
217
|
+
contentType: 'json',
|
|
218
|
+
description: 'Vercel configuration for UCP profile routing',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Netlify configuration
|
|
224
|
+
*/
|
|
225
|
+
function generateNetlifyConfig(config: HostingConfig): InstallArtifact {
|
|
226
|
+
const redirects = `# Netlify redirects for UCP profile
|
|
227
|
+
/.well-known/ucp /ucp.json 200
|
|
228
|
+
`;
|
|
229
|
+
|
|
230
|
+
const headers = `# Netlify headers
|
|
231
|
+
/.well-known/ucp
|
|
232
|
+
Content-Type: application/json
|
|
233
|
+
Access-Control-Allow-Origin: *
|
|
234
|
+
Access-Control-Allow-Methods: GET, OPTIONS
|
|
235
|
+
Cache-Control: public, max-age=3600
|
|
236
|
+
|
|
237
|
+
/ucp.json
|
|
238
|
+
Content-Type: application/json
|
|
239
|
+
Access-Control-Allow-Origin: *
|
|
240
|
+
Cache-Control: public, max-age=3600
|
|
241
|
+
`;
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
filename: '_redirects',
|
|
245
|
+
content: redirects + '\n' + headers.replace(/^/gm, '# '),
|
|
246
|
+
contentType: 'markdown',
|
|
247
|
+
description: 'Netlify redirects and headers (also create _headers file)',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Cloudflare Pages Functions
|
|
253
|
+
*/
|
|
254
|
+
function generateCloudflarePagesFunctions(config: HostingConfig): InstallArtifact {
|
|
255
|
+
const content = `// Cloudflare Pages Function for /.well-known/ucp
|
|
256
|
+
// Place this file at: functions/.well-known/ucp.js
|
|
257
|
+
|
|
258
|
+
export async function onRequest(context) {
|
|
259
|
+
// Import the profile JSON (place ucp.json in your public folder)
|
|
260
|
+
const profileUrl = new URL('/ucp.json', context.request.url);
|
|
261
|
+
|
|
262
|
+
const response = await fetch(profileUrl);
|
|
263
|
+
const profile = await response.text();
|
|
264
|
+
|
|
265
|
+
return new Response(profile, {
|
|
266
|
+
headers: {
|
|
267
|
+
'Content-Type': 'application/json',
|
|
268
|
+
'Access-Control-Allow-Origin': '*',
|
|
269
|
+
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
|
270
|
+
'Cache-Control': 'public, max-age=3600',
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
`;
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
filename: 'functions/.well-known/ucp.js',
|
|
278
|
+
content,
|
|
279
|
+
contentType: 'javascript',
|
|
280
|
+
description: 'Cloudflare Pages Function for UCP endpoint',
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Cloudflare Worker for edge hosting
|
|
286
|
+
*/
|
|
287
|
+
function generateCloudflareWorker(config: HostingConfig): InstallArtifact {
|
|
288
|
+
const hostedUrl = config.hostedProfileUrl ||
|
|
289
|
+
`${HOSTED_PROFILE_BASE_URL}/${config.merchantId}/ucp.json`;
|
|
290
|
+
|
|
291
|
+
const content = `// Cloudflare Worker for UCP Profile Proxy
|
|
292
|
+
// Route: ${config.merchantDomain}/.well-known/ucp*
|
|
293
|
+
|
|
294
|
+
export default {
|
|
295
|
+
async fetch(request, env, ctx) {
|
|
296
|
+
const url = new URL(request.url);
|
|
297
|
+
|
|
298
|
+
// Only handle /.well-known/ucp path
|
|
299
|
+
if (url.pathname !== '/.well-known/ucp') {
|
|
300
|
+
return new Response('Not Found', { status: 404 });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Handle CORS preflight
|
|
304
|
+
if (request.method === 'OPTIONS') {
|
|
305
|
+
return new Response(null, {
|
|
306
|
+
status: 204,
|
|
307
|
+
headers: {
|
|
308
|
+
'Access-Control-Allow-Origin': '*',
|
|
309
|
+
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
|
310
|
+
'Access-Control-Allow-Headers': 'Accept, Content-Type',
|
|
311
|
+
'Access-Control-Max-Age': '86400',
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Fetch profile from hosted service
|
|
317
|
+
const profileUrl = '${hostedUrl}';
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const response = await fetch(profileUrl, {
|
|
321
|
+
cf: {
|
|
322
|
+
cacheTtl: 3600, // Cache for 1 hour
|
|
323
|
+
cacheEverything: true,
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
if (!response.ok) {
|
|
328
|
+
return new Response(
|
|
329
|
+
JSON.stringify({ error: 'Profile not found' }),
|
|
330
|
+
{
|
|
331
|
+
status: response.status,
|
|
332
|
+
headers: { 'Content-Type': 'application/json' }
|
|
333
|
+
}
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const profile = await response.text();
|
|
338
|
+
|
|
339
|
+
return new Response(profile, {
|
|
340
|
+
headers: {
|
|
341
|
+
'Content-Type': 'application/json',
|
|
342
|
+
'Access-Control-Allow-Origin': '*',
|
|
343
|
+
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
|
344
|
+
'Cache-Control': 'public, max-age=3600',
|
|
345
|
+
'X-UCP-Profile-Source': 'hosted',
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
} catch (error) {
|
|
349
|
+
return new Response(
|
|
350
|
+
JSON.stringify({ error: 'Failed to fetch profile' }),
|
|
351
|
+
{
|
|
352
|
+
status: 502,
|
|
353
|
+
headers: { 'Content-Type': 'application/json' }
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
`;
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
filename: 'worker.js',
|
|
363
|
+
content,
|
|
364
|
+
contentType: 'javascript',
|
|
365
|
+
description: 'Cloudflare Worker for proxying UCP profile from hosted service',
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Wrangler configuration for Cloudflare Worker
|
|
371
|
+
*/
|
|
372
|
+
function generateWranglerConfig(config: HostingConfig): InstallArtifact {
|
|
373
|
+
const content = `# Cloudflare Worker configuration
|
|
374
|
+
# Run: npx wrangler deploy
|
|
375
|
+
|
|
376
|
+
name = "ucp-profile-${config.merchantId}"
|
|
377
|
+
main = "worker.js"
|
|
378
|
+
compatibility_date = "2024-01-01"
|
|
379
|
+
|
|
380
|
+
# Route configuration
|
|
381
|
+
# Update this with your actual domain
|
|
382
|
+
routes = [
|
|
383
|
+
{ pattern = "${config.merchantDomain}/.well-known/ucp*", zone_name = "${config.merchantDomain}" }
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
# Optional: Environment variables
|
|
387
|
+
# [vars]
|
|
388
|
+
# PROFILE_URL = "${config.hostedProfileUrl || `${HOSTED_PROFILE_BASE_URL}/${config.merchantId}/ucp.json`}"
|
|
389
|
+
`;
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
filename: 'wrangler.toml',
|
|
393
|
+
content,
|
|
394
|
+
contentType: 'markdown',
|
|
395
|
+
description: 'Wrangler configuration for deploying Cloudflare Worker',
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Nginx reverse proxy configuration
|
|
401
|
+
*/
|
|
402
|
+
function generateNginxProxyConfig(config: HostingConfig): InstallArtifact {
|
|
403
|
+
const hostedUrl = config.hostedProfileUrl ||
|
|
404
|
+
`${HOSTED_PROFILE_BASE_URL}/${config.merchantId}/ucp.json`;
|
|
405
|
+
|
|
406
|
+
const content = `# Nginx reverse proxy configuration for UCP profile
|
|
407
|
+
# Add this to your server block
|
|
408
|
+
|
|
409
|
+
location = /.well-known/ucp {
|
|
410
|
+
# Proxy to hosted profile service
|
|
411
|
+
proxy_pass ${hostedUrl};
|
|
412
|
+
proxy_set_header Host profiles.ucp.tools;
|
|
413
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
414
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
415
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
416
|
+
|
|
417
|
+
# Caching
|
|
418
|
+
proxy_cache_valid 200 1h;
|
|
419
|
+
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
|
|
420
|
+
|
|
421
|
+
# CORS headers
|
|
422
|
+
add_header Access-Control-Allow-Origin "*" always;
|
|
423
|
+
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
|
|
424
|
+
add_header Cache-Control "public, max-age=3600" always;
|
|
425
|
+
|
|
426
|
+
# Handle preflight requests
|
|
427
|
+
if ($request_method = OPTIONS) {
|
|
428
|
+
add_header Access-Control-Allow-Origin "*";
|
|
429
|
+
add_header Access-Control-Allow-Methods "GET, OPTIONS";
|
|
430
|
+
add_header Access-Control-Max-Age 86400;
|
|
431
|
+
return 204;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
`;
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
filename: 'nginx-proxy.conf',
|
|
438
|
+
content,
|
|
439
|
+
contentType: 'nginx',
|
|
440
|
+
description: 'Nginx reverse proxy configuration for hosted UCP profile',
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* S3 + CloudFront instructions
|
|
446
|
+
*/
|
|
447
|
+
function generateS3Instructions(config: HostingConfig): InstallArtifact {
|
|
448
|
+
const content = `# AWS S3 + CloudFront Setup for UCP Profile
|
|
449
|
+
|
|
450
|
+
## 1. Upload Profile to S3
|
|
451
|
+
|
|
452
|
+
\`\`\`bash
|
|
453
|
+
# Create bucket (if needed)
|
|
454
|
+
aws s3 mb s3://${config.merchantDomain}-ucp
|
|
455
|
+
|
|
456
|
+
# Upload profile
|
|
457
|
+
aws s3 cp ucp.json s3://${config.merchantDomain}-ucp/ucp.json \\
|
|
458
|
+
--content-type "application/json" \\
|
|
459
|
+
--cache-control "public, max-age=3600"
|
|
460
|
+
\`\`\`
|
|
461
|
+
|
|
462
|
+
## 2. Configure CloudFront
|
|
463
|
+
|
|
464
|
+
Create a CloudFront distribution with:
|
|
465
|
+
|
|
466
|
+
- Origin: S3 bucket \`${config.merchantDomain}-ucp\`
|
|
467
|
+
- Alternate domain: \`${config.merchantDomain}\`
|
|
468
|
+
- Behavior for \`/.well-known/ucp\`:
|
|
469
|
+
- Origin path: \`/ucp.json\`
|
|
470
|
+
- Allowed methods: GET, HEAD, OPTIONS
|
|
471
|
+
- Response headers policy: Add CORS headers
|
|
472
|
+
|
|
473
|
+
## 3. CloudFront Function (for path rewrite)
|
|
474
|
+
|
|
475
|
+
\`\`\`javascript
|
|
476
|
+
function handler(event) {
|
|
477
|
+
var request = event.request;
|
|
478
|
+
if (request.uri === '/.well-known/ucp') {
|
|
479
|
+
request.uri = '/ucp.json';
|
|
480
|
+
}
|
|
481
|
+
return request;
|
|
482
|
+
}
|
|
483
|
+
\`\`\`
|
|
484
|
+
|
|
485
|
+
## 4. CORS Configuration (S3 bucket policy)
|
|
486
|
+
|
|
487
|
+
\`\`\`json
|
|
488
|
+
{
|
|
489
|
+
"CORSRules": [
|
|
490
|
+
{
|
|
491
|
+
"AllowedHeaders": ["*"],
|
|
492
|
+
"AllowedMethods": ["GET"],
|
|
493
|
+
"AllowedOrigins": ["*"],
|
|
494
|
+
"ExposeHeaders": []
|
|
495
|
+
}
|
|
496
|
+
]
|
|
497
|
+
}
|
|
498
|
+
\`\`\`
|
|
499
|
+
|
|
500
|
+
## 5. Verification
|
|
501
|
+
|
|
502
|
+
\`\`\`bash
|
|
503
|
+
curl -I https://${config.merchantDomain}/.well-known/ucp
|
|
504
|
+
\`\`\`
|
|
505
|
+
|
|
506
|
+
Expected: HTTP 200 with Content-Type: application/json
|
|
507
|
+
`;
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
filename: 'aws-setup.md',
|
|
511
|
+
content,
|
|
512
|
+
contentType: 'markdown',
|
|
513
|
+
description: 'AWS S3 + CloudFront setup instructions',
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Generic static hosting instructions
|
|
519
|
+
*/
|
|
520
|
+
function generateGenericStaticInstructions(config: HostingConfig): InstallArtifact {
|
|
521
|
+
const content = `# UCP Profile Static Hosting Instructions
|
|
522
|
+
|
|
523
|
+
## Requirements
|
|
524
|
+
|
|
525
|
+
Your web server must serve the \`ucp.json\` file at:
|
|
526
|
+
\`\`\`
|
|
527
|
+
https://${config.merchantDomain}/.well-known/ucp
|
|
528
|
+
\`\`\`
|
|
529
|
+
|
|
530
|
+
## Configuration Checklist
|
|
531
|
+
|
|
532
|
+
1. **File Location**: Place \`ucp.json\` in your web root's \`.well-known\` directory
|
|
533
|
+
or configure a rewrite rule from \`/.well-known/ucp\` to your file location.
|
|
534
|
+
|
|
535
|
+
2. **Content-Type**: Ensure the response has \`Content-Type: application/json\`
|
|
536
|
+
|
|
537
|
+
3. **CORS Headers**: Add these headers for cross-origin discovery:
|
|
538
|
+
\`\`\`
|
|
539
|
+
Access-Control-Allow-Origin: *
|
|
540
|
+
Access-Control-Allow-Methods: GET, OPTIONS
|
|
541
|
+
Access-Control-Allow-Headers: Accept, Content-Type
|
|
542
|
+
\`\`\`
|
|
543
|
+
|
|
544
|
+
4. **Caching**: Recommended cache headers:
|
|
545
|
+
\`\`\`
|
|
546
|
+
Cache-Control: public, max-age=3600
|
|
547
|
+
\`\`\`
|
|
548
|
+
|
|
549
|
+
5. **HTTPS**: The endpoint MUST be served over HTTPS.
|
|
550
|
+
|
|
551
|
+
## Verification
|
|
552
|
+
|
|
553
|
+
\`\`\`bash
|
|
554
|
+
# Test the endpoint
|
|
555
|
+
curl -I https://${config.merchantDomain}/.well-known/ucp
|
|
556
|
+
|
|
557
|
+
# Fetch and validate
|
|
558
|
+
curl https://${config.merchantDomain}/.well-known/ucp | jq .
|
|
559
|
+
\`\`\`
|
|
560
|
+
|
|
561
|
+
## Validation
|
|
562
|
+
|
|
563
|
+
Run the UCP validator to check your deployment:
|
|
564
|
+
\`\`\`bash
|
|
565
|
+
ucp-validate --remote ${config.merchantDomain}
|
|
566
|
+
\`\`\`
|
|
567
|
+
`;
|
|
568
|
+
|
|
569
|
+
return {
|
|
570
|
+
filename: 'setup-instructions.md',
|
|
571
|
+
content,
|
|
572
|
+
contentType: 'markdown',
|
|
573
|
+
description: 'Generic static hosting setup instructions',
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Generate README with all options
|
|
579
|
+
*/
|
|
580
|
+
function generateReadme(config: HostingConfig): InstallArtifact {
|
|
581
|
+
const content = `# UCP Profile Installation - ${config.merchantDomain}
|
|
582
|
+
|
|
583
|
+
## Profile Information
|
|
584
|
+
|
|
585
|
+
- **Merchant ID**: ${config.merchantId}
|
|
586
|
+
- **Domain**: ${config.merchantDomain}
|
|
587
|
+
- **Hosting Mode**: ${config.mode}
|
|
588
|
+
${config.platform ? `- **Platform**: ${config.platform}` : ''}
|
|
589
|
+
|
|
590
|
+
## Quick Start
|
|
591
|
+
|
|
592
|
+
Your UCP Business Profile must be accessible at:
|
|
593
|
+
\`\`\`
|
|
594
|
+
https://${config.merchantDomain}/.well-known/ucp
|
|
595
|
+
\`\`\`
|
|
596
|
+
|
|
597
|
+
## Files Included
|
|
598
|
+
|
|
599
|
+
- \`ucp.json\` - Your UCP Business Profile
|
|
600
|
+
- Platform-specific configuration files (see below)
|
|
601
|
+
|
|
602
|
+
## Installation Steps
|
|
603
|
+
|
|
604
|
+
${getInstallationSteps(config)}
|
|
605
|
+
|
|
606
|
+
## Verification
|
|
607
|
+
|
|
608
|
+
After installation, verify your profile is accessible:
|
|
609
|
+
|
|
610
|
+
\`\`\`bash
|
|
611
|
+
# Check endpoint responds
|
|
612
|
+
curl -I https://${config.merchantDomain}/.well-known/ucp
|
|
613
|
+
|
|
614
|
+
# Validate response content
|
|
615
|
+
curl https://${config.merchantDomain}/.well-known/ucp | jq .
|
|
616
|
+
|
|
617
|
+
# Run full validation
|
|
618
|
+
npx ucp-profile-manager validate --remote ${config.merchantDomain}
|
|
619
|
+
\`\`\`
|
|
620
|
+
|
|
621
|
+
## Troubleshooting
|
|
622
|
+
|
|
623
|
+
### Common Issues
|
|
624
|
+
|
|
625
|
+
1. **404 Not Found**: Check file location and rewrite rules
|
|
626
|
+
2. **CORS Errors**: Ensure Access-Control-Allow-Origin header is set
|
|
627
|
+
3. **SSL Errors**: Profile must be served over HTTPS
|
|
628
|
+
4. **Invalid JSON**: Validate JSON syntax with \`jq\`
|
|
629
|
+
|
|
630
|
+
### Support
|
|
631
|
+
|
|
632
|
+
For assistance, contact: hello@ucp.tools
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
Generated by UCP Profile Manager v1.0
|
|
636
|
+
`;
|
|
637
|
+
|
|
638
|
+
return {
|
|
639
|
+
filename: 'README.md',
|
|
640
|
+
content,
|
|
641
|
+
contentType: 'markdown',
|
|
642
|
+
description: 'Installation guide and documentation',
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Get installation steps based on hosting config
|
|
648
|
+
*/
|
|
649
|
+
function getInstallationSteps(config: HostingConfig): string {
|
|
650
|
+
switch (config.mode) {
|
|
651
|
+
case 'static':
|
|
652
|
+
return `### Static File Hosting
|
|
653
|
+
|
|
654
|
+
1. Copy \`ucp.json\` to your web server
|
|
655
|
+
2. Apply the configuration from the platform-specific config file
|
|
656
|
+
3. Restart your web server if needed
|
|
657
|
+
4. Test the endpoint`;
|
|
658
|
+
|
|
659
|
+
case 'edge-worker':
|
|
660
|
+
return `### Edge Worker Deployment
|
|
661
|
+
|
|
662
|
+
1. Install Wrangler CLI: \`npm install -g wrangler\`
|
|
663
|
+
2. Authenticate: \`wrangler login\`
|
|
664
|
+
3. Update \`wrangler.toml\` with your zone settings
|
|
665
|
+
4. Deploy: \`npx wrangler deploy\`
|
|
666
|
+
5. Verify the route is active in Cloudflare dashboard`;
|
|
667
|
+
|
|
668
|
+
case 'reverse-proxy':
|
|
669
|
+
return `### Reverse Proxy Setup
|
|
670
|
+
|
|
671
|
+
1. Add the nginx configuration snippet to your server block
|
|
672
|
+
2. Test configuration: \`nginx -t\`
|
|
673
|
+
3. Reload nginx: \`sudo systemctl reload nginx\`
|
|
674
|
+
4. Verify the endpoint proxies correctly`;
|
|
675
|
+
|
|
676
|
+
default:
|
|
677
|
+
return `See the included configuration files for setup instructions.`;
|
|
678
|
+
}
|
|
679
|
+
}
|