@tamyla/clodo-framework 2.0.0 ā 2.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/CHANGELOG.md +7 -0
- package/dist/deployment/validator.js +112 -0
- package/dist/deployment/wrangler-deployer.js +47 -2
- package/dist/service-management/InputCollector.js +208 -20
- package/dist/shared/deployment/validator.js +112 -0
- package/dist/shared/monitoring/health-checker.js +200 -0
- package/dist/shared/utils/interactive-utils.js +506 -0
- package/dist/utils/ErrorHandler.js +113 -0
- package/dist/utils/scripts-manager.js +167 -0
- package/dist/utils/ui-structures-loader.js +110 -0
- package/docs/README.md +20 -67
- package/docs/api-reference.md +928 -0
- package/package.json +2 -4
- package/docs/FRAMEWORK-ARCHITECTURE-OVERVIEW.md +0 -206
- package/docs/INTEGRATION_GUIDE.md +0 -2045
- package/docs/deployment/deployment-guide.md +0 -540
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [2.0.1](https://github.com/tamylaa/clodo-framework/compare/v2.0.0...v2.0.1) (2025-10-10)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* reorganize documentation structure and fix package distribution ([598d44b](https://github.com/tamylaa/clodo-framework/commit/598d44b669f65c222d215ba33d0361d736a15ac9))
|
|
7
|
+
|
|
1
8
|
# [2.0.0](https://github.com/tamylaa/clodo-framework/compare/v1.0.0...v2.0.0) (2025-10-10)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -270,6 +270,9 @@ export class DeploymentValidator {
|
|
|
270
270
|
|
|
271
271
|
// Validate environment configuration
|
|
272
272
|
await this.validateEnvironmentConfig();
|
|
273
|
+
|
|
274
|
+
// Validate D1 database bindings
|
|
275
|
+
await this.validateD1Configuration();
|
|
273
276
|
this.results.categories.configuration = 'passed';
|
|
274
277
|
this.addResult('configuration', 'Configuration files validated', 'success');
|
|
275
278
|
} catch (error) {
|
|
@@ -457,6 +460,112 @@ export class DeploymentValidator {
|
|
|
457
460
|
console.log(` ā
Environment: ${this.environment} configuration valid`);
|
|
458
461
|
this.addResult('environment', `${this.environment} environment validated`, 'info');
|
|
459
462
|
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Validate D1 database configuration across all services
|
|
466
|
+
*/
|
|
467
|
+
async validateD1Configuration() {
|
|
468
|
+
console.log(' šļø Validating D1 database configuration...');
|
|
469
|
+
try {
|
|
470
|
+
// Import WranglerDeployer for D1 validation capabilities
|
|
471
|
+
const {
|
|
472
|
+
WranglerDeployer
|
|
473
|
+
} = await import('../../../src/deployment/wrangler-deployer.js');
|
|
474
|
+
|
|
475
|
+
// Check if this is a framework-level validation (no specific service)
|
|
476
|
+
if (!this.options?.servicePath) {
|
|
477
|
+
console.log(' āļø Skipping D1 validation (framework-level validation)');
|
|
478
|
+
this.addResult('d1-config', 'D1 validation skipped for framework-level validation', 'info');
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Create deployer instance for the specific service
|
|
483
|
+
const deployer = new WranglerDeployer({
|
|
484
|
+
cwd: this.options.servicePath,
|
|
485
|
+
environment: this.environment
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// Discover deployment configuration
|
|
489
|
+
const deployConfig = await deployer.discoverDeploymentConfig(this.environment);
|
|
490
|
+
if (!deployConfig.configPath) {
|
|
491
|
+
console.log(' āļø No wrangler.toml found, skipping D1 validation');
|
|
492
|
+
this.addResult('d1-config', 'No wrangler.toml found', 'info');
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Validate D1 bindings
|
|
497
|
+
const d1Validation = await deployer.validateD1Bindings(deployConfig);
|
|
498
|
+
if (d1Validation.valid) {
|
|
499
|
+
if (d1Validation.bindings.length === 0) {
|
|
500
|
+
console.log(' ā
No D1 databases configured');
|
|
501
|
+
this.addResult('d1-config', 'No D1 databases configured', 'info');
|
|
502
|
+
} else {
|
|
503
|
+
console.log(` ā
All ${d1Validation.summary.total} D1 database bindings valid`);
|
|
504
|
+
this.addResult('d1-config', `${d1Validation.summary.total} D1 bindings validated`, 'success');
|
|
505
|
+
|
|
506
|
+
// Log details about each binding
|
|
507
|
+
d1Validation.bindings.forEach(binding => {
|
|
508
|
+
if (binding.valid) {
|
|
509
|
+
console.log(` ā
${binding.binding}: ${binding.database_name}`);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
console.log(` ā D1 validation failed: ${d1Validation.summary.invalid} invalid bindings`);
|
|
515
|
+
|
|
516
|
+
// Log details about invalid bindings
|
|
517
|
+
d1Validation.invalidBindings.forEach(binding => {
|
|
518
|
+
console.log(` ā ${binding.binding || 'unnamed'}: ${binding.issues.join(', ')}`);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// Add warning with suggestions
|
|
522
|
+
const suggestions = this.generateD1Suggestions(d1Validation);
|
|
523
|
+
this.addWarning('d1-config', `D1 configuration issues found. ${suggestions}`);
|
|
524
|
+
|
|
525
|
+
// Don't fail validation entirely for D1 issues if not in strict mode
|
|
526
|
+
if (!this.strictMode) {
|
|
527
|
+
console.log(' ā ļø Continuing with warnings (non-strict mode)');
|
|
528
|
+
this.addResult('d1-config', 'D1 issues found but continuing', 'warning');
|
|
529
|
+
} else {
|
|
530
|
+
throw new Error(`D1 validation failed: ${d1Validation.summary.invalid} invalid bindings`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (error.message.includes('D1 validation failed')) {
|
|
535
|
+
throw error; // Re-throw D1 validation errors
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Handle other errors (import errors, etc.)
|
|
539
|
+
console.log(` ā ļø D1 validation error: ${error.message}`);
|
|
540
|
+
this.addWarning('d1-config', `D1 validation error: ${error.message}`);
|
|
541
|
+
if (this.strictMode) {
|
|
542
|
+
throw new Error(`D1 validation failed: ${error.message}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Generate helpful suggestions for D1 configuration issues
|
|
549
|
+
* @param {Object} d1Validation - D1 validation results
|
|
550
|
+
* @returns {string} Suggestions text
|
|
551
|
+
*/
|
|
552
|
+
generateD1Suggestions(d1Validation) {
|
|
553
|
+
const suggestions = [];
|
|
554
|
+
d1Validation.invalidBindings.forEach(binding => {
|
|
555
|
+
binding.issues.forEach(issue => {
|
|
556
|
+
if (issue.includes('not found')) {
|
|
557
|
+
suggestions.push('Run "wrangler d1 list" to see available databases');
|
|
558
|
+
suggestions.push('Create missing database with "wrangler d1 create <name>"');
|
|
559
|
+
} else if (issue.includes('Missing')) {
|
|
560
|
+
suggestions.push('Check wrangler.toml [[d1_databases]] section completeness');
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Remove duplicates
|
|
566
|
+
const uniqueSuggestions = [...new Set(suggestions)];
|
|
567
|
+
return uniqueSuggestions.length > 0 ? `Suggestions: ${uniqueSuggestions.slice(0, 2).join('; ')}` : 'Check D1 database configuration in wrangler.toml';
|
|
568
|
+
}
|
|
460
569
|
async validateDomainEndpoints(domain) {
|
|
461
570
|
console.log(` Validating endpoints for ${domain}...`);
|
|
462
571
|
console.log(` š§ DEBUG: skipEndpointCheck = ${this.options?.skipEndpointCheck}`);
|
|
@@ -538,6 +647,9 @@ export class DeploymentValidator {
|
|
|
538
647
|
this.results.errors.push(message);
|
|
539
648
|
}
|
|
540
649
|
}
|
|
650
|
+
addWarning(category, message) {
|
|
651
|
+
this.addResult(category, message, 'warning');
|
|
652
|
+
}
|
|
541
653
|
printValidationSummary() {
|
|
542
654
|
console.log('\nš VALIDATION SUMMARY');
|
|
543
655
|
console.log('====================');
|
|
@@ -2,10 +2,13 @@ import { spawn } from 'child_process';
|
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import { WranglerD1Manager } from '../../bin/database/wrangler-d1-manager.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* WranglerDeployer - Executes actual Cloudflare Workers deployments using wrangler CLI
|
|
8
9
|
* Provides integration between Clodo Framework orchestration and wrangler deployment
|
|
10
|
+
*
|
|
11
|
+
* Now integrated with WranglerD1Manager for comprehensive D1 database operations
|
|
9
12
|
*/
|
|
10
13
|
export class WranglerDeployer {
|
|
11
14
|
constructor(options = {}) {
|
|
@@ -15,6 +18,12 @@ export class WranglerDeployer {
|
|
|
15
18
|
this.maxRetries = options.maxRetries || 1;
|
|
16
19
|
this.environment = options.environment || this.detectEnvironment();
|
|
17
20
|
this.serviceInfo = options.serviceInfo || this.discoverServiceInfo();
|
|
21
|
+
|
|
22
|
+
// Initialize D1 manager for database operations
|
|
23
|
+
this.d1Manager = new WranglerD1Manager({
|
|
24
|
+
cwd: this.cwd,
|
|
25
|
+
timeout: 60000 // 1 minute for D1 operations
|
|
26
|
+
});
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
/**
|
|
@@ -487,14 +496,19 @@ export class WranglerDeployer {
|
|
|
487
496
|
|
|
488
497
|
// Validate configuration can be parsed
|
|
489
498
|
const configValidation = await this.validateWranglerConfig(deployConfig, environment);
|
|
499
|
+
|
|
500
|
+
// Validate D1 database bindings using D1Manager
|
|
501
|
+
const d1Validation = await this.d1Manager.validateD1Bindings(deployConfig);
|
|
502
|
+
const overallValid = configValidation.valid && d1Validation.valid;
|
|
490
503
|
return {
|
|
491
|
-
valid:
|
|
504
|
+
valid: overallValid,
|
|
492
505
|
version: versionResult.output.trim(),
|
|
493
506
|
config: deployConfig,
|
|
494
507
|
account: accountInfo,
|
|
495
508
|
configValidation,
|
|
509
|
+
d1Validation,
|
|
496
510
|
authenticated: true,
|
|
497
|
-
ready:
|
|
511
|
+
ready: overallValid
|
|
498
512
|
};
|
|
499
513
|
} catch (error) {
|
|
500
514
|
return {
|
|
@@ -570,5 +584,36 @@ export class WranglerDeployer {
|
|
|
570
584
|
}
|
|
571
585
|
return warnings;
|
|
572
586
|
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Handle D1 binding errors with interactive recovery options (delegated to D1Manager)
|
|
590
|
+
* @param {string} error - Error message
|
|
591
|
+
* @param {Object} context - Error context
|
|
592
|
+
* @returns {Promise<Object>} Recovery result
|
|
593
|
+
*/
|
|
594
|
+
async handleD1BindingError(error, context = {}) {
|
|
595
|
+
return await this.d1Manager.handleD1BindingError(error, context);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Validate D1 bindings using D1Manager (delegated)
|
|
600
|
+
*/
|
|
601
|
+
async validateD1Bindings(deployConfig) {
|
|
602
|
+
return await this.d1Manager.validateD1Bindings(deployConfig);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Extract D1 bindings using D1Manager (delegated)
|
|
607
|
+
*/
|
|
608
|
+
extractD1Bindings(configContent) {
|
|
609
|
+
return this.d1Manager.extractD1Bindings(configContent);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Check D1 database existence using D1Manager (delegated)
|
|
614
|
+
*/
|
|
615
|
+
async checkD1DatabaseExists(nameOrId) {
|
|
616
|
+
return await this.d1Manager.checkD1DatabaseExists(nameOrId);
|
|
617
|
+
}
|
|
573
618
|
}
|
|
574
619
|
export default WranglerDeployer;
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { createInterface } from 'readline';
|
|
15
15
|
import chalk from 'chalk';
|
|
16
16
|
import { validateServiceName, validateDomainName } from '../utils/validation.js';
|
|
17
|
+
import { uiStructuresLoader } from '../utils/ui-structures-loader.js';
|
|
17
18
|
export class InputCollector {
|
|
18
19
|
constructor(options = {}) {
|
|
19
20
|
this.interactive = options.interactive !== false;
|
|
@@ -24,34 +25,221 @@ export class InputCollector {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
|
-
* Collect all
|
|
28
|
+
* Collect all inputs using three-tier template-driven approach
|
|
29
|
+
* Tier 1: 6 core inputs (required)
|
|
30
|
+
* Tier 2: 15 smart confirmations (assumed, user can modify)
|
|
31
|
+
* Tier 3: 67 automated generations (framework handles)
|
|
28
32
|
*/
|
|
29
|
-
async
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
async collectInputsWithTransparency() {
|
|
34
|
+
await uiStructuresLoader.loadTemplates();
|
|
35
|
+
const result = {
|
|
36
|
+
collectionMetadata: {
|
|
37
|
+
method: 'three-tier-template-driven',
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
tiers: {
|
|
40
|
+
core: 6,
|
|
41
|
+
confirmable: 15,
|
|
42
|
+
automated: 67
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
coreInputs: {},
|
|
46
|
+
smartConfirmations: {},
|
|
47
|
+
automatedGenerations: {},
|
|
48
|
+
userModifications: []
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Tier 1: Core Inputs (required from user)
|
|
52
|
+
console.log(chalk.cyan('\nš Tier 1: Core Service Information'));
|
|
53
|
+
console.log(chalk.white('These 6 inputs are required to create your service.\n'));
|
|
54
|
+
const coreTemplate = uiStructuresLoader.getCoreInputsTemplate();
|
|
55
|
+
if (!coreTemplate) {
|
|
56
|
+
throw new Error('Core inputs template not found. Cannot proceed with input collection.');
|
|
57
|
+
}
|
|
58
|
+
if (coreTemplate) {
|
|
59
|
+
for (const inputDef of coreTemplate.inputs) {
|
|
60
|
+
const value = await this.collectInputFromDefinition(inputDef);
|
|
61
|
+
result.coreInputs[inputDef.id] = {
|
|
62
|
+
value,
|
|
63
|
+
source: 'user-provided',
|
|
64
|
+
required: true
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
33
68
|
|
|
34
|
-
//
|
|
35
|
-
|
|
69
|
+
// Tier 2: Smart Confirmations (framework assumptions, user can modify)
|
|
70
|
+
console.log(chalk.cyan('\nš¤ Tier 2: Smart Confirmations'));
|
|
71
|
+
console.log(chalk.white('Based on your core inputs, we\'ve made smart assumptions. Review and modify as needed.\n'));
|
|
72
|
+
const confirmTemplate = uiStructuresLoader.getSmartConfirmableTemplate();
|
|
73
|
+
if (confirmTemplate) {
|
|
74
|
+
for (const category of confirmTemplate.categories) {
|
|
75
|
+
console.log(chalk.yellow(`\n${category.title}`));
|
|
76
|
+
console.log(chalk.gray(`${category.description}\n`));
|
|
77
|
+
for (const inputId of category.inputs) {
|
|
78
|
+
// Generate smart default based on core inputs
|
|
79
|
+
const smartDefault = this.generateSmartDefault(inputId, result.coreInputs);
|
|
80
|
+
const userValue = await this.confirmOrModifyValue(inputId, smartDefault);
|
|
81
|
+
result.smartConfirmations[inputId] = {
|
|
82
|
+
value: userValue,
|
|
83
|
+
defaultAssumed: smartDefault,
|
|
84
|
+
userModified: userValue !== smartDefault,
|
|
85
|
+
source: userValue === smartDefault ? 'framework-assumed' : 'user-modified'
|
|
86
|
+
};
|
|
87
|
+
if (userValue !== smartDefault) {
|
|
88
|
+
result.userModifications.push({
|
|
89
|
+
field: inputId,
|
|
90
|
+
assumed: smartDefault,
|
|
91
|
+
chosen: userValue
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
36
97
|
|
|
37
|
-
//
|
|
38
|
-
|
|
98
|
+
// Tier 3: Automated Generation (show transparency)
|
|
99
|
+
console.log(chalk.cyan('\nā” Tier 3: Automated Generation'));
|
|
100
|
+
console.log(chalk.white('The following will be automatically generated from your inputs:\n'));
|
|
101
|
+
const autoTemplate = uiStructuresLoader.getAutomatedGenerationTemplate();
|
|
102
|
+
if (autoTemplate) {
|
|
103
|
+
console.log(chalk.gray(`š ${autoTemplate.collectionStrategy.inputCount} configurations will be generated automatically`));
|
|
104
|
+
console.log(chalk.gray(`ā±ļø Estimated time: ${autoTemplate.template.estimatedTime}`));
|
|
39
105
|
|
|
40
|
-
|
|
41
|
-
|
|
106
|
+
// Show some examples of what will be automated
|
|
107
|
+
result.automatedGenerations = {
|
|
108
|
+
count: autoTemplate.collectionStrategy.inputCount,
|
|
109
|
+
estimatedTime: autoTemplate.template.estimatedTime,
|
|
110
|
+
examples: ['Database connection strings', 'Environment variables', 'API endpoints', 'Security configurations', 'Deployment scripts']
|
|
111
|
+
};
|
|
112
|
+
}
|
|
42
113
|
|
|
43
|
-
//
|
|
44
|
-
|
|
114
|
+
// Summary
|
|
115
|
+
console.log(chalk.green('\nā
Collection Complete!'));
|
|
116
|
+
console.log(chalk.white(`Core inputs: ${Object.keys(result.coreInputs).length}`));
|
|
117
|
+
console.log(chalk.white(`Smart confirmations: ${Object.keys(result.smartConfirmations).length}`));
|
|
118
|
+
console.log(chalk.white(`Automated generations: ${result.automatedGenerations.count || 0}`));
|
|
119
|
+
if (result.userModifications.length > 0) {
|
|
120
|
+
console.log(chalk.yellow(`User modifications: ${result.userModifications.length}`));
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
45
124
|
|
|
46
|
-
|
|
47
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Generate smart defaults based on core inputs
|
|
127
|
+
*/
|
|
128
|
+
generateSmartDefault(inputId, coreInputs) {
|
|
129
|
+
const serviceName = coreInputs.serviceName?.value || '';
|
|
130
|
+
const environment = coreInputs.environment?.value || 'development';
|
|
131
|
+
const domainName = coreInputs.domainName?.value || '';
|
|
132
|
+
switch (inputId) {
|
|
133
|
+
case 'display-name':
|
|
134
|
+
return serviceName ? serviceName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) : '';
|
|
135
|
+
case 'description':
|
|
136
|
+
return `A service built with CLODO Framework`;
|
|
137
|
+
case 'version':
|
|
138
|
+
return '1.0.0';
|
|
139
|
+
case 'author':
|
|
140
|
+
return 'CLODO Framework';
|
|
141
|
+
case 'production-url':
|
|
142
|
+
return domainName ? `https://api.${domainName}` : '';
|
|
143
|
+
case 'staging-url':
|
|
144
|
+
return domainName ? `https://staging-api.${domainName}` : '';
|
|
145
|
+
case 'development-url':
|
|
146
|
+
return domainName ? `https://dev-api.${domainName}` : '';
|
|
147
|
+
case 'service-directory':
|
|
148
|
+
return serviceName ? `./services/${serviceName}` : '';
|
|
149
|
+
case 'database-name':
|
|
150
|
+
return serviceName ? `${serviceName}-db` : '';
|
|
151
|
+
case 'worker-name':
|
|
152
|
+
return serviceName ? `${serviceName}-worker` : '';
|
|
153
|
+
case 'log-level':
|
|
154
|
+
return environment === 'production' ? 'warn' : environment === 'staging' ? 'info' : 'debug';
|
|
155
|
+
case 'env-prefix':
|
|
156
|
+
return environment === 'production' ? 'PROD_' : environment === 'staging' ? 'STAGING_' : 'DEV_';
|
|
157
|
+
default:
|
|
158
|
+
return '';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
48
161
|
|
|
49
|
-
|
|
50
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Allow user to confirm or modify a smart default
|
|
164
|
+
*/
|
|
165
|
+
async confirmOrModifyValue(inputId, defaultValue) {
|
|
166
|
+
console.log(chalk.blue(`ā ${this.formatFieldName(inputId)}`));
|
|
167
|
+
console.log(chalk.gray(` Suggested: ${defaultValue}`));
|
|
168
|
+
const answer = await this.question(`Press Enter to accept, or enter new value: `);
|
|
169
|
+
return answer.trim() || defaultValue;
|
|
170
|
+
}
|
|
51
171
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Format field names for display
|
|
174
|
+
*/
|
|
175
|
+
formatFieldName(inputId) {
|
|
176
|
+
return inputId.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Collect a single input based on UI definition
|
|
181
|
+
*/
|
|
182
|
+
async collectInputFromDefinition(inputDef) {
|
|
183
|
+
const {
|
|
184
|
+
id,
|
|
185
|
+
ui,
|
|
186
|
+
validation,
|
|
187
|
+
examples,
|
|
188
|
+
followUp
|
|
189
|
+
} = inputDef;
|
|
190
|
+
|
|
191
|
+
// Display prompt
|
|
192
|
+
console.log(chalk.blue(`ā ${ui.label}`));
|
|
193
|
+
if (ui.description) {
|
|
194
|
+
console.log(chalk.gray(` ${ui.description}`));
|
|
195
|
+
}
|
|
196
|
+
if (examples && examples.length > 0) {
|
|
197
|
+
console.log(chalk.gray(` Examples: ${examples.join(', ')}`));
|
|
198
|
+
}
|
|
199
|
+
for (;;) {
|
|
200
|
+
const answer = await this.question(`${ui.placeholder || 'Enter value'}: `);
|
|
201
|
+
|
|
202
|
+
// Basic validation
|
|
203
|
+
if (validation) {
|
|
204
|
+
if (validation.required && !answer) {
|
|
205
|
+
console.log(chalk.red('ā This field is required'));
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (validation.minLength && answer.length < validation.minLength) {
|
|
209
|
+
console.log(chalk.red(`ā Minimum length: ${validation.minLength}`));
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (validation.maxLength && answer.length > validation.maxLength) {
|
|
213
|
+
console.log(chalk.red(`ā Maximum length: ${validation.maxLength}`));
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (validation.pattern && !new RegExp(validation.pattern).test(answer)) {
|
|
217
|
+
console.log(chalk.red(`ā ${validation.customMessage || 'Invalid format'}`));
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Follow-up message
|
|
223
|
+
if (followUp) {
|
|
224
|
+
const message = followUp.message.replace('{value}', answer);
|
|
225
|
+
console.log(chalk.green(`ā
${message}`));
|
|
226
|
+
if (followUp.preview) {
|
|
227
|
+
console.log(chalk.gray(` ${followUp.preview.replace('{value}', answer)}`));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return answer;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Promisified readline question
|
|
236
|
+
*/
|
|
237
|
+
question(prompt) {
|
|
238
|
+
return new Promise(resolve => {
|
|
239
|
+
this.rl.question(prompt, answer => {
|
|
240
|
+
resolve(answer.trim());
|
|
241
|
+
});
|
|
242
|
+
});
|
|
55
243
|
}
|
|
56
244
|
|
|
57
245
|
/**
|
|
@@ -270,6 +270,9 @@ export class DeploymentValidator {
|
|
|
270
270
|
|
|
271
271
|
// Validate environment configuration
|
|
272
272
|
await this.validateEnvironmentConfig();
|
|
273
|
+
|
|
274
|
+
// Validate D1 database bindings
|
|
275
|
+
await this.validateD1Configuration();
|
|
273
276
|
this.results.categories.configuration = 'passed';
|
|
274
277
|
this.addResult('configuration', 'Configuration files validated', 'success');
|
|
275
278
|
} catch (error) {
|
|
@@ -457,6 +460,112 @@ export class DeploymentValidator {
|
|
|
457
460
|
console.log(` ā
Environment: ${this.environment} configuration valid`);
|
|
458
461
|
this.addResult('environment', `${this.environment} environment validated`, 'info');
|
|
459
462
|
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Validate D1 database configuration across all services
|
|
466
|
+
*/
|
|
467
|
+
async validateD1Configuration() {
|
|
468
|
+
console.log(' šļø Validating D1 database configuration...');
|
|
469
|
+
try {
|
|
470
|
+
// Import WranglerDeployer for D1 validation capabilities
|
|
471
|
+
const {
|
|
472
|
+
WranglerDeployer
|
|
473
|
+
} = await import('../../../src/deployment/wrangler-deployer.js');
|
|
474
|
+
|
|
475
|
+
// Check if this is a framework-level validation (no specific service)
|
|
476
|
+
if (!this.options?.servicePath) {
|
|
477
|
+
console.log(' āļø Skipping D1 validation (framework-level validation)');
|
|
478
|
+
this.addResult('d1-config', 'D1 validation skipped for framework-level validation', 'info');
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Create deployer instance for the specific service
|
|
483
|
+
const deployer = new WranglerDeployer({
|
|
484
|
+
cwd: this.options.servicePath,
|
|
485
|
+
environment: this.environment
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// Discover deployment configuration
|
|
489
|
+
const deployConfig = await deployer.discoverDeploymentConfig(this.environment);
|
|
490
|
+
if (!deployConfig.configPath) {
|
|
491
|
+
console.log(' āļø No wrangler.toml found, skipping D1 validation');
|
|
492
|
+
this.addResult('d1-config', 'No wrangler.toml found', 'info');
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Validate D1 bindings
|
|
497
|
+
const d1Validation = await deployer.validateD1Bindings(deployConfig);
|
|
498
|
+
if (d1Validation.valid) {
|
|
499
|
+
if (d1Validation.bindings.length === 0) {
|
|
500
|
+
console.log(' ā
No D1 databases configured');
|
|
501
|
+
this.addResult('d1-config', 'No D1 databases configured', 'info');
|
|
502
|
+
} else {
|
|
503
|
+
console.log(` ā
All ${d1Validation.summary.total} D1 database bindings valid`);
|
|
504
|
+
this.addResult('d1-config', `${d1Validation.summary.total} D1 bindings validated`, 'success');
|
|
505
|
+
|
|
506
|
+
// Log details about each binding
|
|
507
|
+
d1Validation.bindings.forEach(binding => {
|
|
508
|
+
if (binding.valid) {
|
|
509
|
+
console.log(` ā
${binding.binding}: ${binding.database_name}`);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
console.log(` ā D1 validation failed: ${d1Validation.summary.invalid} invalid bindings`);
|
|
515
|
+
|
|
516
|
+
// Log details about invalid bindings
|
|
517
|
+
d1Validation.invalidBindings.forEach(binding => {
|
|
518
|
+
console.log(` ā ${binding.binding || 'unnamed'}: ${binding.issues.join(', ')}`);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// Add warning with suggestions
|
|
522
|
+
const suggestions = this.generateD1Suggestions(d1Validation);
|
|
523
|
+
this.addWarning('d1-config', `D1 configuration issues found. ${suggestions}`);
|
|
524
|
+
|
|
525
|
+
// Don't fail validation entirely for D1 issues if not in strict mode
|
|
526
|
+
if (!this.strictMode) {
|
|
527
|
+
console.log(' ā ļø Continuing with warnings (non-strict mode)');
|
|
528
|
+
this.addResult('d1-config', 'D1 issues found but continuing', 'warning');
|
|
529
|
+
} else {
|
|
530
|
+
throw new Error(`D1 validation failed: ${d1Validation.summary.invalid} invalid bindings`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (error.message.includes('D1 validation failed')) {
|
|
535
|
+
throw error; // Re-throw D1 validation errors
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Handle other errors (import errors, etc.)
|
|
539
|
+
console.log(` ā ļø D1 validation error: ${error.message}`);
|
|
540
|
+
this.addWarning('d1-config', `D1 validation error: ${error.message}`);
|
|
541
|
+
if (this.strictMode) {
|
|
542
|
+
throw new Error(`D1 validation failed: ${error.message}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Generate helpful suggestions for D1 configuration issues
|
|
549
|
+
* @param {Object} d1Validation - D1 validation results
|
|
550
|
+
* @returns {string} Suggestions text
|
|
551
|
+
*/
|
|
552
|
+
generateD1Suggestions(d1Validation) {
|
|
553
|
+
const suggestions = [];
|
|
554
|
+
d1Validation.invalidBindings.forEach(binding => {
|
|
555
|
+
binding.issues.forEach(issue => {
|
|
556
|
+
if (issue.includes('not found')) {
|
|
557
|
+
suggestions.push('Run "wrangler d1 list" to see available databases');
|
|
558
|
+
suggestions.push('Create missing database with "wrangler d1 create <name>"');
|
|
559
|
+
} else if (issue.includes('Missing')) {
|
|
560
|
+
suggestions.push('Check wrangler.toml [[d1_databases]] section completeness');
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Remove duplicates
|
|
566
|
+
const uniqueSuggestions = [...new Set(suggestions)];
|
|
567
|
+
return uniqueSuggestions.length > 0 ? `Suggestions: ${uniqueSuggestions.slice(0, 2).join('; ')}` : 'Check D1 database configuration in wrangler.toml';
|
|
568
|
+
}
|
|
460
569
|
async validateDomainEndpoints(domain) {
|
|
461
570
|
console.log(` Validating endpoints for ${domain}...`);
|
|
462
571
|
console.log(` š§ DEBUG: skipEndpointCheck = ${this.options?.skipEndpointCheck}`);
|
|
@@ -538,6 +647,9 @@ export class DeploymentValidator {
|
|
|
538
647
|
this.results.errors.push(message);
|
|
539
648
|
}
|
|
540
649
|
}
|
|
650
|
+
addWarning(category, message) {
|
|
651
|
+
this.addResult(category, message, 'warning');
|
|
652
|
+
}
|
|
541
653
|
printValidationSummary() {
|
|
542
654
|
console.log('\nš VALIDATION SUMMARY');
|
|
543
655
|
console.log('====================');
|