@tamyla/clodo-framework 4.0.3 ā 4.0.5
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 +14 -0
- package/dist/database/database-orchestrator.js +5 -0
- package/dist/lib/database/deployment-db-manager.js +2 -2
- package/dist/lib/shared/cloudflare/ops.js +53 -13
- package/dist/lib/shared/deployment/credential-collector.js +14 -3
- package/dist/lib/shared/deployment/workflows/interactive-confirmation.js +19 -4
- package/dist/lib/shared/deployment/workflows/interactive-database-workflow.js +44 -14
- package/dist/lib/shared/deployment/workflows/interactive-deployment-coordinator.js +1 -1
- package/dist/lib/shared/deployment/workflows/interactive-domain-info-gatherer.js +6 -2
- package/dist/lib/shared/deployment/workflows/interactive-validation.js +142 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [4.0.5](https://github.com/tamylaa/clodo-framework/compare/v4.0.4...v4.0.5) (2025-12-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* ensure deployments use correct Cloudflare account and zone ([22d42a9](https://github.com/tamylaa/clodo-framework/commit/22d42a9d4cf800e78953a0bcd897d6cb94dfefc5))
|
|
7
|
+
|
|
8
|
+
## [4.0.4](https://github.com/tamylaa/clodo-framework/compare/v4.0.3...v4.0.4) (2025-12-08)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* enhance existing resource handling and API token propagation ([c7b3a22](https://github.com/tamylaa/clodo-framework/commit/c7b3a226b01609f01f6e8cb1cf27b957cd11868e))
|
|
14
|
+
|
|
1
15
|
## [4.0.3](https://github.com/tamylaa/clodo-framework/compare/v4.0.2...v4.0.3) (2025-12-08)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -682,6 +682,11 @@ export class DatabaseOrchestrator {
|
|
|
682
682
|
} else {
|
|
683
683
|
command += ` --local`;
|
|
684
684
|
}
|
|
685
|
+
|
|
686
|
+
// Add API token if available
|
|
687
|
+
if (this.cloudflareToken) {
|
|
688
|
+
command = `CLOUDFLARE_API_TOKEN=${this.cloudflareToken} ${command}`;
|
|
689
|
+
}
|
|
685
690
|
return command;
|
|
686
691
|
}
|
|
687
692
|
buildBackupCommand(databaseName, environment, backupFile, isRemote) {
|
|
@@ -200,10 +200,10 @@ export class DeploymentDatabaseManager {
|
|
|
200
200
|
/**
|
|
201
201
|
* Run database migrations
|
|
202
202
|
*/
|
|
203
|
-
async runDatabaseMigrations(config) {
|
|
203
|
+
async runDatabaseMigrations(config, credentials = {}) {
|
|
204
204
|
console.log('\nš Running database migrations...');
|
|
205
205
|
try {
|
|
206
|
-
await runMigrations(config.database.name);
|
|
206
|
+
await runMigrations(config.database.name, 'production', credentials);
|
|
207
207
|
console.log(' ā
Database migrations completed');
|
|
208
208
|
this.state.migrationsRun = true;
|
|
209
209
|
} catch (error) {
|
|
@@ -154,11 +154,28 @@ export async function getCloudflareToken(requiredPermissions = ['api_access']) {
|
|
|
154
154
|
}
|
|
155
155
|
throw new Error('No valid Cloudflare API token found with required permissions');
|
|
156
156
|
}
|
|
157
|
-
export async function listWorkers() {
|
|
157
|
+
export async function listWorkers(options = {}) {
|
|
158
|
+
const {
|
|
159
|
+
apiToken,
|
|
160
|
+
accountId
|
|
161
|
+
} = options;
|
|
162
|
+
|
|
163
|
+
// Use API-based operation if credentials provided
|
|
164
|
+
if (apiToken && accountId) {
|
|
165
|
+
const {
|
|
166
|
+
CloudflareAPI
|
|
167
|
+
} = await import('@tamyla/clodo-framework/utils/cloudflare');
|
|
168
|
+
const cf = new CloudflareAPI(apiToken);
|
|
169
|
+
return await cf.listWorkers(accountId);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Fallback to CLI-based operation
|
|
158
173
|
try {
|
|
174
|
+
const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
|
|
175
|
+
const command = `${envVars} npx wrangler list`;
|
|
159
176
|
const {
|
|
160
177
|
stdout: list
|
|
161
|
-
} = await executeWithRateLimit(
|
|
178
|
+
} = await executeWithRateLimit(command, 'workers');
|
|
162
179
|
return list;
|
|
163
180
|
} catch (error) {
|
|
164
181
|
throw new Error(`Failed to list workers: ${error.message}`);
|
|
@@ -226,8 +243,9 @@ export async function deploySecret(key, value, env = 'production', options = {})
|
|
|
226
243
|
return;
|
|
227
244
|
}
|
|
228
245
|
|
|
229
|
-
// Fallback to CLI-based operation
|
|
230
|
-
const
|
|
246
|
+
// Fallback to CLI-based operation with API token if available
|
|
247
|
+
const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
|
|
248
|
+
const command = process.platform === 'win32' ? `${envVars} powershell -Command "Write-Output '${value}' | npx wrangler secret put ${key} --env ${env}"` : `${envVars} echo "${value}" | npx wrangler secret put ${key} --env ${env}`;
|
|
231
249
|
try {
|
|
232
250
|
// Increase retries for secrets due to higher timeout likelihood
|
|
233
251
|
await executeWithRateLimit(command, 'workers', 5);
|
|
@@ -235,18 +253,28 @@ export async function deploySecret(key, value, env = 'production', options = {})
|
|
|
235
253
|
throw new Error(`Secret deployment failed: ${error.message}`);
|
|
236
254
|
}
|
|
237
255
|
}
|
|
238
|
-
export async function deleteSecret(key, env = 'production') {
|
|
256
|
+
export async function deleteSecret(key, env = 'production', options = {}) {
|
|
257
|
+
const {
|
|
258
|
+
apiToken
|
|
259
|
+
} = options;
|
|
260
|
+
const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
|
|
261
|
+
const command = `${envVars} npx wrangler secret delete ${key} --env ${env}`;
|
|
239
262
|
try {
|
|
240
|
-
await executeWithRateLimit(
|
|
263
|
+
await executeWithRateLimit(command, 'workers');
|
|
241
264
|
} catch (error) {
|
|
242
265
|
throw new Error(`Secret deletion failed: ${error.message}`);
|
|
243
266
|
}
|
|
244
267
|
}
|
|
245
|
-
export async function listSecrets(env = 'production') {
|
|
268
|
+
export async function listSecrets(env = 'production', options = {}) {
|
|
269
|
+
const {
|
|
270
|
+
apiToken
|
|
271
|
+
} = options;
|
|
272
|
+
const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
|
|
273
|
+
const command = `${envVars} npx wrangler secret list --env ${env}`;
|
|
246
274
|
try {
|
|
247
275
|
const {
|
|
248
276
|
stdout: list
|
|
249
|
-
} = await executeWithRateLimit(
|
|
277
|
+
} = await executeWithRateLimit(command, 'workers');
|
|
250
278
|
return list;
|
|
251
279
|
} catch (error) {
|
|
252
280
|
throw new Error(`Failed to list secrets: ${error.message}`);
|
|
@@ -318,9 +346,11 @@ export async function createDatabase(name, options = {}) {
|
|
|
318
346
|
|
|
319
347
|
// Fallback to CLI-based operation
|
|
320
348
|
try {
|
|
349
|
+
const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
|
|
350
|
+
const command = `${envVars} npx wrangler d1 create ${name}`;
|
|
321
351
|
const {
|
|
322
352
|
stdout: output
|
|
323
|
-
} = await executeWithRateLimit(
|
|
353
|
+
} = await executeWithRateLimit(command, 'd1');
|
|
324
354
|
const idMatch = output.match(/database_id = "([^"]+)"/);
|
|
325
355
|
if (!idMatch) {
|
|
326
356
|
throw new Error('Could not extract database ID from creation output');
|
|
@@ -330,19 +360,29 @@ export async function createDatabase(name, options = {}) {
|
|
|
330
360
|
throw new Error(`Database creation failed: ${error.message}`);
|
|
331
361
|
}
|
|
332
362
|
}
|
|
333
|
-
export async function deleteDatabase(name) {
|
|
363
|
+
export async function deleteDatabase(name, options = {}) {
|
|
364
|
+
const {
|
|
365
|
+
apiToken
|
|
366
|
+
} = options;
|
|
367
|
+
const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
|
|
368
|
+
const command = `${envVars} npx wrangler d1 delete ${name} --skip-confirmation`;
|
|
334
369
|
try {
|
|
335
|
-
await executeWithRateLimit(
|
|
370
|
+
await executeWithRateLimit(command, 'd1');
|
|
336
371
|
} catch (error) {
|
|
337
372
|
throw new Error(`Database deletion failed: ${error.message}`);
|
|
338
373
|
}
|
|
339
374
|
}
|
|
340
|
-
export async function runMigrations(databaseName, env = 'production') {
|
|
375
|
+
export async function runMigrations(databaseName, env = 'production', options = {}) {
|
|
376
|
+
const {
|
|
377
|
+
apiToken
|
|
378
|
+
} = options;
|
|
341
379
|
const startTime = Date.now();
|
|
342
380
|
try {
|
|
343
381
|
await ensureMonitoringInitialized();
|
|
344
382
|
const result = await errorRecovery.executeWithRecovery(async () => {
|
|
345
|
-
|
|
383
|
+
const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
|
|
384
|
+
const command = `${envVars} npx wrangler d1 migrations apply ${databaseName} --env ${env} --remote`;
|
|
385
|
+
await executeWithRateLimit(command, 'd1');
|
|
346
386
|
return true;
|
|
347
387
|
}, {
|
|
348
388
|
operationId: `runMigrations_${databaseName}_${env}`
|
|
@@ -77,6 +77,7 @@ export class DeploymentCredentialCollector {
|
|
|
77
77
|
console.log(chalk.cyan('\nš Validating Cloudflare API token...\n'));
|
|
78
78
|
}
|
|
79
79
|
let accountId = startCredentials.accountId;
|
|
80
|
+
let accountName = startCredentials.accountName;
|
|
80
81
|
let zoneId = startCredentials.zoneId;
|
|
81
82
|
let zoneName = startCredentials.zoneName;
|
|
82
83
|
try {
|
|
@@ -95,7 +96,9 @@ export class DeploymentCredentialCollector {
|
|
|
95
96
|
|
|
96
97
|
// If account ID not provided, fetch from Cloudflare
|
|
97
98
|
if (!accountId) {
|
|
98
|
-
|
|
99
|
+
const accountInfo = await this.fetchAccountId(cloudflareAPI);
|
|
100
|
+
accountId = accountInfo.id;
|
|
101
|
+
accountName = accountInfo.name;
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
// If zone ID not provided, fetch from Cloudflare
|
|
@@ -118,12 +121,14 @@ export class DeploymentCredentialCollector {
|
|
|
118
121
|
return {
|
|
119
122
|
token,
|
|
120
123
|
accountId,
|
|
124
|
+
accountName,
|
|
121
125
|
zoneId,
|
|
122
126
|
zoneName,
|
|
123
127
|
// Convenience: cloudflareSettings object for passing to orchestrators
|
|
124
128
|
cloudflareSettings: {
|
|
125
129
|
token,
|
|
126
130
|
accountId,
|
|
131
|
+
accountName,
|
|
127
132
|
zoneId,
|
|
128
133
|
zoneName
|
|
129
134
|
}
|
|
@@ -184,7 +189,10 @@ export class DeploymentCredentialCollector {
|
|
|
184
189
|
if (!this.quiet) {
|
|
185
190
|
console.log(chalk.green(`ā
Found account: ${accounts[0].name}\n`));
|
|
186
191
|
}
|
|
187
|
-
return
|
|
192
|
+
return {
|
|
193
|
+
id: accounts[0].id,
|
|
194
|
+
name: accounts[0].name
|
|
195
|
+
};
|
|
188
196
|
}
|
|
189
197
|
|
|
190
198
|
// Multiple accounts - let user choose
|
|
@@ -197,7 +205,10 @@ export class DeploymentCredentialCollector {
|
|
|
197
205
|
if (!this.quiet) {
|
|
198
206
|
console.log(chalk.green(`ā
Selected: ${selectedAccount.name}\n`));
|
|
199
207
|
}
|
|
200
|
-
return
|
|
208
|
+
return {
|
|
209
|
+
id: selectedAccount.id,
|
|
210
|
+
name: selectedAccount.name
|
|
211
|
+
};
|
|
201
212
|
} catch (error) {
|
|
202
213
|
console.error(chalk.red('ā Failed to fetch account ID:'));
|
|
203
214
|
console.error(chalk.yellow(error.message));
|
|
@@ -45,10 +45,25 @@ export class InteractiveConfirmation {
|
|
|
45
45
|
console.log('=====================');
|
|
46
46
|
console.log(`š Domain: ${config.domain}`);
|
|
47
47
|
console.log(`š Environment: ${config.environment}`);
|
|
48
|
-
console.log(`ā” Worker: ${config.worker
|
|
49
|
-
console.log(`š URL: ${config.worker
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
console.log(`ā” Worker: ${config.worker?.name || 'Not configured'}`);
|
|
49
|
+
console.log(`š URL: ${config.worker?.url || 'Not configured'}`);
|
|
50
|
+
|
|
51
|
+
// Database info from resources
|
|
52
|
+
const database = deploymentState.resources?.database;
|
|
53
|
+
if (database) {
|
|
54
|
+
console.log(`šļø Database: ${database.name} (${database.id})`);
|
|
55
|
+
} else {
|
|
56
|
+
console.log(`šļø Database: Not configured`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Secrets info from resources
|
|
60
|
+
const secrets = deploymentState.resources?.secrets;
|
|
61
|
+
if (secrets) {
|
|
62
|
+
const secretCount = Object.keys(secrets).length;
|
|
63
|
+
console.log(`š Secrets: ${secretCount} configured`);
|
|
64
|
+
} else {
|
|
65
|
+
console.log(`š Secrets: Not configured`);
|
|
66
|
+
}
|
|
52
67
|
console.log(`š Deployment ID: ${deploymentState.deploymentId}`);
|
|
53
68
|
}
|
|
54
69
|
|
|
@@ -51,9 +51,15 @@ export class InteractiveDatabaseWorkflow {
|
|
|
51
51
|
const databaseName = await this.promptForDatabaseName(suggestedName, options.interactive);
|
|
52
52
|
|
|
53
53
|
// Check if database exists
|
|
54
|
-
const existingInfo = await this.checkExistingDatabase(databaseName
|
|
54
|
+
const existingInfo = await this.checkExistingDatabase(databaseName, {
|
|
55
|
+
apiToken: options.apiToken,
|
|
56
|
+
accountId: options.accountId
|
|
57
|
+
});
|
|
55
58
|
if (existingInfo.exists) {
|
|
56
|
-
return await this.handleExistingDatabase(databaseName, existingInfo, options.interactive
|
|
59
|
+
return await this.handleExistingDatabase(databaseName, existingInfo, options.interactive, {
|
|
60
|
+
apiToken: options.apiToken,
|
|
61
|
+
accountId: options.accountId
|
|
62
|
+
});
|
|
57
63
|
} else {
|
|
58
64
|
return await this.createNewDatabase(databaseName, options.interactive, {
|
|
59
65
|
apiToken: options.apiToken,
|
|
@@ -87,16 +93,36 @@ export class InteractiveDatabaseWorkflow {
|
|
|
87
93
|
* @param {string} name - Database name
|
|
88
94
|
* @returns {Promise<Object>} { exists: boolean, id?: string, error?: string }
|
|
89
95
|
*/
|
|
90
|
-
async checkExistingDatabase(name) {
|
|
96
|
+
async checkExistingDatabase(name, credentials = {}) {
|
|
91
97
|
console.log('\nš Checking for existing database...');
|
|
92
98
|
try {
|
|
93
|
-
const exists = await databaseExists(name);
|
|
99
|
+
const exists = await databaseExists(name, credentials);
|
|
94
100
|
if (exists) {
|
|
95
101
|
console.log(` š Database '${name}' already exists`);
|
|
96
102
|
|
|
97
|
-
//
|
|
103
|
+
// If using API, get the database ID from API
|
|
104
|
+
if (credentials.apiToken && credentials.accountId) {
|
|
105
|
+
try {
|
|
106
|
+
const {
|
|
107
|
+
CloudflareAPI
|
|
108
|
+
} = await import('@tamyla/clodo-framework/utils/cloudflare');
|
|
109
|
+
const cf = new CloudflareAPI(credentials.apiToken);
|
|
110
|
+
const dbInfo = await cf.getD1Database(credentials.accountId, name);
|
|
111
|
+
return {
|
|
112
|
+
exists: true,
|
|
113
|
+
id: dbInfo.uuid
|
|
114
|
+
};
|
|
115
|
+
} catch (apiError) {
|
|
116
|
+
console.log(` ā ļø Could not get database ID from API: ${apiError.message}`);
|
|
117
|
+
// Fall back to CLI method for ID extraction
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Extract database ID from the CLI list command (fallback)
|
|
98
122
|
try {
|
|
99
|
-
const
|
|
123
|
+
const envVars = credentials.apiToken ? `CLOUDFLARE_API_TOKEN=${credentials.apiToken}` : '';
|
|
124
|
+
const command = `${envVars} npx wrangler d1 list`;
|
|
125
|
+
const dbListResult = await execAsync(command);
|
|
100
126
|
const lines = dbListResult.stdout.split('\n');
|
|
101
127
|
for (const line of lines) {
|
|
102
128
|
if (line.includes(name)) {
|
|
@@ -109,11 +135,14 @@ export class InteractiveDatabaseWorkflow {
|
|
|
109
135
|
}
|
|
110
136
|
}
|
|
111
137
|
}
|
|
112
|
-
} catch (
|
|
113
|
-
console.log(
|
|
138
|
+
} catch (cliError) {
|
|
139
|
+
console.log(` ā ļø Could not extract database ID: ${cliError.message}`);
|
|
114
140
|
}
|
|
141
|
+
|
|
142
|
+
// Return exists=true but no ID if we can't get it
|
|
115
143
|
return {
|
|
116
|
-
exists: true
|
|
144
|
+
exists: true,
|
|
145
|
+
id: null
|
|
117
146
|
};
|
|
118
147
|
} else {
|
|
119
148
|
console.log(` ā
Database '${name}' does not exist`);
|
|
@@ -122,10 +151,10 @@ export class InteractiveDatabaseWorkflow {
|
|
|
122
151
|
};
|
|
123
152
|
}
|
|
124
153
|
} catch (error) {
|
|
125
|
-
console.log(
|
|
154
|
+
console.log(` ā ļø Error checking database existence: ${error.message}`);
|
|
155
|
+
console.log(' ā¹ļø Assuming database does not exist');
|
|
126
156
|
return {
|
|
127
|
-
exists: false
|
|
128
|
-
error: error.message
|
|
157
|
+
exists: false
|
|
129
158
|
};
|
|
130
159
|
}
|
|
131
160
|
}
|
|
@@ -136,9 +165,10 @@ export class InteractiveDatabaseWorkflow {
|
|
|
136
165
|
* @param {string} name - Database name
|
|
137
166
|
* @param {Object} existingInfo - Information about existing database
|
|
138
167
|
* @param {boolean} interactive - Enable interactive prompts
|
|
168
|
+
* @param {Object} credentials - Cloudflare API credentials
|
|
139
169
|
* @returns {Promise<Object>} Database configuration
|
|
140
170
|
*/
|
|
141
|
-
async handleExistingDatabase(name, existingInfo, interactive = true) {
|
|
171
|
+
async handleExistingDatabase(name, existingInfo, interactive = true, credentials = {}) {
|
|
142
172
|
if (!interactive) {
|
|
143
173
|
// Non-interactive mode: reuse existing
|
|
144
174
|
console.log(` ā
Using existing database: ${name}`);
|
|
@@ -178,7 +208,7 @@ export class InteractiveDatabaseWorkflow {
|
|
|
178
208
|
throw new Error('Database deletion cancelled');
|
|
179
209
|
}
|
|
180
210
|
console.log(`\nšļø Deleting existing database...`);
|
|
181
|
-
await deleteDatabase(name);
|
|
211
|
+
await deleteDatabase(name, credentials);
|
|
182
212
|
return await this.createNewDatabase(name, interactive);
|
|
183
213
|
}
|
|
184
214
|
default:
|
|
@@ -212,7 +212,7 @@ export class InteractiveDeploymentCoordinator {
|
|
|
212
212
|
const result = await Clodo.deploy({
|
|
213
213
|
servicePath: this.deploymentState.config.servicePath,
|
|
214
214
|
environment: this.deploymentState.config.environment,
|
|
215
|
-
domain: this.deploymentState.config.
|
|
215
|
+
domain: this.deploymentState.config.credentials.zoneName,
|
|
216
216
|
serviceName: this.options.serviceName,
|
|
217
217
|
dryRun: this.deploymentState.config.dryRun,
|
|
218
218
|
credentials: this.deploymentState.config.credentials
|
|
@@ -92,7 +92,10 @@ export class InteractiveDomainInfoGatherer {
|
|
|
92
92
|
// Generate default worker configuration
|
|
93
93
|
config.worker = config.worker || {};
|
|
94
94
|
config.worker.name = `${config.domain}-data-service`;
|
|
95
|
-
|
|
95
|
+
|
|
96
|
+
// Use account name from credentials for workers.dev subdomain
|
|
97
|
+
const accountName = config.credentials?.accountName || 'tamylatrading';
|
|
98
|
+
config.worker.url = `https://${config.worker.name}.${accountName}.workers.dev`;
|
|
96
99
|
console.log(`\nš§ Generated Configuration:`);
|
|
97
100
|
console.log(` Worker Name: ${config.worker.name}`);
|
|
98
101
|
console.log(` Worker URL: ${config.worker.url}`);
|
|
@@ -111,7 +114,8 @@ export class InteractiveDomainInfoGatherer {
|
|
|
111
114
|
console.log(` š§ Using extracted name: ${customWorkerName}`);
|
|
112
115
|
}
|
|
113
116
|
config.worker.name = customWorkerName;
|
|
114
|
-
|
|
117
|
+
const accountName = config.credentials?.accountName || 'tamylatrading';
|
|
118
|
+
config.worker.url = `https://${config.worker.name}.${accountName}.workers.dev`;
|
|
115
119
|
console.log(`\nā
Updated worker configuration`);
|
|
116
120
|
}
|
|
117
121
|
}
|
|
@@ -35,8 +35,8 @@ export class InteractiveValidationWorkflow {
|
|
|
35
35
|
// Check prerequisites
|
|
36
36
|
await this.validatePrerequisites();
|
|
37
37
|
|
|
38
|
-
// Check authentication
|
|
39
|
-
await this.validateAuthentication();
|
|
38
|
+
// Check authentication - pass existing credentials if available
|
|
39
|
+
await this.validateAuthentication(config);
|
|
40
40
|
|
|
41
41
|
// Check for existing deployments
|
|
42
42
|
await this.checkExistingDeployments(config);
|
|
@@ -68,9 +68,16 @@ export class InteractiveValidationWorkflow {
|
|
|
68
68
|
/**
|
|
69
69
|
* Validate Cloudflare authentication
|
|
70
70
|
*
|
|
71
|
+
* @param {Object} config - Deployment configuration with credentials
|
|
71
72
|
* @returns {Promise<void>}
|
|
72
73
|
*/
|
|
73
|
-
async validateAuthentication() {
|
|
74
|
+
async validateAuthentication(config = {}) {
|
|
75
|
+
// If credentials are already collected, skip authentication check
|
|
76
|
+
if (config.credentials?.token && config.credentials?.accountId) {
|
|
77
|
+
console.log('\nš Using collected Cloudflare credentials...');
|
|
78
|
+
console.log('ā
Authentication validated via deployment workflow');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
74
81
|
console.log('\nš Checking Cloudflare authentication...');
|
|
75
82
|
const isAuthenticated = await checkAuth();
|
|
76
83
|
if (!isAuthenticated) {
|
|
@@ -104,17 +111,144 @@ export class InteractiveValidationWorkflow {
|
|
|
104
111
|
console.log(' ā¹ļø Non-interactive mode: will overwrite existing worker');
|
|
105
112
|
return true;
|
|
106
113
|
}
|
|
107
|
-
|
|
108
|
-
if (!shouldOverwrite) {
|
|
109
|
-
throw new Error('Deployment cancelled - worker already exists');
|
|
110
|
-
}
|
|
111
|
-
return true;
|
|
114
|
+
return await this.handleExistingWorker(config);
|
|
112
115
|
} else {
|
|
113
116
|
console.log(` ā
Worker name '${config.worker.name}' is available`);
|
|
114
117
|
return false;
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Handle existing worker with multiple options
|
|
123
|
+
*
|
|
124
|
+
* @param {Object} config - Deployment configuration
|
|
125
|
+
* @returns {Promise<boolean>} True if deployment should proceed
|
|
126
|
+
*/
|
|
127
|
+
async handleExistingWorker(config) {
|
|
128
|
+
console.log('\nš Existing Worker Options:');
|
|
129
|
+
console.log('===========================');
|
|
130
|
+
const {
|
|
131
|
+
askChoice
|
|
132
|
+
} = await import('../utils/prompt-utils.js');
|
|
133
|
+
const choice = await askChoice('How would you like to handle the existing worker?', ['š Overwrite/Update - Deploy new version (recommended)', 'š Rename - Create with a different worker name', 'š Compare - Show differences before deciding', 'š¾ Backup & Update - Create backup before overwriting', 'ā Cancel - Stop deployment'], 0);
|
|
134
|
+
switch (choice) {
|
|
135
|
+
case 0:
|
|
136
|
+
// Overwrite/Update
|
|
137
|
+
console.log(' ā
Will overwrite existing worker with new deployment');
|
|
138
|
+
return true;
|
|
139
|
+
case 1:
|
|
140
|
+
// Rename
|
|
141
|
+
const newName = await this.promptForNewWorkerName(config.worker.name);
|
|
142
|
+
config.worker.name = newName;
|
|
143
|
+
config.worker.url = `https://${newName}.${config.credentials?.zoneName || config.credentials?.accountName || 'tamylatrading.workers.dev'}`;
|
|
144
|
+
console.log(` ā
Will deploy as new worker: ${newName}`);
|
|
145
|
+
return true;
|
|
146
|
+
case 2:
|
|
147
|
+
// Compare
|
|
148
|
+
await this.compareExistingWorker(config);
|
|
149
|
+
// After comparison, ask again
|
|
150
|
+
return await this.handleExistingWorker(config);
|
|
151
|
+
case 3:
|
|
152
|
+
// Backup & Update
|
|
153
|
+
await this.backupExistingWorker(config);
|
|
154
|
+
console.log(' ā
Backup created, will now overwrite existing worker');
|
|
155
|
+
return true;
|
|
156
|
+
case 4: // Cancel
|
|
157
|
+
default:
|
|
158
|
+
throw new Error('Deployment cancelled by user');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Prompt for a new worker name
|
|
164
|
+
*
|
|
165
|
+
* @param {string} currentName - Current worker name
|
|
166
|
+
* @returns {Promise<string>} New worker name
|
|
167
|
+
*/
|
|
168
|
+
async promptForNewWorkerName(currentName) {
|
|
169
|
+
const {
|
|
170
|
+
askUser
|
|
171
|
+
} = await import('../utils/prompt-utils.js');
|
|
172
|
+
let newName = await askUser(`Enter new worker name (current: ${currentName})`);
|
|
173
|
+
newName = newName.trim();
|
|
174
|
+
if (!newName) {
|
|
175
|
+
throw new Error('Worker name cannot be empty');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Basic validation
|
|
179
|
+
if (newName.length < 3) {
|
|
180
|
+
throw new Error('Worker name must be at least 3 characters long');
|
|
181
|
+
}
|
|
182
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(newName)) {
|
|
183
|
+
throw new Error('Worker name can only contain letters, numbers, hyphens, and underscores');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check if the new name also exists
|
|
187
|
+
const exists = await workerExists(newName);
|
|
188
|
+
if (exists) {
|
|
189
|
+
console.log(` ā ļø Worker '${newName}' also exists!`);
|
|
190
|
+
const useAnyway = await askUser('Use this name anyway? (y/n)', 'n');
|
|
191
|
+
if (useAnyway.toLowerCase() !== 'y' && useAnyway.toLowerCase() !== 'yes') {
|
|
192
|
+
return await this.promptForNewWorkerName(currentName);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return newName;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Compare existing worker configuration
|
|
200
|
+
*
|
|
201
|
+
* @param {Object} config - Deployment configuration
|
|
202
|
+
*/
|
|
203
|
+
async compareExistingWorker(config) {
|
|
204
|
+
console.log('\nš Comparing Worker Configurations:');
|
|
205
|
+
console.log('=====================================');
|
|
206
|
+
try {
|
|
207
|
+
// This is a simplified comparison - in a real implementation,
|
|
208
|
+
// you might fetch the existing worker's configuration
|
|
209
|
+
console.log(` š Existing Worker: ${config.worker.name}`);
|
|
210
|
+
console.log(` š URL: https://${config.worker.name}.${config.credentials?.zoneName || config.credentials?.accountName || 'tamylatrading.workers.dev'}`);
|
|
211
|
+
console.log(` š
Last deployed: Unknown (would need API call to check)`);
|
|
212
|
+
console.log(` š§ Environment: ${config.environment || 'production'}`);
|
|
213
|
+
console.log('\n š New Deployment:');
|
|
214
|
+
console.log(` š Worker: ${config.worker.name}`);
|
|
215
|
+
console.log(` š URL: ${config.worker.url}`);
|
|
216
|
+
console.log(` š§ Environment: ${config.environment || 'production'}`);
|
|
217
|
+
console.log(` šļø Database: ${config.database?.name || 'None'}`);
|
|
218
|
+
console.log(` š Secrets: ${Object.keys(config.secrets || {}).length} configured`);
|
|
219
|
+
console.log('\n ā¹ļø Note: Full comparison would require additional API calls to fetch existing worker details');
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.log(` ā ļø Could not compare configurations: ${error.message}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Create a backup of existing worker
|
|
227
|
+
*
|
|
228
|
+
* @param {Object} config - Deployment configuration
|
|
229
|
+
*/
|
|
230
|
+
async backupExistingWorker(config) {
|
|
231
|
+
console.log('\nš¾ Creating Worker Backup:');
|
|
232
|
+
console.log('===========================');
|
|
233
|
+
try {
|
|
234
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
235
|
+
const backupName = `${config.worker.name}-backup-${timestamp}`;
|
|
236
|
+
console.log(` š Creating backup worker: ${backupName}`);
|
|
237
|
+
|
|
238
|
+
// Note: This is a placeholder - actual backup would require
|
|
239
|
+
// downloading the existing worker code and creating a new worker
|
|
240
|
+
// This would be complex and might not be worth implementing
|
|
241
|
+
// since Cloudflare doesn't provide direct worker code access
|
|
242
|
+
|
|
243
|
+
console.log(` ā ļø Worker backup not fully implemented yet`);
|
|
244
|
+
console.log(` š” Consider manually backing up important worker code before overwriting`);
|
|
245
|
+
console.log(` š” You can also use version control to track changes`);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.log(` ā ļø Backup failed: ${error.message}`);
|
|
248
|
+
console.log(` š Continuing with deployment anyway...`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
118
252
|
/**
|
|
119
253
|
* Execute comprehensive validation
|
|
120
254
|
*
|