@ucptools/validator 1.0.0 → 1.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/.claude/settings.local.json +60 -0
- package/.vercel/README.txt +11 -0
- package/.vercel/project.json +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +279 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/compliance/compliance-generator.d.ts +34 -0
- package/dist/compliance/compliance-generator.d.ts.map +1 -0
- package/dist/compliance/compliance-generator.js +320 -0
- package/dist/compliance/compliance-generator.js.map +1 -0
- package/dist/compliance/index.d.ts +8 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +17 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/compliance/templates.d.ts +34 -0
- package/dist/compliance/templates.d.ts.map +1 -0
- package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
- package/dist/compliance/templates.js.map +1 -0
- package/dist/compliance/types.d.ts +64 -0
- package/dist/compliance/types.d.ts.map +1 -0
- package/dist/compliance/types.js +64 -0
- package/dist/compliance/types.js.map +1 -0
- package/dist/db/index.d.ts +11 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +63 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +444 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +65 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
- package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
- package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +642 -726
- package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
- package/dist/feed-analyzer/index.d.ts +8 -0
- package/dist/feed-analyzer/index.d.ts.map +1 -0
- package/dist/feed-analyzer/index.js +19 -0
- package/dist/feed-analyzer/index.js.map +1 -0
- package/dist/feed-analyzer/types.d.ts +204 -0
- package/dist/feed-analyzer/types.d.ts.map +1 -0
- package/dist/feed-analyzer/types.js +162 -0
- package/dist/feed-analyzer/types.js.map +1 -0
- package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +13 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/key-generator.d.ts +24 -0
- package/dist/generator/key-generator.d.ts.map +1 -0
- package/dist/generator/key-generator.js +144 -0
- package/dist/generator/key-generator.js.map +1 -0
- package/dist/generator/profile-builder.d.ts +15 -0
- package/dist/generator/profile-builder.d.ts.map +1 -0
- package/dist/generator/profile-builder.js +338 -0
- package/dist/generator/profile-builder.js.map +1 -0
- package/dist/hosting/artifacts-generator.d.ts +10 -0
- package/dist/hosting/artifacts-generator.d.ts.map +1 -0
- package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
- package/dist/hosting/artifacts-generator.js.map +1 -0
- package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
- package/dist/hosting/index.d.ts.map +1 -0
- package/dist/hosting/index.js +10 -0
- package/dist/hosting/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +12 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/security-scanner.d.ts +10 -0
- package/dist/security/security-scanner.d.ts.map +1 -0
- package/dist/security/security-scanner.js +541 -0
- package/dist/security/security-scanner.js.map +1 -0
- package/dist/security/types.d.ts +48 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security/types.js +21 -0
- package/dist/security/types.js.map +1 -0
- package/dist/services/directory.d.ts +104 -0
- package/dist/services/directory.d.ts.map +1 -0
- package/dist/services/directory.js +333 -0
- package/dist/services/directory.js.map +1 -0
- package/dist/simulator/agent-simulator.d.ts +69 -0
- package/dist/simulator/agent-simulator.d.ts.map +1 -0
- package/{src/simulator/agent-simulator.ts → dist/simulator/agent-simulator.js} +650 -941
- package/dist/simulator/agent-simulator.js.map +1 -0
- package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
- package/dist/simulator/index.d.ts.map +1 -0
- package/dist/simulator/index.js +23 -0
- package/dist/simulator/index.js.map +1 -0
- package/{src/simulator/types.ts → dist/simulator/types.d.ts} +145 -170
- package/dist/simulator/types.d.ts.map +1 -0
- package/dist/simulator/types.js +18 -0
- package/dist/simulator/types.js.map +1 -0
- package/dist/types/generator.d.ts +106 -0
- package/dist/types/generator.d.ts.map +1 -0
- package/dist/types/generator.js +6 -0
- package/dist/types/generator.js.map +1 -0
- package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/ucp-profile.d.ts +103 -0
- package/dist/types/ucp-profile.d.ts.map +1 -0
- package/dist/types/ucp-profile.js +45 -0
- package/dist/types/ucp-profile.js.map +1 -0
- package/dist/types/validation.d.ts +68 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +32 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/validator/index.d.ts +26 -0
- package/dist/validator/index.d.ts.map +1 -0
- package/dist/validator/index.js +161 -0
- package/dist/validator/index.js.map +1 -0
- package/dist/validator/network-validator.d.ts +28 -0
- package/dist/validator/network-validator.d.ts.map +1 -0
- package/dist/validator/network-validator.js +319 -0
- package/dist/validator/network-validator.js.map +1 -0
- package/dist/validator/rules-validator.d.ts +11 -0
- package/dist/validator/rules-validator.d.ts.map +1 -0
- package/dist/validator/rules-validator.js +257 -0
- package/dist/validator/rules-validator.js.map +1 -0
- package/dist/validator/sdk-validator.d.ts +58 -0
- package/dist/validator/sdk-validator.d.ts.map +1 -0
- package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
- package/dist/validator/sdk-validator.js.map +1 -0
- package/dist/validator/structural-validator.d.ts +11 -0
- package/dist/validator/structural-validator.d.ts.map +1 -0
- package/dist/validator/structural-validator.js +415 -0
- package/dist/validator/structural-validator.js.map +1 -0
- package/package.json +1 -1
- package/publish-output.txt +0 -0
- package/CLAUDE.md +0 -109
- package/api/analyze-feed.js +0 -140
- package/api/badge.js +0 -185
- package/api/benchmark.js +0 -177
- package/api/directory-stats.ts +0 -29
- package/api/directory.ts +0 -73
- package/api/generate-compliance.js +0 -143
- package/api/generate-schema.js +0 -457
- package/api/generate.js +0 -132
- package/api/security-scan.js +0 -133
- package/api/simulate.js +0 -187
- package/api/tsconfig.json +0 -10
- package/api/validate.js +0 -1351
- package/apify-actor/.actor/actor.json +0 -68
- package/apify-actor/.actor/input_schema.json +0 -32
- package/apify-actor/APIFY-STORE-LISTING.md +0 -412
- package/apify-actor/Dockerfile +0 -8
- package/apify-actor/README.md +0 -166
- package/apify-actor/main.ts +0 -111
- package/apify-actor/package.json +0 -17
- package/apify-actor/src/main.js +0 -199
- package/docs/BRAND-IDENTITY.md +0 -238
- package/docs/BRAND-STYLE-GUIDE.md +0 -356
- package/drizzle/0000_black_king_cobra.sql +0 -39
- package/drizzle/meta/0000_snapshot.json +0 -309
- package/drizzle/meta/_journal.json +0 -13
- package/drizzle.config.ts +0 -10
- package/public/.well-known/ucp +0 -25
- 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 +0 -321
- package/public/directory.html +0 -701
- 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 +0 -743
- package/public/guides/fastucp.html +0 -838
- package/public/guides/magento.html +0 -779
- package/public/guides/shopify.html +0 -726
- package/public/guides/squarespace.html +0 -749
- package/public/guides/wix.html +0 -747
- package/public/guides/woocommerce.html +0 -733
- package/public/index.html +0 -3835
- package/public/learn.html +0 -396
- 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 +0 -6
- package/public/site.webmanifest +0 -31
- package/public/sitemap.xml +0 -69
- 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 +0 -410
- package/scripts/generate-favicons.js +0 -44
- package/scripts/generate-ico.js +0 -23
- package/scripts/generate-og-image.js +0 -45
- package/scripts/reset-db.ts +0 -77
- package/scripts/seed-db.ts +0 -71
- package/scripts/setup-benchmark-db.js +0 -70
- package/src/api/server.ts +0 -266
- package/src/cli/index.ts +0 -302
- package/src/compliance/compliance-generator.ts +0 -452
- package/src/compliance/index.ts +0 -28
- package/src/compliance/types.ts +0 -170
- package/src/db/index.ts +0 -28
- package/src/db/schema.ts +0 -84
- package/src/feed-analyzer/index.ts +0 -34
- package/src/feed-analyzer/types.ts +0 -354
- package/src/generator/key-generator.ts +0 -124
- package/src/generator/profile-builder.ts +0 -402
- package/src/index.ts +0 -105
- package/src/security/security-scanner.ts +0 -604
- package/src/security/types.ts +0 -55
- package/src/services/directory.ts +0 -434
- package/src/types/generator.ts +0 -140
- package/src/types/ucp-profile.ts +0 -140
- package/src/types/validation.ts +0 -89
- package/src/validator/index.ts +0 -194
- package/src/validator/network-validator.ts +0 -417
- package/src/validator/rules-validator.ts +0 -297
- package/src/validator/structural-validator.ts +0 -476
- package/tests/fixtures/non-compliant-profile.json +0 -25
- package/tests/fixtures/official-sample-profile.json +0 -75
- package/tests/integration/benchmark.test.ts +0 -207
- package/tests/integration/database.test.ts +0 -163
- package/tests/integration/directory-api.test.ts +0 -268
- package/tests/integration/simulate-api.test.ts +0 -230
- package/tests/integration/validate-api.test.ts +0 -269
- package/tests/setup.ts +0 -15
- package/tests/unit/agent-simulator.test.ts +0 -575
- package/tests/unit/compliance-generator.test.ts +0 -374
- package/tests/unit/directory-service.test.ts +0 -272
- package/tests/unit/feed-analyzer.test.ts +0 -517
- package/tests/unit/lint-suggestions.test.ts +0 -423
- package/tests/unit/official-samples.test.ts +0 -211
- package/tests/unit/pdf-report.test.ts +0 -390
- package/tests/unit/sdk-validator.test.ts +0 -531
- package/tests/unit/security-scanner.test.ts +0 -410
- package/tests/unit/validation.test.ts +0 -390
- package/vercel.json +0 -34
- package/vitest.config.ts +0 -22
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Setup script for benchmark database tables
|
|
3
|
-
* Run with: node scripts/setup-benchmark-db.js
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import pg from 'pg';
|
|
7
|
-
|
|
8
|
-
const { Pool } = pg;
|
|
9
|
-
|
|
10
|
-
async function setup() {
|
|
11
|
-
const pool = new Pool({
|
|
12
|
-
connectionString: process.env.DATABASE_URL,
|
|
13
|
-
ssl: { rejectUnauthorized: false }
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
console.log('Creating benchmark tables...');
|
|
18
|
-
|
|
19
|
-
// Create benchmark_stats table for aggregate statistics
|
|
20
|
-
await pool.query(`
|
|
21
|
-
CREATE TABLE IF NOT EXISTS benchmark_stats (
|
|
22
|
-
id SERIAL PRIMARY KEY,
|
|
23
|
-
score_bucket INT NOT NULL CHECK (score_bucket >= 0 AND score_bucket <= 100),
|
|
24
|
-
count INT DEFAULT 0,
|
|
25
|
-
UNIQUE(score_bucket)
|
|
26
|
-
);
|
|
27
|
-
`);
|
|
28
|
-
|
|
29
|
-
// Initialize score buckets (0-10, 10-20, ..., 90-100)
|
|
30
|
-
for (let bucket = 0; bucket <= 100; bucket += 10) {
|
|
31
|
-
await pool.query(`
|
|
32
|
-
INSERT INTO benchmark_stats (score_bucket, count)
|
|
33
|
-
VALUES ($1, 0)
|
|
34
|
-
ON CONFLICT (score_bucket) DO NOTHING;
|
|
35
|
-
`, [bucket]);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Create summary table for quick stats
|
|
39
|
-
await pool.query(`
|
|
40
|
-
CREATE TABLE IF NOT EXISTS benchmark_summary (
|
|
41
|
-
id INT PRIMARY KEY DEFAULT 1,
|
|
42
|
-
total_validations INT DEFAULT 0,
|
|
43
|
-
avg_score DECIMAL(5,2) DEFAULT 0,
|
|
44
|
-
updated_at TIMESTAMP DEFAULT NOW(),
|
|
45
|
-
CHECK (id = 1)
|
|
46
|
-
);
|
|
47
|
-
`);
|
|
48
|
-
|
|
49
|
-
// Initialize summary row
|
|
50
|
-
await pool.query(`
|
|
51
|
-
INSERT INTO benchmark_summary (id, total_validations, avg_score)
|
|
52
|
-
VALUES (1, 0, 0)
|
|
53
|
-
ON CONFLICT (id) DO NOTHING;
|
|
54
|
-
`);
|
|
55
|
-
|
|
56
|
-
console.log('✓ Benchmark tables created successfully');
|
|
57
|
-
|
|
58
|
-
// Verify setup
|
|
59
|
-
const result = await pool.query('SELECT * FROM benchmark_stats ORDER BY score_bucket');
|
|
60
|
-
console.log('Score buckets:', result.rows.length);
|
|
61
|
-
|
|
62
|
-
} catch (error) {
|
|
63
|
-
console.error('Error setting up database:', error);
|
|
64
|
-
process.exit(1);
|
|
65
|
-
} finally {
|
|
66
|
-
await pool.end();
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
setup();
|
package/src/api/server.ts
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UCP Profile Manager API Server
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import express from 'express';
|
|
6
|
-
import type { Request, Response, NextFunction } from 'express';
|
|
7
|
-
import { nanoid } from 'nanoid';
|
|
8
|
-
import pino from 'pino';
|
|
9
|
-
import { buildProfile, generateMinimalProfile } from '../generator/index.js';
|
|
10
|
-
import { validateProfile, validateRemote, validateQuick, validateJsonString } from '../validator/index.js';
|
|
11
|
-
import { generateHostingArtifacts } from '../hosting/index.js';
|
|
12
|
-
import type { GeneratorInput, HostingConfig } from '../types/generator.js';
|
|
13
|
-
import type { ValidationOptions } from '../types/validation.js';
|
|
14
|
-
|
|
15
|
-
// Logger
|
|
16
|
-
const logger = pino({
|
|
17
|
-
level: process.env.LOG_LEVEL || 'info',
|
|
18
|
-
transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty' } : undefined,
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const app = express();
|
|
22
|
-
app.use(express.json({ limit: '1mb' }));
|
|
23
|
-
|
|
24
|
-
// Request logging middleware
|
|
25
|
-
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
26
|
-
const requestId = nanoid(8);
|
|
27
|
-
(req as any).requestId = requestId;
|
|
28
|
-
logger.info({ requestId, method: req.method, path: req.path }, 'Request received');
|
|
29
|
-
next();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// CORS middleware
|
|
33
|
-
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
34
|
-
res.header('Access-Control-Allow-Origin', '*');
|
|
35
|
-
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
36
|
-
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
37
|
-
if (req.method === 'OPTIONS') {
|
|
38
|
-
return res.sendStatus(204);
|
|
39
|
-
}
|
|
40
|
-
next();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Health check
|
|
44
|
-
app.get('/health', (req: Request, res: Response) => {
|
|
45
|
-
res.json({ status: 'ok', version: '1.0.0' });
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// API version prefix
|
|
49
|
-
const api = express.Router();
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Generate a UCP profile
|
|
53
|
-
* POST /v1/profiles/generate
|
|
54
|
-
*/
|
|
55
|
-
api.post('/profiles/generate', async (req: Request, res: Response) => {
|
|
56
|
-
try {
|
|
57
|
-
const input: GeneratorInput = req.body;
|
|
58
|
-
|
|
59
|
-
// Validate required fields
|
|
60
|
-
if (!input.merchant?.merchantId || !input.merchant?.primaryDomain) {
|
|
61
|
-
return res.status(400).json({
|
|
62
|
-
error: 'Missing required fields: merchant.merchantId and merchant.primaryDomain',
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!input.transport?.rest?.endpoint) {
|
|
67
|
-
return res.status(400).json({
|
|
68
|
-
error: 'Missing required field: transport.rest.endpoint',
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Set defaults
|
|
73
|
-
input.capabilities = input.capabilities || { checkout: true, order: false, fulfillment: false, discount: false };
|
|
74
|
-
|
|
75
|
-
const result = await buildProfile(input);
|
|
76
|
-
|
|
77
|
-
res.json({
|
|
78
|
-
profile: result.profile,
|
|
79
|
-
signing_key_pair: result.signingKeyPair ? {
|
|
80
|
-
public_key: result.signingKeyPair.publicKey,
|
|
81
|
-
private_key_pem: result.signingKeyPair.privateKey,
|
|
82
|
-
} : undefined,
|
|
83
|
-
});
|
|
84
|
-
} catch (error) {
|
|
85
|
-
logger.error({ error }, 'Profile generation failed');
|
|
86
|
-
res.status(500).json({ error: 'Failed to generate profile' });
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Generate minimal profile
|
|
92
|
-
* POST /v1/profiles/generate-minimal
|
|
93
|
-
*/
|
|
94
|
-
api.post('/profiles/generate-minimal', (req: Request, res: Response) => {
|
|
95
|
-
try {
|
|
96
|
-
const { endpoint, version } = req.body;
|
|
97
|
-
|
|
98
|
-
if (!endpoint) {
|
|
99
|
-
return res.status(400).json({ error: 'Missing required field: endpoint' });
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const profile = generateMinimalProfile(endpoint, version);
|
|
103
|
-
res.json({ profile });
|
|
104
|
-
} catch (error) {
|
|
105
|
-
logger.error({ error }, 'Minimal profile generation failed');
|
|
106
|
-
res.status(500).json({ error: 'Failed to generate minimal profile' });
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Validate a profile (from request body)
|
|
112
|
-
* POST /v1/profiles/validate
|
|
113
|
-
*/
|
|
114
|
-
api.post('/profiles/validate', async (req: Request, res: Response) => {
|
|
115
|
-
try {
|
|
116
|
-
const { profile, options } = req.body as {
|
|
117
|
-
profile: unknown;
|
|
118
|
-
options?: ValidationOptions;
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
if (!profile) {
|
|
122
|
-
return res.status(400).json({ error: 'Missing required field: profile' });
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const report = await validateProfile(profile, options || {});
|
|
126
|
-
res.json(report);
|
|
127
|
-
} catch (error) {
|
|
128
|
-
logger.error({ error }, 'Profile validation failed');
|
|
129
|
-
res.status(500).json({ error: 'Validation failed' });
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Quick validate (structural + rules only)
|
|
135
|
-
* POST /v1/profiles/validate-quick
|
|
136
|
-
*/
|
|
137
|
-
api.post('/profiles/validate-quick', (req: Request, res: Response) => {
|
|
138
|
-
try {
|
|
139
|
-
const { profile } = req.body;
|
|
140
|
-
|
|
141
|
-
if (!profile) {
|
|
142
|
-
return res.status(400).json({ error: 'Missing required field: profile' });
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const report = validateQuick(profile);
|
|
146
|
-
res.json(report);
|
|
147
|
-
} catch (error) {
|
|
148
|
-
logger.error({ error }, 'Quick validation failed');
|
|
149
|
-
res.status(500).json({ error: 'Validation failed' });
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Validate remote profile
|
|
155
|
-
* POST /v1/profiles/validate-remote
|
|
156
|
-
*/
|
|
157
|
-
api.post('/profiles/validate-remote', async (req: Request, res: Response) => {
|
|
158
|
-
try {
|
|
159
|
-
const { domain, options } = req.body as {
|
|
160
|
-
domain: string;
|
|
161
|
-
options?: ValidationOptions;
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
if (!domain) {
|
|
165
|
-
return res.status(400).json({ error: 'Missing required field: domain' });
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const report = await validateRemote(domain, options || {});
|
|
169
|
-
res.json(report);
|
|
170
|
-
} catch (error) {
|
|
171
|
-
logger.error({ error }, 'Remote validation failed');
|
|
172
|
-
res.status(500).json({ error: 'Remote validation failed' });
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Validate JSON string
|
|
178
|
-
* POST /v1/profiles/validate-json
|
|
179
|
-
*/
|
|
180
|
-
api.post('/profiles/validate-json', async (req: Request, res: Response) => {
|
|
181
|
-
try {
|
|
182
|
-
const { json, options } = req.body as {
|
|
183
|
-
json: string;
|
|
184
|
-
options?: ValidationOptions;
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
if (!json) {
|
|
188
|
-
return res.status(400).json({ error: 'Missing required field: json' });
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const report = await validateJsonString(json, options || {});
|
|
192
|
-
res.json(report);
|
|
193
|
-
} catch (error) {
|
|
194
|
-
logger.error({ error }, 'JSON validation failed');
|
|
195
|
-
res.status(500).json({ error: 'Validation failed' });
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Generate hosting artifacts
|
|
201
|
-
* POST /v1/hosting/artifacts
|
|
202
|
-
*/
|
|
203
|
-
api.post('/hosting/artifacts', (req: Request, res: Response) => {
|
|
204
|
-
try {
|
|
205
|
-
const { config, profile_json } = req.body as {
|
|
206
|
-
config: HostingConfig;
|
|
207
|
-
profile_json: string;
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
if (!config?.merchantId || !config?.merchantDomain || !config?.mode) {
|
|
211
|
-
return res.status(400).json({
|
|
212
|
-
error: 'Missing required config fields: merchantId, merchantDomain, mode',
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (!profile_json) {
|
|
217
|
-
return res.status(400).json({ error: 'Missing required field: profile_json' });
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const artifacts = generateHostingArtifacts(config, profile_json);
|
|
221
|
-
res.json({ artifacts });
|
|
222
|
-
} catch (error) {
|
|
223
|
-
logger.error({ error }, 'Artifact generation failed');
|
|
224
|
-
res.status(500).json({ error: 'Failed to generate artifacts' });
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Hosted profile endpoint (for edge worker proxying)
|
|
230
|
-
* GET /hosted/:merchantId/ucp.json
|
|
231
|
-
*/
|
|
232
|
-
api.get('/hosted/:merchantId/ucp.json', async (req: Request, res: Response) => {
|
|
233
|
-
const { merchantId } = req.params;
|
|
234
|
-
|
|
235
|
-
// TODO: Implement database lookup for stored profiles
|
|
236
|
-
// For now, return 404 as this requires database integration
|
|
237
|
-
res.status(404).json({
|
|
238
|
-
error: 'Profile not found',
|
|
239
|
-
hint: 'This endpoint requires database integration. Use /v1/profiles/generate to create profiles.',
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
// Mount API routes
|
|
244
|
-
app.use('/v1', api);
|
|
245
|
-
|
|
246
|
-
// Error handler
|
|
247
|
-
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
248
|
-
logger.error({ error: err }, 'Unhandled error');
|
|
249
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// 404 handler
|
|
253
|
-
app.use((req: Request, res: Response) => {
|
|
254
|
-
res.status(404).json({ error: 'Not found' });
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// Start server
|
|
258
|
-
const PORT = process.env.PORT || 3000;
|
|
259
|
-
|
|
260
|
-
if (process.env.NODE_ENV !== 'test') {
|
|
261
|
-
app.listen(PORT, () => {
|
|
262
|
-
logger.info({ port: PORT }, 'UCP Profile Manager API started');
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
export { app };
|
package/src/cli/index.ts
DELETED
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* UCP Profile Validator CLI
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { program } from 'commander';
|
|
7
|
-
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
8
|
-
import { resolve } from 'path';
|
|
9
|
-
import { validateProfile, validateRemote, validateQuick } from '../validator/index.js';
|
|
10
|
-
import { buildProfile, generateMinimalProfile } from '../generator/index.js';
|
|
11
|
-
import { generateHostingArtifacts } from '../hosting/index.js';
|
|
12
|
-
import type { ValidationReport } from '../types/validation.js';
|
|
13
|
-
import type { GeneratorInput, HostingConfig, HostingMode, HostingPlatform } from '../types/generator.js';
|
|
14
|
-
|
|
15
|
-
// Colors for terminal output (simple implementation without chalk for ESM)
|
|
16
|
-
const colors = {
|
|
17
|
-
red: (s: string) => `\x1b[31m${s}\x1b[0m`,
|
|
18
|
-
green: (s: string) => `\x1b[32m${s}\x1b[0m`,
|
|
19
|
-
yellow: (s: string) => `\x1b[33m${s}\x1b[0m`,
|
|
20
|
-
blue: (s: string) => `\x1b[34m${s}\x1b[0m`,
|
|
21
|
-
gray: (s: string) => `\x1b[90m${s}\x1b[0m`,
|
|
22
|
-
bold: (s: string) => `\x1b[1m${s}\x1b[0m`,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
program
|
|
26
|
-
.name('ucp-validate')
|
|
27
|
-
.description('UCP Profile Validator and Generator CLI')
|
|
28
|
-
.version('1.0.0');
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Validate command
|
|
32
|
-
*/
|
|
33
|
-
program
|
|
34
|
-
.command('validate')
|
|
35
|
-
.description('Validate a UCP profile')
|
|
36
|
-
.option('-f, --file <path>', 'Path to local JSON file')
|
|
37
|
-
.option('-r, --remote <domain>', 'Remote domain to fetch profile from')
|
|
38
|
-
.option('-q, --quick', 'Quick validation (no network checks)')
|
|
39
|
-
.option('-o, --output <path>', 'Output report to file')
|
|
40
|
-
.option('--json', 'Output as JSON')
|
|
41
|
-
.action(async (options) => {
|
|
42
|
-
try {
|
|
43
|
-
let report: ValidationReport;
|
|
44
|
-
|
|
45
|
-
if (options.remote) {
|
|
46
|
-
console.log(colors.blue(`Fetching profile from https://${options.remote}/.well-known/ucp...`));
|
|
47
|
-
report = await validateRemote(options.remote, {
|
|
48
|
-
skipNetworkChecks: options.quick,
|
|
49
|
-
});
|
|
50
|
-
} else if (options.file) {
|
|
51
|
-
const filePath = resolve(options.file);
|
|
52
|
-
if (!existsSync(filePath)) {
|
|
53
|
-
console.error(colors.red(`File not found: ${filePath}`));
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
57
|
-
const profile = JSON.parse(content);
|
|
58
|
-
|
|
59
|
-
if (options.quick) {
|
|
60
|
-
report = validateQuick(profile);
|
|
61
|
-
} else {
|
|
62
|
-
report = await validateProfile(profile);
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
console.error(colors.red('Please specify --file or --remote'));
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Output
|
|
70
|
-
if (options.json) {
|
|
71
|
-
console.log(JSON.stringify(report, null, 2));
|
|
72
|
-
} else {
|
|
73
|
-
printValidationReport(report);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (options.output) {
|
|
77
|
-
writeFileSync(options.output, JSON.stringify(report, null, 2));
|
|
78
|
-
console.log(colors.gray(`\nReport saved to: ${options.output}`));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
process.exit(report.ok ? 0 : 1);
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.error(colors.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Generate command
|
|
90
|
-
*/
|
|
91
|
-
program
|
|
92
|
-
.command('generate')
|
|
93
|
-
.description('Generate a new UCP profile')
|
|
94
|
-
.requiredOption('-d, --domain <domain>', 'Primary domain (e.g., merchant.com)')
|
|
95
|
-
.requiredOption('-e, --endpoint <url>', 'REST API endpoint URL')
|
|
96
|
-
.option('-i, --id <id>', 'Merchant ID (auto-generated if not provided)')
|
|
97
|
-
.option('--order', 'Enable Order capability')
|
|
98
|
-
.option('--fulfillment', 'Enable Fulfillment capability')
|
|
99
|
-
.option('--discount', 'Enable Discount capability')
|
|
100
|
-
.option('-o, --output <path>', 'Output file path', 'ucp.json')
|
|
101
|
-
.option('--json', 'Output to stdout as JSON')
|
|
102
|
-
.action(async (options) => {
|
|
103
|
-
try {
|
|
104
|
-
const input: GeneratorInput = {
|
|
105
|
-
merchant: {
|
|
106
|
-
merchantId: options.id || `merchant-${Date.now()}`,
|
|
107
|
-
primaryDomain: options.domain,
|
|
108
|
-
},
|
|
109
|
-
transport: {
|
|
110
|
-
rest: {
|
|
111
|
-
endpoint: options.endpoint,
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
capabilities: {
|
|
115
|
-
checkout: true,
|
|
116
|
-
order: options.order || false,
|
|
117
|
-
fulfillment: options.fulfillment || false,
|
|
118
|
-
discount: options.discount || false,
|
|
119
|
-
},
|
|
120
|
-
security: {
|
|
121
|
-
generateSigningKeys: options.order || false,
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const result = await buildProfile(input);
|
|
126
|
-
|
|
127
|
-
if (options.json) {
|
|
128
|
-
console.log(result.profileJson);
|
|
129
|
-
} else {
|
|
130
|
-
writeFileSync(options.output, result.profileJson);
|
|
131
|
-
console.log(colors.green(`✓ Profile generated: ${options.output}`));
|
|
132
|
-
|
|
133
|
-
if (result.signingKeyPair) {
|
|
134
|
-
const keyFile = options.output.replace('.json', '-private-key.pem');
|
|
135
|
-
writeFileSync(keyFile, result.signingKeyPair.privateKey);
|
|
136
|
-
console.log(colors.yellow(`⚠ Private key saved: ${keyFile}`));
|
|
137
|
-
console.log(colors.gray(' Keep this file secure! Do not commit to version control.'));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
console.log(colors.blue('\nNext steps:'));
|
|
141
|
-
console.log(' 1. Review the generated profile');
|
|
142
|
-
console.log(' 2. Run: ucp-validate validate -f ' + options.output);
|
|
143
|
-
console.log(' 3. Deploy to https://' + options.domain + '/.well-known/ucp');
|
|
144
|
-
}
|
|
145
|
-
} catch (error) {
|
|
146
|
-
console.error(colors.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
147
|
-
process.exit(1);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Generate minimal profile
|
|
153
|
-
*/
|
|
154
|
-
program
|
|
155
|
-
.command('generate-minimal')
|
|
156
|
-
.description('Generate a minimal starter profile')
|
|
157
|
-
.requiredOption('-e, --endpoint <url>', 'REST API endpoint URL')
|
|
158
|
-
.option('-o, --output <path>', 'Output file path', 'ucp.json')
|
|
159
|
-
.action((options) => {
|
|
160
|
-
try {
|
|
161
|
-
const profile = generateMinimalProfile(options.endpoint);
|
|
162
|
-
const json = JSON.stringify(profile, null, 2);
|
|
163
|
-
|
|
164
|
-
writeFileSync(options.output, json);
|
|
165
|
-
console.log(colors.green(`✓ Minimal profile generated: ${options.output}`));
|
|
166
|
-
} catch (error) {
|
|
167
|
-
console.error(colors.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
168
|
-
process.exit(1);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Generate hosting artifacts
|
|
174
|
-
*/
|
|
175
|
-
program
|
|
176
|
-
.command('hosting')
|
|
177
|
-
.description('Generate hosting configuration files')
|
|
178
|
-
.requiredOption('-f, --file <path>', 'Path to profile JSON file')
|
|
179
|
-
.requiredOption('-d, --domain <domain>', 'Merchant domain')
|
|
180
|
-
.requiredOption('-m, --mode <mode>', 'Hosting mode: static, edge-worker, reverse-proxy')
|
|
181
|
-
.option('-p, --platform <platform>', 'Platform: nginx, apache, vercel, netlify, cloudflare-worker, s3-cloudfront')
|
|
182
|
-
.option('-i, --id <id>', 'Merchant ID')
|
|
183
|
-
.option('-o, --output <dir>', 'Output directory', './ucp-hosting')
|
|
184
|
-
.action((options) => {
|
|
185
|
-
try {
|
|
186
|
-
const filePath = resolve(options.file);
|
|
187
|
-
if (!existsSync(filePath)) {
|
|
188
|
-
console.error(colors.red(`File not found: ${filePath}`));
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const profileJson = readFileSync(filePath, 'utf-8');
|
|
193
|
-
|
|
194
|
-
const config: HostingConfig = {
|
|
195
|
-
mode: options.mode as HostingMode,
|
|
196
|
-
platform: options.platform as HostingPlatform,
|
|
197
|
-
merchantId: options.id || `merchant-${Date.now()}`,
|
|
198
|
-
merchantDomain: options.domain,
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const artifacts = generateHostingArtifacts(config, profileJson);
|
|
202
|
-
|
|
203
|
-
// Create output directory
|
|
204
|
-
const outputDir = resolve(options.output);
|
|
205
|
-
if (!existsSync(outputDir)) {
|
|
206
|
-
const { mkdirSync } = require('fs');
|
|
207
|
-
mkdirSync(outputDir, { recursive: true });
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Write artifacts
|
|
211
|
-
for (const artifact of artifacts) {
|
|
212
|
-
const artifactPath = resolve(outputDir, artifact.filename);
|
|
213
|
-
// Create subdirectories if needed
|
|
214
|
-
const dir = artifactPath.substring(0, artifactPath.lastIndexOf('/'));
|
|
215
|
-
if (dir && !existsSync(dir)) {
|
|
216
|
-
const { mkdirSync } = require('fs');
|
|
217
|
-
mkdirSync(dir, { recursive: true });
|
|
218
|
-
}
|
|
219
|
-
writeFileSync(artifactPath, artifact.content);
|
|
220
|
-
console.log(colors.green(`✓ ${artifact.filename}`));
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
console.log(colors.blue(`\nArtifacts generated in: ${outputDir}`));
|
|
224
|
-
console.log(colors.gray('See README.md for installation instructions.'));
|
|
225
|
-
} catch (error) {
|
|
226
|
-
console.error(colors.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
227
|
-
process.exit(1);
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Print validation report to console
|
|
233
|
-
*/
|
|
234
|
-
function printValidationReport(report: ValidationReport): void {
|
|
235
|
-
console.log('\n' + colors.bold('UCP Profile Validation Report'));
|
|
236
|
-
console.log('═'.repeat(50));
|
|
237
|
-
|
|
238
|
-
if (report.profile_url) {
|
|
239
|
-
console.log(colors.gray(`Profile: ${report.profile_url}`));
|
|
240
|
-
}
|
|
241
|
-
if (report.ucp_version) {
|
|
242
|
-
console.log(colors.gray(`UCP Version: ${report.ucp_version}`));
|
|
243
|
-
}
|
|
244
|
-
console.log(colors.gray(`Validated: ${report.validated_at}`));
|
|
245
|
-
console.log(colors.gray(`Mode: ${report.validation_mode}`));
|
|
246
|
-
console.log('');
|
|
247
|
-
|
|
248
|
-
// Summary
|
|
249
|
-
if (report.ok) {
|
|
250
|
-
console.log(colors.green('✓ Validation PASSED'));
|
|
251
|
-
} else {
|
|
252
|
-
console.log(colors.red('✗ Validation FAILED'));
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Issue counts
|
|
256
|
-
const errors = report.issues.filter(i => i.severity === 'error');
|
|
257
|
-
const warnings = report.issues.filter(i => i.severity === 'warn');
|
|
258
|
-
const info = report.issues.filter(i => i.severity === 'info');
|
|
259
|
-
|
|
260
|
-
if (report.issues.length === 0) {
|
|
261
|
-
console.log(colors.green('\nNo issues found!'));
|
|
262
|
-
} else {
|
|
263
|
-
console.log(`\n${errors.length} error(s), ${warnings.length} warning(s), ${info.length} info`);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Print issues
|
|
267
|
-
if (errors.length > 0) {
|
|
268
|
-
console.log('\n' + colors.red('Errors:'));
|
|
269
|
-
for (const issue of errors) {
|
|
270
|
-
printIssue(issue);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (warnings.length > 0) {
|
|
275
|
-
console.log('\n' + colors.yellow('Warnings:'));
|
|
276
|
-
for (const issue of warnings) {
|
|
277
|
-
printIssue(issue);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (info.length > 0) {
|
|
282
|
-
console.log('\n' + colors.blue('Info:'));
|
|
283
|
-
for (const issue of info) {
|
|
284
|
-
printIssue(issue);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
console.log('');
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function printIssue(issue: { severity: string; code: string; path: string; message: string; hint?: string }): void {
|
|
292
|
-
const icon = issue.severity === 'error' ? '✗' : issue.severity === 'warn' ? '⚠' : 'ℹ';
|
|
293
|
-
const color = issue.severity === 'error' ? colors.red : issue.severity === 'warn' ? colors.yellow : colors.blue;
|
|
294
|
-
|
|
295
|
-
console.log(` ${color(icon)} [${issue.code}] ${issue.message}`);
|
|
296
|
-
console.log(colors.gray(` Path: ${issue.path}`));
|
|
297
|
-
if (issue.hint) {
|
|
298
|
-
console.log(colors.gray(` Hint: ${issue.hint}`));
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
program.parse();
|