Skip to content

Commit 0f99a12

Browse files
committed
Enhance plugin core utilities and type definitions
- Refactored plugin core with comprehensive template and validation utilities - Updated MySQL plugin to leverage new core template generation methods - Restructured type definitions for better type safety and modularity - Added validation and template generation functions for database plugins - Improved type definitions for plugin configurations and answers
1 parent 245859c commit 0f99a12

File tree

16 files changed

+348
-217
lines changed

16 files changed

+348
-217
lines changed

src/plugins/core/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Re-export all types from our core types
2+
export * from './types';
3+
4+
// Export base plugin creator
5+
export { createDatabasePlugin } from '../databases/core/base-plugin';
6+
7+
// Export template utilities
8+
export {
9+
createBaseTemplateVariables,
10+
mergeTemplateVariables
11+
} from './template-utils';
12+
13+
// Export validation utilities
14+
export {
15+
validatePort,
16+
validateRequired
17+
} from './validation';
18+
19+
// Export general utilities
20+
export {
21+
createConnectionStringValidator
22+
} from './utils';

src/plugins/core/template-utils.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { DatabasePluginConfig, DatabaseAnswers, TemplateVariables } from '@types';
2+
3+
export const createBaseTemplateVariables = (
4+
config: DatabasePluginConfig,
5+
answers: DatabaseAnswers
6+
): TemplateVariables => ({
7+
database: {
8+
type: config.name.toLowerCase(),
9+
name: answers.database,
10+
user: answers.username,
11+
password: answers.password,
12+
port: answers.port.toString(),
13+
host: 'localhost'
14+
},
15+
environment: {
16+
[`${config.envPrefix}_DATABASE`]: answers.database,
17+
[`${config.envPrefix}_USER`]: answers.username,
18+
[`${config.envPrefix}_PASSWORD`]: answers.password,
19+
[`${config.envPrefix}_ROOT_PASSWORD`]: answers.rootPassword || answers.password,
20+
},
21+
volumes: {
22+
data: config.volumePath
23+
},
24+
image: {
25+
name: config.image.name,
26+
tag: answers.version || config.defaultVersion
27+
}
28+
});
29+
30+
export const mergeTemplateVariables = (
31+
base: TemplateVariables,
32+
custom: Partial<TemplateVariables>
33+
): TemplateVariables => ({
34+
...base,
35+
...custom,
36+
environment: {
37+
...base.environment,
38+
...custom.environment
39+
}
40+
});

src/plugins/core/types.ts

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,41 @@
1-
export interface DatabasePluginConfig {
2-
name: string;
3-
defaultPort: number;
4-
containerName: string;
5-
volumePath: string;
6-
envPrefix: string;
7-
defaultVersion: string;
8-
image: {
9-
name: string;
10-
tag: string;
11-
};
12-
healthcheck?: {
13-
test: string[] | string;
14-
interval: string;
15-
timeout: string;
16-
retries: number;
17-
};
18-
}
1+
import {
2+
DatabasePlugin as BaseDBPlugin,
3+
DatabasePluginConfig as BaseDBConfig,
4+
DatabaseTemplateVariables as BaseTemplateVars,
5+
DatabaseAnswers
6+
} from '@/types/database';
197

20-
export interface DatabaseAnswers {
21-
database: string;
22-
username: string;
23-
password: string;
24-
port: string;
25-
version?: string;
26-
charset?: string;
27-
collation?: string;
28-
rootPassword?: string;
8+
import {
9+
Question,
10+
ValidationRules,
11+
PluginUtils,
12+
PluginTemplateUtils
13+
} from '@/types/plugin';
14+
15+
// Extend base plugin types with additional functionality
16+
export interface DatabasePlugin extends BaseDBPlugin {
17+
getQuestions: () => Promise<Question[]>;
18+
processAnswers: (answers: Record<string, any>) => DatabaseAnswers;
2919
}
3020

31-
export interface ValidationResult {
32-
isValid: boolean;
33-
errors: string[];
21+
export interface DatabasePluginConfig extends BaseDBConfig {
22+
validations?: Record<string, ValidationRules>;
23+
additionalQuestions?: Question[];
3424
}
3525

36-
export interface TemplateVariables {
37-
database: {
38-
type: string;
39-
name: string;
40-
user: string;
41-
password: string;
42-
port: string;
43-
host: string;
44-
};
45-
environment: Record<string, string>;
46-
volumes: {
47-
data: string;
48-
};
49-
image: {
50-
name: string;
51-
tag: string;
26+
// Template types
27+
export interface TemplateVariables extends BaseTemplateVars {
28+
database: BaseTemplateVars['database'] & {
29+
charset?: string;
30+
collation?: string;
5231
};
5332
}
5433

55-
export interface DatabasePlugin {
56-
name: string;
57-
defaultPort: number;
58-
getTemplateVariables(answers: DatabaseAnswers): TemplateVariables;
59-
validateConfig(answers: DatabaseAnswers): ValidationResult;
60-
validateConnectionString(url: string): boolean;
61-
}
34+
// Re-export plugin types
35+
export type {
36+
Question,
37+
ValidationRules,
38+
PluginUtils,
39+
PluginTemplateUtils,
40+
DatabaseAnswers
41+
};

src/plugins/core/utils.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { DatabaseAnswers, ValidationResult } from '@types';
2+
3+
export const createConnectionStringValidator = (databaseType: string) =>
4+
(connectionString: string): boolean => {
5+
const pattern = new RegExp(`^${databaseType}:\/\/[^:]+:[^@]+@[^:]+:\d+\/\w+$`);
6+
return pattern.test(connectionString);
7+
};
8+
9+
export const validatePort = (port: string): ValidationResult => {
10+
const errors: string[] = [];
11+
const portNum = parseInt(port);
12+
13+
if (isNaN(portNum) || portNum < 1024 || portNum > 65535) {
14+
errors.push('Port must be a number between 1024 and 65535');
15+
}
16+
17+
return {
18+
isValid: errors.length === 0,
19+
errors
20+
};
21+
};
22+
23+
export const validateRequired = (value: any, fieldName: string): ValidationResult => {
24+
return {
25+
isValid: !!value,
26+
errors: value ? [] : [`${fieldName} is required`]
27+
};
28+
};

src/plugins/core/validation.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ValidationResult } from '@types';
2+
3+
export const validatePort = (port: string): ValidationResult => {
4+
const errors: string[] = [];
5+
const portNum = parseInt(port);
6+
7+
if (isNaN(portNum) || portNum < 1024 || portNum > 65535) {
8+
errors.push('Port must be a number between 1024 and 65535');
9+
}
10+
11+
return {
12+
isValid: errors.length === 0,
13+
errors
14+
};
15+
};
16+
17+
export const validateRequired = (value: any, fieldName: string): ValidationResult => {
18+
return {
19+
isValid: !!value,
20+
errors: value ? [] : [`${fieldName} is required`]
21+
};
22+
};

src/plugins/databases/core/base-plugin.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
import {
2-
DatabaseAnswers,
3-
DatabasePlugin,
4-
DatabasePluginConfig,
5-
Question,
6-
TemplateVariables,
7-
ValidationResult
8-
} from '@types';
1+
import { Question } from "@/types/cli";
2+
import { DatabasePlugin, DatabasePluginConfig } from "@/types/database";
93
import { getDockerImageVersions } from '@utils/docker-versions';
104
import { generateSecurePassword } from '@utils/passwords';
115

@@ -59,16 +53,16 @@ export function createDatabasePlugin(config: DatabasePluginConfig): DatabasePlug
5953
name: 'PASSWORD_TYPE',
6054
message: 'How would you like to set the database password?',
6155
choices: [
62-
{ label: 'Generate', value: 'generate', hint: 'Generate a secure password' },
63-
{ label: 'Custom', value: 'custom', hint: 'Use a custom password' }
56+
{ title: 'Generate', value: 'generate', description: 'Generate a secure password' },
57+
{ title: 'Custom', value: 'custom', description: 'Use a custom password' }
6458
],
6559
default: 'generate'
6660
},
6761
{
6862
type: 'text',
6963
name: `${prefix}_PASSWORD`,
7064
message: 'Enter your database password:',
71-
when: (answers) => answers.PASSWORD_TYPE === 'custom',
65+
when: (answers: Record<string, any>) => answers.PASSWORD_TYPE === 'custom',
7266
validate: (value) => {
7367
if (!value) return 'Password is required';
7468
if (value.length < 8) return 'Password must be at least 8 characters long';
@@ -92,9 +86,9 @@ export function createDatabasePlugin(config: DatabasePluginConfig): DatabasePlug
9286
name: `${prefix}_VERSION`,
9387
message: `Which ${config.name} version would you like to use?`,
9488
choices: versions.map(version => ({
89+
title: `${config.name} ${version}`,
9590
value: version,
96-
label: `${config.name} ${version}`,
97-
hint: version.includes('alpine') ? 'Recommended for production' : undefined
91+
description: version.includes('alpine') ? 'Recommended for production' : undefined
9892
})),
9993
default: versions.find(v => v === config.defaultVersion) || versions[0]
10094
}
@@ -105,7 +99,7 @@ export function createDatabasePlugin(config: DatabasePluginConfig): DatabasePlug
10599

106100
function validateConfig(answers: DatabaseAnswers): ValidationResult {
107101
const errors: string[] = [];
108-
const requiredFields = ['database', 'username', 'password', 'port'];
102+
const requiredFields = ['database', 'username', 'password', 'port'] as const;
109103

110104
for (const field of requiredFields) {
111105
if (!answers[field]) {
@@ -115,7 +109,7 @@ export function createDatabasePlugin(config: DatabasePluginConfig): DatabasePlug
115109

116110
if (config.validations) {
117111
Object.entries(config.validations).forEach(([field, rules]) => {
118-
const value = answers[field];
112+
const value = answers[field as keyof DatabaseAnswers];
119113
if (rules.required && !value) {
120114
errors.push(`${field} is required`);
121115
}
@@ -145,7 +139,7 @@ export function createDatabasePlugin(config: DatabasePluginConfig): DatabasePlug
145139
return pattern.test(connectionString);
146140
}
147141

148-
function getTemplateVariables(answers: DatabaseAnswers): TemplateVariables {
142+
function getTemplateVariables(answers: DatabaseAnswers): DatabaseTemplateVariables {
149143
const prefix = config.envPrefix;
150144
return {
151145
database: {

src/plugins/databases/mysql/index.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1-
import { DatabasePlugin } from '@types';
2-
import { createDatabasePlugin } from '../core/base-plugin';
1+
import { DatabasePlugin, DatabaseAnswers } from '@types';
2+
import {
3+
createDatabasePlugin,
4+
createBaseTemplateVariables,
5+
mergeTemplateVariables
6+
} from '../../core';
37
import { mysqlConfig } from './config';
8+
import { validateConfig } from './validation';
49

510
// Create the plugin instance using the base plugin creator
6-
const MySQLPlugin: DatabasePlugin = createDatabasePlugin(mysqlConfig);
11+
const MySQLPlugin: DatabasePlugin = {
12+
...createDatabasePlugin(mysqlConfig),
13+
validateConfig,
14+
getTemplateVariables: (answers: DatabaseAnswers) => {
15+
const baseVariables = createBaseTemplateVariables(mysqlConfig, answers);
16+
return mergeTemplateVariables(baseVariables, {
17+
environment: {
18+
MYSQL_CHARACTER_SET_SERVER: answers.charset || 'utf8mb4',
19+
MYSQL_COLLATION_SERVER: answers.collation || 'utf8mb4_unicode_ci'
20+
}
21+
});
22+
}
23+
};
724

825
export default MySQLPlugin;

src/plugins/databases/mysql/validation.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import { DatabaseAnswers, ValidationResult } from '@types';
2+
import { validateRequired, validatePort } from '../../core/utils';
23

3-
export function validateConfig(config: DatabaseAnswers): ValidationResult {
4+
export function validateConfig(answers: DatabaseAnswers): ValidationResult {
45
const errors: string[] = [];
56

6-
const requiredFields = ['database', 'username', 'password', 'port'];
7-
for (const field of requiredFields) {
8-
if (!config[field]) {
9-
errors.push(`${field} is required`);
10-
}
7+
// Base validation
8+
const requiredValidation = validateRequired(answers.database, 'database');
9+
const portValidation = validatePort(answers.port);
10+
11+
errors.push(...requiredValidation.errors, ...portValidation.errors);
12+
13+
// MySQL specific validation
14+
if (answers.charset && !['utf8mb4', 'utf8', 'latin1'].includes(answers.charset)) {
15+
errors.push('Invalid character set');
1116
}
1217

13-
if (config.port) {
14-
const port = parseInt(config.port.toString());
15-
if (isNaN(port) || port < 1024 || port > 65535) {
16-
errors.push('Port must be a number between 1024 and 65535');
17-
}
18+
if (answers.collation && !answers.collation.startsWith(answers.charset || '')) {
19+
errors.push('Collation must match character set');
1820
}
1921

2022
return {

src/plugins/index.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { DatabasePlugin, PluginType } from '@types';
2+
import MySQLPlugin from './databases/mysql';
3+
import MariaDBPlugin from './databases/mariadb';
4+
import PostgreSQLPlugin from './databases/postgresql';
5+
6+
// Plugin registry
7+
const plugins: Record<string, DatabasePlugin> = {
8+
mysql: MySQLPlugin,
9+
mariadb: MariaDBPlugin,
10+
postgresql: PostgreSQLPlugin
11+
} as const;
12+
13+
// Plugin type guard
14+
export const isDatabasePlugin = (plugin: unknown): plugin is DatabasePlugin => {
15+
return plugin !== null &&
16+
typeof plugin === 'object' &&
17+
'type' in plugin &&
18+
(plugin as DatabasePlugin).type === 'database';
19+
};
20+
21+
// Plugin utilities with proper typing
22+
export const getPlugin = (name: string): DatabasePlugin | undefined =>
23+
plugins[name];
24+
25+
export const getPluginsByType = (type: PluginType): DatabasePlugin[] =>
26+
Object.values(plugins).filter(plugin =>
27+
isDatabasePlugin(plugin) && plugin.type === type
28+
);
29+
30+
export const getAllPlugins = (): DatabasePlugin[] =>
31+
Object.values(plugins);
32+
33+
// Export core functionality
34+
export * from './core';
35+
36+
// Export plugin registry
37+
export default plugins;

0 commit comments

Comments
 (0)