@tamyla/clodo-framework 3.1.21 → 3.1.22
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 +9 -0
- package/README.md +53 -0
- package/dist/bin/clodo-service.js +47 -15
- package/dist/bin/commands/deploy.js +115 -83
- package/dist/bin/commands/helpers/deployment-ui.js +138 -0
- package/dist/bin/commands/helpers/deployment-verification.js +251 -0
- package/dist/bin/commands/helpers/error-recovery.js +80 -0
- package/dist/bin/commands/helpers/resource-detection.js +113 -0
- package/dist/bin/commands/validate.js +1 -1
- package/dist/bin/security/security-cli.js +1 -1
- package/dist/bin/shared/cache/configuration-cache.js +82 -0
- package/dist/bin/shared/cloudflare/domain-manager.js +1 -1
- package/dist/bin/shared/cloudflare/index.js +1 -1
- package/dist/bin/shared/cloudflare/ops.js +6 -4
- package/dist/bin/shared/config/ConfigurationManager.js +23 -1
- package/dist/bin/shared/config/command-config-manager.js +19 -3
- package/dist/bin/shared/config/index.js +1 -1
- package/dist/bin/shared/deployment/credential-collector.js +30 -7
- package/dist/bin/shared/deployment/index.js +2 -2
- package/dist/bin/shared/deployment/rollback-manager.js +4 -520
- package/dist/bin/shared/deployment/utilities/d1-error-recovery.js +177 -0
- package/dist/bin/shared/deployment/validator.js +40 -10
- package/dist/bin/shared/deployment/workflows/deployment-summary.js +214 -0
- package/dist/bin/shared/deployment/workflows/interactive-confirmation.js +188 -0
- package/dist/bin/shared/deployment/workflows/interactive-database-workflow.js +234 -0
- package/dist/bin/shared/deployment/workflows/interactive-domain-info-gatherer.js +240 -0
- package/dist/bin/shared/deployment/workflows/interactive-secret-workflow.js +228 -0
- package/dist/bin/shared/deployment/workflows/interactive-testing-workflow.js +235 -0
- package/dist/bin/shared/deployment/workflows/interactive-validation.js +218 -0
- package/dist/bin/shared/error-handling/error-classifier.js +46 -0
- package/dist/bin/shared/monitoring/health-checker.js +129 -1
- package/dist/bin/shared/monitoring/memory-manager.js +17 -6
- package/dist/bin/shared/routing/domain-router.js +1 -1
- package/dist/bin/shared/utils/deployment-validator.js +97 -0
- package/dist/bin/shared/utils/formatters.js +10 -0
- package/dist/bin/shared/utils/index.js +13 -1
- package/dist/bin/shared/utils/interactive-prompts.js +34 -18
- package/dist/bin/shared/utils/progress-manager.js +2 -2
- package/dist/bin/shared/utils/progress-spinner.js +53 -0
- package/dist/bin/shared/utils/sensitive-redactor.js +91 -0
- package/dist/bin/shared/validation/ValidationRegistry.js +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/patterns/insecure-patterns.js +1 -1
- package/dist/utils/constants.js +102 -0
- package/dist/utils/deployment/wrangler-config-manager.js +215 -48
- package/dist/utils/framework-config.js +2 -2
- package/dist/utils/interactive-prompts.js +10 -59
- package/package.json +16 -8
- package/dist/bin/clodo-service-old.js +0 -868
- package/dist/bin/clodo-service-test.js +0 -10
- package/dist/bin/commands/assess.js +0 -91
- package/dist/bin/commands/create.js +0 -77
- package/dist/bin/commands/diagnose.js +0 -83
- package/dist/bin/commands/helpers.js +0 -138
- package/dist/bin/commands/update.js +0 -75
- package/dist/bin/database/deployment-db-manager.js +0 -423
- package/dist/bin/database/enterprise-db-manager.js +0 -457
- package/dist/bin/database/wrangler-d1-manager.js +0 -685
- package/dist/bin/deployment/enterprise-deploy.js +0 -877
- package/dist/bin/deployment/master-deploy.js +0 -1376
- package/dist/bin/deployment/modular-enterprise-deploy.js +0 -466
- package/dist/bin/deployment/modules/DeploymentConfiguration.js +0 -395
- package/dist/bin/deployment/modules/DeploymentOrchestrator.js +0 -492
- package/dist/bin/deployment/modules/EnvironmentManager.js +0 -517
- package/dist/bin/deployment/modules/MonitoringIntegration.js +0 -560
- package/dist/bin/deployment/modules/ValidationManager.js +0 -342
- package/dist/bin/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -426
- package/dist/bin/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
- package/dist/bin/deployment/orchestration/PortfolioOrchestrator.js +0 -273
- package/dist/bin/deployment/orchestration/SingleServiceOrchestrator.js +0 -231
- package/dist/bin/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -662
- package/dist/bin/deployment/test-interactive-utils.js +0 -66
- package/dist/bin/portfolio/portfolio-manager.js +0 -487
- package/dist/bin/service-management/create-service.js +0 -122
- package/dist/bin/service-management/init-service.js +0 -79
- package/dist/config/customers.js +0 -623
- package/dist/config/domains.js +0 -186
- package/dist/config/index.js +0 -6
- package/dist/database/database-orchestrator.js +0 -795
- package/dist/database/index.js +0 -4
- package/dist/deployment/index.js +0 -11
- package/dist/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -426
- package/dist/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
- package/dist/deployment/orchestration/PortfolioOrchestrator.js +0 -273
- package/dist/deployment/orchestration/SingleServiceOrchestrator.js +0 -231
- package/dist/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -662
- package/dist/deployment/orchestration/index.js +0 -17
- package/dist/deployment/rollback-manager.js +0 -36
- package/dist/deployment/wrangler-deployer.js +0 -640
- package/dist/handlers/GenericRouteHandler.js +0 -532
- package/dist/migration/MigrationAdapters.js +0 -562
- package/dist/modules/ModuleManager.js +0 -668
- package/dist/modules/security.js +0 -96
- package/dist/orchestration/cross-domain-coordinator.js +0 -1083
- package/dist/orchestration/index.js +0 -5
- package/dist/orchestration/modules/DeploymentCoordinator.js +0 -368
- package/dist/orchestration/modules/DomainResolver.js +0 -198
- package/dist/orchestration/modules/StateManager.js +0 -332
- package/dist/orchestration/multi-domain-orchestrator.js +0 -724
- package/dist/routing/EnhancedRouter.js +0 -158
- package/dist/schema/SchemaManager.js +0 -778
- package/dist/service-management/ConfirmationEngine.js +0 -412
- package/dist/service-management/ErrorTracker.js +0 -299
- package/dist/service-management/GenerationEngine.js +0 -447
- package/dist/service-management/InputCollector.js +0 -619
- package/dist/service-management/ServiceCreator.js +0 -265
- package/dist/service-management/ServiceInitializer.js +0 -453
- package/dist/service-management/ServiceOrchestrator.js +0 -633
- package/dist/service-management/generators/BaseGenerator.js +0 -233
- package/dist/service-management/generators/GeneratorRegistry.js +0 -254
- package/dist/service-management/generators/cicd/CiWorkflowGenerator.js +0 -87
- package/dist/service-management/generators/cicd/DeployWorkflowGenerator.js +0 -106
- package/dist/service-management/generators/code/ServiceHandlersGenerator.js +0 -235
- package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +0 -116
- package/dist/service-management/generators/code/ServiceUtilsGenerator.js +0 -246
- package/dist/service-management/generators/code/WorkerIndexGenerator.js +0 -143
- package/dist/service-management/generators/config/DevelopmentEnvGenerator.js +0 -101
- package/dist/service-management/generators/config/DomainsConfigGenerator.js +0 -175
- package/dist/service-management/generators/config/EnvExampleGenerator.js +0 -178
- package/dist/service-management/generators/config/ProductionEnvGenerator.js +0 -97
- package/dist/service-management/generators/config/StagingEnvGenerator.js +0 -97
- package/dist/service-management/generators/config/WranglerTomlGenerator.js +0 -238
- package/dist/service-management/generators/core/PackageJsonGenerator.js +0 -243
- package/dist/service-management/generators/core/SiteConfigGenerator.js +0 -115
- package/dist/service-management/generators/documentation/ApiDocsGenerator.js +0 -331
- package/dist/service-management/generators/documentation/ConfigurationDocsGenerator.js +0 -294
- package/dist/service-management/generators/documentation/DeploymentDocsGenerator.js +0 -244
- package/dist/service-management/generators/documentation/ReadmeGenerator.js +0 -196
- package/dist/service-management/generators/schemas/ServiceSchemaGenerator.js +0 -190
- package/dist/service-management/generators/scripts/DeployScriptGenerator.js +0 -123
- package/dist/service-management/generators/scripts/HealthCheckScriptGenerator.js +0 -101
- package/dist/service-management/generators/scripts/SetupScriptGenerator.js +0 -88
- package/dist/service-management/generators/service-types/StaticSiteGenerator.js +0 -342
- package/dist/service-management/generators/testing/EslintConfigGenerator.js +0 -85
- package/dist/service-management/generators/testing/IntegrationTestsGenerator.js +0 -237
- package/dist/service-management/generators/testing/JestConfigGenerator.js +0 -72
- package/dist/service-management/generators/testing/UnitTestsGenerator.js +0 -277
- package/dist/service-management/generators/tooling/DockerComposeGenerator.js +0 -71
- package/dist/service-management/generators/tooling/GitignoreGenerator.js +0 -143
- package/dist/service-management/generators/utils/FileWriter.js +0 -179
- package/dist/service-management/generators/utils/PathResolver.js +0 -157
- package/dist/service-management/generators/utils/ServiceManifestGenerator.js +0 -111
- package/dist/service-management/generators/utils/TemplateEngine.js +0 -185
- package/dist/service-management/generators/utils/index.js +0 -18
- package/dist/service-management/handlers/ConfirmationHandler.js +0 -71
- package/dist/service-management/handlers/GenerationHandler.js +0 -80
- package/dist/service-management/handlers/InputHandler.js +0 -59
- package/dist/service-management/handlers/ValidationHandler.js +0 -203
- package/dist/service-management/index.js +0 -14
- package/dist/service-management/routing/DomainRouteMapper.js +0 -311
- package/dist/service-management/routing/RouteGenerator.js +0 -266
- package/dist/service-management/routing/WranglerRoutesBuilder.js +0 -273
- package/dist/service-management/routing/index.js +0 -14
- package/dist/service-management/services/DirectoryStructureService.js +0 -56
- package/dist/service-management/services/GenerationCoordinator.js +0 -208
- package/dist/service-management/services/GeneratorRegistry.js +0 -174
- package/dist/services/GenericDataService.js +0 -501
- package/dist/ui-structures/concepts/second-order-acquisition-strategy.md +0 -286
- package/dist/ui-structures/concepts/service-lifecycle-management.md +0 -150
- package/dist/ui-structures/concepts/service-manifest-guide.md +0 -309
- package/dist/ui-structures/concepts/three-tier-categorization-strategy.md +0 -231
- package/dist/ui-structures/creation/automated-generation-ui.json +0 -246
- package/dist/ui-structures/creation/core-inputs-ui.json +0 -217
- package/dist/ui-structures/creation/smart-confirmable-ui.json +0 -451
- package/dist/ui-structures/reference/absolutely-required-inputs.json +0 -315
- package/dist/ui-structures/reference/service-manifest-template.json +0 -342
- package/dist/version/VersionDetector.js +0 -723
- package/dist/worker/index.js +0 -4
- package/dist/worker/integration.js +0 -351
|
@@ -1,619 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* InputCollector - Tier 1: Core Input Collection
|
|
3
|
-
*
|
|
4
|
-
* Collects the 6 absolutely required inputs for service creation:
|
|
5
|
-
* 1. Service Name
|
|
6
|
-
* 2. Service Type
|
|
7
|
-
* 3. Domain Name
|
|
8
|
-
* 4. Cloudflare API Token
|
|
9
|
-
* 5. Cloudflare Account ID
|
|
10
|
-
* 6. Cloudflare Zone ID
|
|
11
|
-
* 7. Target Environment (derived but required)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { createInterface } from 'readline';
|
|
15
|
-
import chalk from 'chalk';
|
|
16
|
-
import { validateServiceName, validateDomainName } from '../utils/validation.js';
|
|
17
|
-
import { uiStructuresLoader } from '../utils/ui-structures-loader.js';
|
|
18
|
-
import { NameFormatters, UrlFormatters, ResourceFormatters } from "../../dist/bin/shared/utils/Formatters.js";
|
|
19
|
-
|
|
20
|
-
// Assessment capabilities moved to @tamyla/clodo-orchestration (professional edition)
|
|
21
|
-
|
|
22
|
-
export class InputCollector {
|
|
23
|
-
constructor(options = {}) {
|
|
24
|
-
this.interactive = options.interactive !== false;
|
|
25
|
-
this.isPowerShell = process.env.PSModulePath !== undefined;
|
|
26
|
-
|
|
27
|
-
// Don't create readline immediately - lazy initialize
|
|
28
|
-
this.rl = null;
|
|
29
|
-
this.options = options;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Ensure readline is initialized and healthy
|
|
34
|
-
* Creates new instance if needed (e.g., after password input corrupted state)
|
|
35
|
-
*/
|
|
36
|
-
ensureReadline() {
|
|
37
|
-
if (!this.rl || this.rl.closed) {
|
|
38
|
-
// Fix for PowerShell double-echo issue
|
|
39
|
-
this.rl = this.interactive ? createInterface({
|
|
40
|
-
input: process.stdin,
|
|
41
|
-
output: process.stdout,
|
|
42
|
-
terminal: !this.isPowerShell // Disable terminal mode in PowerShell
|
|
43
|
-
}) : null;
|
|
44
|
-
}
|
|
45
|
-
return this.rl;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Collect all inputs using three-tier template-driven approach
|
|
50
|
-
* Tier 1: 6 core inputs (required)
|
|
51
|
-
* Tier 2: 15 smart confirmations (assumed, user can modify)
|
|
52
|
-
* Tier 3: 67 automated generations (framework handles)
|
|
53
|
-
*/
|
|
54
|
-
async collectInputsWithTransparency() {
|
|
55
|
-
await uiStructuresLoader.loadTemplates();
|
|
56
|
-
const result = {
|
|
57
|
-
collectionMetadata: {
|
|
58
|
-
method: 'three-tier-template-driven',
|
|
59
|
-
timestamp: new Date().toISOString(),
|
|
60
|
-
tiers: {
|
|
61
|
-
core: 6,
|
|
62
|
-
confirmable: 15,
|
|
63
|
-
automated: 67
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
coreInputs: {},
|
|
67
|
-
smartConfirmations: {},
|
|
68
|
-
automatedGenerations: {},
|
|
69
|
-
userModifications: []
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Tier 1: Core Inputs (required from user)
|
|
73
|
-
console.log(chalk.cyan('\n📝 Tier 1: Core Service Information'));
|
|
74
|
-
console.log(chalk.white('These 6 inputs are required to create your service.\n'));
|
|
75
|
-
const coreTemplate = uiStructuresLoader.getCoreInputsTemplate();
|
|
76
|
-
if (!coreTemplate) {
|
|
77
|
-
throw new Error('Core inputs template not found. Cannot proceed with input collection.');
|
|
78
|
-
}
|
|
79
|
-
if (coreTemplate) {
|
|
80
|
-
for (const inputDef of coreTemplate.inputs) {
|
|
81
|
-
const value = await this.collectInputFromDefinition(inputDef);
|
|
82
|
-
result.coreInputs[inputDef.id] = {
|
|
83
|
-
value,
|
|
84
|
-
source: 'user-provided',
|
|
85
|
-
required: true
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// Assessment moved to professional edition (@tamyla/clodo-orchestration)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Tier 2: Smart Confirmations (framework assumptions, user can modify)
|
|
93
|
-
console.log(chalk.cyan('\n🤔 Tier 2: Smart Confirmations'));
|
|
94
|
-
console.log(chalk.white('Based on your core inputs, we\'ve made smart assumptions. Review and modify as needed.\n'));
|
|
95
|
-
const confirmTemplate = uiStructuresLoader.getSmartConfirmableTemplate();
|
|
96
|
-
if (confirmTemplate) {
|
|
97
|
-
for (const category of confirmTemplate.categories) {
|
|
98
|
-
console.log(chalk.yellow(`\n${category.title}`));
|
|
99
|
-
console.log(chalk.gray(`${category.description}\n`));
|
|
100
|
-
for (const inputId of category.inputs) {
|
|
101
|
-
// Generate smart default based on core inputs
|
|
102
|
-
const smartDefault = this.generateSmartDefault(inputId, result.coreInputs);
|
|
103
|
-
const userValue = await this.confirmOrModifyValue(inputId, smartDefault);
|
|
104
|
-
result.smartConfirmations[inputId] = {
|
|
105
|
-
value: userValue,
|
|
106
|
-
defaultAssumed: smartDefault,
|
|
107
|
-
userModified: userValue !== smartDefault,
|
|
108
|
-
source: userValue === smartDefault ? 'framework-assumed' : 'user-modified'
|
|
109
|
-
};
|
|
110
|
-
if (userValue !== smartDefault) {
|
|
111
|
-
result.userModifications.push({
|
|
112
|
-
field: inputId,
|
|
113
|
-
assumed: smartDefault,
|
|
114
|
-
chosen: userValue
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Tier 3: Automated Generation (show transparency)
|
|
122
|
-
console.log(chalk.cyan('\n⚡ Tier 3: Automated Generation'));
|
|
123
|
-
console.log(chalk.white('The following will be automatically generated from your inputs:\n'));
|
|
124
|
-
const autoTemplate = uiStructuresLoader.getAutomatedGenerationTemplate();
|
|
125
|
-
if (autoTemplate) {
|
|
126
|
-
console.log(chalk.gray(`📊 ${autoTemplate.template.inputCount} configurations will be generated automatically`));
|
|
127
|
-
console.log(chalk.gray(`⏱️ Estimated time: ${autoTemplate.template.estimatedTime}`));
|
|
128
|
-
|
|
129
|
-
// Show some examples of what will be automated
|
|
130
|
-
result.automatedGenerations = {
|
|
131
|
-
count: autoTemplate.template.inputCount,
|
|
132
|
-
estimatedTime: autoTemplate.template.estimatedTime,
|
|
133
|
-
examples: ['Database connection strings', 'Environment variables', 'API endpoints', 'Security configurations', 'Deployment scripts']
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Summary
|
|
138
|
-
console.log(chalk.green('\n✅ Collection Complete!'));
|
|
139
|
-
console.log(chalk.white(`Core inputs: ${Object.keys(result.coreInputs).length}`));
|
|
140
|
-
console.log(chalk.white(`Smart confirmations: ${Object.keys(result.smartConfirmations).length}`));
|
|
141
|
-
console.log(chalk.white(`Automated generations: ${result.automatedGenerations.count || 0}`));
|
|
142
|
-
if (result.userModifications.length > 0) {
|
|
143
|
-
console.log(chalk.yellow(`User modifications: ${result.userModifications.length}`));
|
|
144
|
-
}
|
|
145
|
-
return result;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Generate smart defaults based on core inputs
|
|
150
|
-
*/
|
|
151
|
-
generateSmartDefault(inputId, coreInputs) {
|
|
152
|
-
const serviceName = coreInputs['service-name']?.value || coreInputs.serviceName?.value || '';
|
|
153
|
-
const environment = coreInputs['environment']?.value || coreInputs.environment?.value || 'development';
|
|
154
|
-
const domainName = coreInputs['domain-name']?.value || coreInputs.domainName?.value || '';
|
|
155
|
-
const serviceType = coreInputs['service-type']?.value || coreInputs.serviceType?.value || '';
|
|
156
|
-
const customerName = coreInputs['customer-name']?.value || coreInputs.customerName?.value || '';
|
|
157
|
-
const cloudflareToken = coreInputs['cloudflare-api-token']?.value || coreInputs.cloudflareApiToken?.value || '';
|
|
158
|
-
switch (inputId) {
|
|
159
|
-
case 'display-name':
|
|
160
|
-
return serviceName ? NameFormatters.toDisplayName(serviceName) : '';
|
|
161
|
-
case 'description':
|
|
162
|
-
return `A service built with CLODO Framework`;
|
|
163
|
-
case 'version':
|
|
164
|
-
return '1.0.0';
|
|
165
|
-
case 'author':
|
|
166
|
-
return 'CLODO Framework';
|
|
167
|
-
case 'production-url':
|
|
168
|
-
return domainName && serviceName ? UrlFormatters.buildProductionUrl(serviceName, domainName) : '';
|
|
169
|
-
case 'staging-url':
|
|
170
|
-
return domainName && serviceName ? UrlFormatters.buildStagingUrl(serviceName, domainName) : '';
|
|
171
|
-
case 'development-url':
|
|
172
|
-
return domainName && serviceName ? UrlFormatters.buildDevUrl(serviceName, domainName) : '';
|
|
173
|
-
case 'service-directory':
|
|
174
|
-
return serviceName ? `./services/${serviceName}` : '';
|
|
175
|
-
case 'database-name':
|
|
176
|
-
return serviceName ? ResourceFormatters.databaseName(serviceName) : '';
|
|
177
|
-
case 'worker-name':
|
|
178
|
-
return serviceName ? ResourceFormatters.workerName(serviceName) : '';
|
|
179
|
-
case 'log-level':
|
|
180
|
-
return environment === 'production' ? 'warn' : environment === 'staging' ? 'info' : 'debug';
|
|
181
|
-
case 'cors-policy':
|
|
182
|
-
return domainName ? `https://${domainName}` : '*';
|
|
183
|
-
case 'env-prefix':
|
|
184
|
-
return environment === 'production' ? 'PROD_' : environment === 'staging' ? 'STAGING_' : 'DEV_';
|
|
185
|
-
default:
|
|
186
|
-
return '';
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Allow user to confirm or modify a smart default
|
|
192
|
-
*/
|
|
193
|
-
async confirmOrModifyValue(inputId, defaultValue) {
|
|
194
|
-
console.log(chalk.blue(`❓ ${this.formatFieldName(inputId)}`));
|
|
195
|
-
console.log(chalk.gray(` Suggested: ${defaultValue}`));
|
|
196
|
-
const answer = await this.question(`Press Enter to accept, or enter new value: `);
|
|
197
|
-
return answer.trim() || defaultValue;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Format field names for display
|
|
202
|
-
*/
|
|
203
|
-
formatFieldName(inputId) {
|
|
204
|
-
return NameFormatters.toDisplayName(inputId);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Collect a single input based on UI definition
|
|
209
|
-
*/
|
|
210
|
-
async collectInputFromDefinition(inputDef) {
|
|
211
|
-
const {
|
|
212
|
-
id,
|
|
213
|
-
ui,
|
|
214
|
-
validation,
|
|
215
|
-
examples,
|
|
216
|
-
followUp
|
|
217
|
-
} = inputDef;
|
|
218
|
-
|
|
219
|
-
// Display prompt
|
|
220
|
-
console.log(chalk.blue(`❓ ${ui.label}`));
|
|
221
|
-
if (ui.description) {
|
|
222
|
-
console.log(chalk.gray(` ${ui.description}`));
|
|
223
|
-
}
|
|
224
|
-
if (examples && examples.length > 0) {
|
|
225
|
-
console.log(chalk.gray(` Examples: ${examples.join(', ')}`));
|
|
226
|
-
}
|
|
227
|
-
for (;;) {
|
|
228
|
-
const answer = await this.question(`${ui.placeholder || 'Enter value'}: `);
|
|
229
|
-
|
|
230
|
-
// Basic validation
|
|
231
|
-
if (validation) {
|
|
232
|
-
if (validation.required && !answer) {
|
|
233
|
-
console.log(chalk.red('❌ This field is required'));
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
if (validation.minLength && answer.length < validation.minLength) {
|
|
237
|
-
console.log(chalk.red(`❌ Minimum length: ${validation.minLength}`));
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
if (validation.maxLength && answer.length > validation.maxLength) {
|
|
241
|
-
console.log(chalk.red(`❌ Maximum length: ${validation.maxLength}`));
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
if (validation.pattern && !new RegExp(validation.pattern).test(answer)) {
|
|
245
|
-
console.log(chalk.red(`❌ ${validation.customMessage || 'Invalid format'}`));
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Follow-up message
|
|
251
|
-
if (followUp) {
|
|
252
|
-
const message = followUp.message.replace('{value}', answer);
|
|
253
|
-
console.log(chalk.green(`✅ ${message}`));
|
|
254
|
-
if (followUp.preview) {
|
|
255
|
-
console.log(chalk.gray(` ${followUp.preview.replace('{value}', answer)}`));
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
return answer;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Promisified readline question with timeout protection
|
|
264
|
-
* Automatically recreates readline if needed
|
|
265
|
-
*/
|
|
266
|
-
question(prompt, timeout = 120000) {
|
|
267
|
-
return new Promise((resolve, reject) => {
|
|
268
|
-
const rl = this.ensureReadline();
|
|
269
|
-
if (!rl) {
|
|
270
|
-
reject(new Error('Readline interface not initialized - running in non-interactive mode?'));
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Verify stdin is readable
|
|
275
|
-
if (!process.stdin.readable) {
|
|
276
|
-
reject(new Error('stdin not readable - terminal may be in broken state'));
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Set timeout to detect hangs (default 2 minutes)
|
|
281
|
-
const timer = setTimeout(() => {
|
|
282
|
-
console.log(chalk.red('\n\n⚠️ Input timeout - readline may be blocked'));
|
|
283
|
-
console.log(chalk.yellow('This can happen in some terminal environments.'));
|
|
284
|
-
console.log(chalk.white('Try running with explicit parameters: npx clodo-service deploy --customer=NAME --env=ENV\n'));
|
|
285
|
-
reject(new Error('Input timeout after ' + timeout / 1000 + ' seconds'));
|
|
286
|
-
}, timeout);
|
|
287
|
-
rl.question(prompt, answer => {
|
|
288
|
-
clearTimeout(timer);
|
|
289
|
-
resolve(answer.trim());
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Validate core inputs (for non-interactive mode)
|
|
296
|
-
*/
|
|
297
|
-
async validateCoreInputs(inputs) {
|
|
298
|
-
const required = ['serviceName', 'serviceType', 'domainName', 'cloudflareToken', 'cloudflareAccountId', 'cloudflareZoneId', 'environment'];
|
|
299
|
-
for (const field of required) {
|
|
300
|
-
if (!inputs[field]) {
|
|
301
|
-
throw new Error(`Missing required input: ${field}`);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Validate service name
|
|
306
|
-
if (!validateServiceName(inputs.serviceName)) {
|
|
307
|
-
throw new Error('Invalid service name format');
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Validate domain name
|
|
311
|
-
if (!validateDomainName(inputs.domainName)) {
|
|
312
|
-
throw new Error('Invalid domain name format');
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Validate service type
|
|
316
|
-
const validTypes = ['data-service', 'auth-service', 'content-service', 'api-gateway', 'generic'];
|
|
317
|
-
if (!validTypes.includes(inputs.serviceType)) {
|
|
318
|
-
throw new Error(`Invalid service type. Must be one of: ${validTypes.join(', ')}`);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Validate environment
|
|
322
|
-
const validEnvs = ['development', 'staging', 'production'];
|
|
323
|
-
if (!validEnvs.includes(inputs.environment)) {
|
|
324
|
-
throw new Error(`Invalid environment. Must be one of: ${validEnvs.join(', ')}`);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Collect service name with validation
|
|
330
|
-
*/
|
|
331
|
-
async collectServiceName() {
|
|
332
|
-
for (;;) {
|
|
333
|
-
const name = await this.prompt('Service Name (lowercase, letters/numbers/hyphens only): ');
|
|
334
|
-
if (validateServiceName(name)) {
|
|
335
|
-
return name;
|
|
336
|
-
}
|
|
337
|
-
console.log(chalk.red('✗ Invalid service name. Use only lowercase letters, numbers, and hyphens.'));
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Collect service type with menu
|
|
343
|
-
*/
|
|
344
|
-
async collectServiceType() {
|
|
345
|
-
const types = {
|
|
346
|
-
'data-service': 'Data service with CRUD operations, search, and filtering',
|
|
347
|
-
'auth-service': 'Authentication and authorization service',
|
|
348
|
-
'content-service': 'Content management with file storage and search',
|
|
349
|
-
'api-gateway': 'API gateway with rate limiting and routing',
|
|
350
|
-
'generic': 'Basic service with core Clodo Framework features'
|
|
351
|
-
};
|
|
352
|
-
console.log(chalk.cyan('Available Service Types:'));
|
|
353
|
-
Object.entries(types).forEach(([type, desc]) => {
|
|
354
|
-
console.log(chalk.white(` ${type}: ${desc}`));
|
|
355
|
-
});
|
|
356
|
-
console.log('');
|
|
357
|
-
for (;;) {
|
|
358
|
-
const type = await this.prompt('Service Type (default: generic): ', 'generic');
|
|
359
|
-
if (Object.keys(types).includes(type)) {
|
|
360
|
-
return type;
|
|
361
|
-
}
|
|
362
|
-
console.log(chalk.red('✗ Invalid service type. Choose from the list above.'));
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Collect domain name with validation
|
|
368
|
-
*/
|
|
369
|
-
async collectDomainName() {
|
|
370
|
-
for (;;) {
|
|
371
|
-
const domain = await this.prompt('Domain Name (e.g., my-service.com): ');
|
|
372
|
-
if (validateDomainName(domain)) {
|
|
373
|
-
return domain;
|
|
374
|
-
}
|
|
375
|
-
console.log(chalk.red('✗ Invalid domain name format.'));
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Collect Cloudflare API token (securely, hidden input)
|
|
381
|
-
*/
|
|
382
|
-
async collectCloudflareToken() {
|
|
383
|
-
console.log(chalk.yellow('Cloudflare Configuration:'));
|
|
384
|
-
console.log(chalk.white('You can find your API token at: https://dash.cloudflare.com/profile/api-tokens'));
|
|
385
|
-
console.log('');
|
|
386
|
-
for (;;) {
|
|
387
|
-
// Use secure password input to hide token from terminal history
|
|
388
|
-
const {
|
|
389
|
-
askPassword
|
|
390
|
-
} = await import('../utils/interactive-prompts.js');
|
|
391
|
-
const token = await askPassword('Cloudflare API Token (hidden)');
|
|
392
|
-
if (token && token.length > 20) {
|
|
393
|
-
// Basic length validation
|
|
394
|
-
// Verify token with CloudflareAPI
|
|
395
|
-
try {
|
|
396
|
-
const {
|
|
397
|
-
CloudflareAPI
|
|
398
|
-
} = await import('../utils/cloudflare/api.js');
|
|
399
|
-
const cfApi = new CloudflareAPI(token);
|
|
400
|
-
const tokenCheck = await cfApi.verifyToken();
|
|
401
|
-
if (tokenCheck.valid) {
|
|
402
|
-
// Check D1 permissions
|
|
403
|
-
const permissionCheck = await cfApi.checkD1Permissions();
|
|
404
|
-
if (!permissionCheck.hasPermission) {
|
|
405
|
-
console.log(chalk.yellow(`⚠️ ${permissionCheck.error}`));
|
|
406
|
-
console.log(chalk.white(' 💡 You can update permissions at: https://dash.cloudflare.com/profile/api-tokens'));
|
|
407
|
-
console.log(chalk.white(' 💡 Or continue and the framework will fall back to OAuth authentication'));
|
|
408
|
-
console.log('');
|
|
409
|
-
const {
|
|
410
|
-
askYesNo
|
|
411
|
-
} = await import('../utils/interactive-prompts.js');
|
|
412
|
-
const continueAnyway = await askYesNo('Continue with limited API token permissions?', false);
|
|
413
|
-
if (!continueAnyway) {
|
|
414
|
-
console.log(chalk.blue('Please update your API token permissions and try again.'));
|
|
415
|
-
process.exit(0);
|
|
416
|
-
}
|
|
417
|
-
console.log(chalk.yellow('⚠️ Proceeding with limited permissions - database operations will use OAuth'));
|
|
418
|
-
}
|
|
419
|
-
console.log(chalk.green('✓ API token verified successfully'));
|
|
420
|
-
return token;
|
|
421
|
-
}
|
|
422
|
-
} catch (error) {
|
|
423
|
-
console.log(chalk.red(`✗ Token verification failed: ${error.message}`));
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
console.log(chalk.red('✗ Invalid API token format.'));
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Collect Cloudflare configuration with automatic domain discovery
|
|
433
|
-
* Returns { accountId, zoneId, domainName }
|
|
434
|
-
*/
|
|
435
|
-
async collectCloudflareConfigWithDiscovery(apiToken, preferredDomain = null) {
|
|
436
|
-
try {
|
|
437
|
-
const {
|
|
438
|
-
CloudflareAPI
|
|
439
|
-
} = await import('../utils/cloudflare/api.js');
|
|
440
|
-
const {
|
|
441
|
-
formatZonesForDisplay,
|
|
442
|
-
parseZoneSelection
|
|
443
|
-
} = await import('../utils/cloudflare/api.js');
|
|
444
|
-
const cfApi = new CloudflareAPI(apiToken);
|
|
445
|
-
console.log(chalk.cyan('\n🔍 Discovering your Cloudflare domains...'));
|
|
446
|
-
const zones = await cfApi.listZones();
|
|
447
|
-
if (!zones || zones.length === 0) {
|
|
448
|
-
console.log(chalk.yellow('⚠️ No domains found in your Cloudflare account.'));
|
|
449
|
-
console.log(chalk.white('Please add a domain to Cloudflare first.'));
|
|
450
|
-
throw new Error('No domains available');
|
|
451
|
-
}
|
|
452
|
-
console.log(chalk.green(`✓ Found ${zones.length} domain(s)\n`));
|
|
453
|
-
let selectedZone;
|
|
454
|
-
|
|
455
|
-
// Auto-select if only one domain
|
|
456
|
-
if (zones.length === 1) {
|
|
457
|
-
selectedZone = zones[0];
|
|
458
|
-
console.log(chalk.white(` 1. ✅ ${zones[0].name} (${zones[0].plan?.name || 'Free'}) - Account: ${zones[0].account?.name || 'N/A'}`));
|
|
459
|
-
console.log(chalk.green(`\n✓ Auto-selected: ${selectedZone.name} (only domain available)\n`));
|
|
460
|
-
} else {
|
|
461
|
-
// Format zones for display
|
|
462
|
-
const formatted = formatZonesForDisplay(zones);
|
|
463
|
-
formatted.forEach((line, index) => {
|
|
464
|
-
console.log(chalk.white(` ${index + 1}. ${line}`));
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
// Let user select a domain
|
|
468
|
-
const selection = await this.prompt('\nSelect domain (enter number or name): ');
|
|
469
|
-
const selectedIndex = parseZoneSelection(selection, zones);
|
|
470
|
-
if (selectedIndex === -1) {
|
|
471
|
-
throw new Error('Invalid domain selection');
|
|
472
|
-
}
|
|
473
|
-
selectedZone = zones[selectedIndex];
|
|
474
|
-
console.log(chalk.green(`\n✓ Selected: ${selectedZone.name}`));
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Get full zone details
|
|
478
|
-
const zoneDetails = await cfApi.getZoneDetails(selectedZone.id);
|
|
479
|
-
return {
|
|
480
|
-
domainName: zoneDetails.name,
|
|
481
|
-
zoneId: zoneDetails.id,
|
|
482
|
-
accountId: zoneDetails.accountId,
|
|
483
|
-
// Already flattened in getZoneDetails response
|
|
484
|
-
accountName: zoneDetails.accountName,
|
|
485
|
-
nameServers: zoneDetails.nameServers,
|
|
486
|
-
status: zoneDetails.status
|
|
487
|
-
};
|
|
488
|
-
} catch (error) {
|
|
489
|
-
console.log(chalk.red(`\n✗ Domain discovery failed: ${error.message}`));
|
|
490
|
-
console.log(chalk.yellow('Falling back to manual entry...\n'));
|
|
491
|
-
|
|
492
|
-
// Fallback to manual entry
|
|
493
|
-
return {
|
|
494
|
-
accountId: await this.collectCloudflareAccountIdManual(),
|
|
495
|
-
zoneId: await this.collectCloudflareZoneIdManual(),
|
|
496
|
-
domainName: preferredDomain
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* Manual Cloudflare Account ID collection (fallback)
|
|
503
|
-
*/
|
|
504
|
-
async collectCloudflareAccountIdManual() {
|
|
505
|
-
console.log(chalk.white('Find your Account ID in the right sidebar of your Cloudflare dashboard.'));
|
|
506
|
-
console.log('');
|
|
507
|
-
for (;;) {
|
|
508
|
-
const accountId = await this.prompt('Cloudflare Account ID: ');
|
|
509
|
-
if (accountId && /^[a-f0-9]{32}$/i.test(accountId)) {
|
|
510
|
-
// 32 hex chars
|
|
511
|
-
return accountId;
|
|
512
|
-
}
|
|
513
|
-
console.log(chalk.red('✗ Invalid Account ID format (should be 32 hexadecimal characters).'));
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Manual Cloudflare Zone ID collection (fallback)
|
|
519
|
-
*/
|
|
520
|
-
async collectCloudflareZoneIdManual() {
|
|
521
|
-
console.log(chalk.white('Find your Zone ID in the Overview tab of your domain in Cloudflare.'));
|
|
522
|
-
console.log('');
|
|
523
|
-
for (;;) {
|
|
524
|
-
const zoneId = await this.prompt('Cloudflare Zone ID: ');
|
|
525
|
-
if (zoneId && /^[a-f0-9]{32}$/i.test(zoneId)) {
|
|
526
|
-
// 32 hex chars
|
|
527
|
-
return zoneId;
|
|
528
|
-
}
|
|
529
|
-
console.log(chalk.red('✗ Invalid Zone ID format (should be 32 hexadecimal characters).'));
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Collect Cloudflare Account ID (kept for backward compatibility)
|
|
535
|
-
* @deprecated Use collectCloudflareConfigWithDiscovery instead
|
|
536
|
-
*/
|
|
537
|
-
async collectCloudflareAccountId() {
|
|
538
|
-
return this.collectCloudflareAccountIdManual();
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Collect Cloudflare Zone ID (kept for backward compatibility)
|
|
543
|
-
* @deprecated Use collectCloudflareConfigWithDiscovery instead
|
|
544
|
-
*/
|
|
545
|
-
async collectCloudflareZoneId() {
|
|
546
|
-
return this.collectCloudflareZoneIdManual();
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/**
|
|
550
|
-
* Collect target environment
|
|
551
|
-
*/
|
|
552
|
-
async collectEnvironment() {
|
|
553
|
-
const environments = {
|
|
554
|
-
'development': 'Local development environment',
|
|
555
|
-
'staging': 'Staging environment for testing',
|
|
556
|
-
'production': 'Production environment for live services'
|
|
557
|
-
};
|
|
558
|
-
console.log(chalk.cyan('Target Environment:'));
|
|
559
|
-
Object.entries(environments).forEach(([env, desc]) => {
|
|
560
|
-
console.log(chalk.white(` ${env}: ${desc}`));
|
|
561
|
-
});
|
|
562
|
-
console.log('');
|
|
563
|
-
for (;;) {
|
|
564
|
-
const env = await this.prompt('Environment (default: development): ', 'development');
|
|
565
|
-
if (Object.keys(environments).includes(env)) {
|
|
566
|
-
return env;
|
|
567
|
-
}
|
|
568
|
-
console.log(chalk.red('✗ Invalid environment. Choose from development, staging, or production.'));
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* Prompt user for input (interactive mode)
|
|
574
|
-
*/
|
|
575
|
-
async prompt(question, defaultValue = '') {
|
|
576
|
-
if (!this.interactive) {
|
|
577
|
-
throw new Error('Cannot prompt in non-interactive mode');
|
|
578
|
-
}
|
|
579
|
-
return new Promise(resolve => {
|
|
580
|
-
const promptText = defaultValue ? `${question}(default: ${defaultValue}) ` : question;
|
|
581
|
-
this.rl.question(promptText, answer => {
|
|
582
|
-
resolve(answer || defaultValue);
|
|
583
|
-
});
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
/**
|
|
588
|
-
* Close readline interface and clean up
|
|
589
|
-
*/
|
|
590
|
-
close() {
|
|
591
|
-
if (this.rl && !this.rl.closed) {
|
|
592
|
-
this.rl.close();
|
|
593
|
-
this.rl = null; // Clear reference so ensureReadline() can create new one if needed
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* Collect inputs and return full three-tier result
|
|
599
|
-
* Used by CLI for comprehensive service operations
|
|
600
|
-
*/
|
|
601
|
-
async collect() {
|
|
602
|
-
const result = await this.collectInputsWithTransparency();
|
|
603
|
-
|
|
604
|
-
// For CLI compatibility, also provide flat coreInputs
|
|
605
|
-
const flatCoreInputs = {};
|
|
606
|
-
for (const [key, inputObj] of Object.entries(result.coreInputs)) {
|
|
607
|
-
flatCoreInputs[key] = inputObj.value;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// Merge smart confirmations into flat object
|
|
611
|
-
for (const [key, value] of Object.entries(result.smartConfirmations)) {
|
|
612
|
-
flatCoreInputs[key] = value;
|
|
613
|
-
}
|
|
614
|
-
return {
|
|
615
|
-
...result,
|
|
616
|
-
flatInputs: flatCoreInputs
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
}
|