@friggframework/devtools 2.0.0--canary.522.923dfae.0 → 2.0.0--canary.540.c5ef83f.0
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/frigg-cli/README.md +1 -1
- package/frigg-cli/__tests__/unit/commands/doctor.test.js +2 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +19 -23
- package/frigg-cli/__tests__/unit/dependencies.test.js +2 -2
- package/frigg-cli/doctor-command/index.js +16 -17
- package/frigg-cli/index.js +6 -21
- package/frigg-cli/index.test.js +2 -7
- package/frigg-cli/init-command/backend-first-handler.js +42 -124
- package/frigg-cli/init-command/index.js +1 -2
- package/frigg-cli/init-command/template-handler.js +3 -13
- package/frigg-cli/install-command/backend-js.js +3 -3
- package/frigg-cli/install-command/environment-variables.js +19 -16
- package/frigg-cli/install-command/environment-variables.test.js +13 -12
- package/frigg-cli/install-command/index.js +9 -14
- package/frigg-cli/install-command/integration-file.js +3 -3
- package/frigg-cli/install-command/logger.js +12 -0
- package/frigg-cli/install-command/validate-package.js +9 -5
- package/frigg-cli/jest.config.js +1 -4
- package/frigg-cli/repair-command/index.js +128 -101
- package/frigg-cli/start-command/index.js +2 -246
- package/frigg-cli/ui-command/index.js +36 -58
- package/frigg-cli/utils/repo-detection.js +37 -85
- package/infrastructure/docs/iam-policy-templates.md +1 -1
- package/infrastructure/domains/networking/vpc-builder.test.js +4 -2
- package/infrastructure/domains/networking/vpc-resolver.test.js +1 -1
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +7 -4
- package/infrastructure/domains/shared/resource-discovery.js +5 -5
- package/infrastructure/domains/shared/types/app-definition.js +0 -21
- package/infrastructure/domains/shared/types/discovery-result.test.js +1 -1
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +1 -10
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +2 -2
- package/infrastructure/infrastructure-composer.test.js +2 -2
- package/management-ui/README.md +109 -245
- package/package.json +7 -8
- package/frigg-cli/__tests__/application/use-cases/AddApiModuleToIntegrationUseCase.test.js +0 -326
- package/frigg-cli/__tests__/application/use-cases/CreateApiModuleUseCase.test.js +0 -337
- package/frigg-cli/__tests__/domain/entities/ApiModule.test.js +0 -373
- package/frigg-cli/__tests__/domain/entities/AppDefinition.test.js +0 -313
- package/frigg-cli/__tests__/domain/services/IntegrationValidator.test.js +0 -269
- package/frigg-cli/__tests__/domain/value-objects/IntegrationName.test.js +0 -82
- package/frigg-cli/__tests__/infrastructure/adapters/IntegrationJsUpdater.test.js +0 -408
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemApiModuleRepository.test.js +0 -583
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemAppDefinitionRepository.test.js +0 -314
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemIntegrationRepository.test.js +0 -383
- package/frigg-cli/__tests__/unit/commands/init.test.js +0 -406
- package/frigg-cli/__tests__/unit/commands/repair.test.js +0 -275
- package/frigg-cli/__tests__/unit/start-command/application/RunPreflightChecksUseCase.test.js +0 -411
- package/frigg-cli/__tests__/unit/start-command/infrastructure/DatabaseAdapter.test.js +0 -405
- package/frigg-cli/__tests__/unit/start-command/infrastructure/DockerAdapter.test.js +0 -496
- package/frigg-cli/__tests__/unit/start-command/presentation/InteractivePromptAdapter.test.js +0 -474
- package/frigg-cli/__tests__/unit/utils/output.test.js +0 -196
- package/frigg-cli/application/use-cases/AddApiModuleToIntegrationUseCase.js +0 -93
- package/frigg-cli/application/use-cases/CreateApiModuleUseCase.js +0 -93
- package/frigg-cli/application/use-cases/CreateIntegrationUseCase.js +0 -103
- package/frigg-cli/container.js +0 -172
- package/frigg-cli/docs/OUTPUT_MIGRATION_GUIDE.md +0 -286
- package/frigg-cli/domain/entities/ApiModule.js +0 -272
- package/frigg-cli/domain/entities/AppDefinition.js +0 -227
- package/frigg-cli/domain/entities/Integration.js +0 -198
- package/frigg-cli/domain/exceptions/DomainException.js +0 -24
- package/frigg-cli/domain/ports/IApiModuleRepository.js +0 -53
- package/frigg-cli/domain/ports/IAppDefinitionRepository.js +0 -43
- package/frigg-cli/domain/ports/IIntegrationRepository.js +0 -61
- package/frigg-cli/domain/services/IntegrationValidator.js +0 -185
- package/frigg-cli/domain/value-objects/IntegrationId.js +0 -42
- package/frigg-cli/domain/value-objects/IntegrationName.js +0 -60
- package/frigg-cli/domain/value-objects/SemanticVersion.js +0 -70
- package/frigg-cli/infrastructure/UnitOfWork.js +0 -46
- package/frigg-cli/infrastructure/adapters/BackendJsUpdater.js +0 -197
- package/frigg-cli/infrastructure/adapters/FileSystemAdapter.js +0 -224
- package/frigg-cli/infrastructure/adapters/IntegrationJsUpdater.js +0 -249
- package/frigg-cli/infrastructure/adapters/SchemaValidator.js +0 -92
- package/frigg-cli/infrastructure/repositories/FileSystemApiModuleRepository.js +0 -373
- package/frigg-cli/infrastructure/repositories/FileSystemAppDefinitionRepository.js +0 -116
- package/frigg-cli/infrastructure/repositories/FileSystemIntegrationRepository.js +0 -277
- package/frigg-cli/package-lock.json +0 -16226
- package/frigg-cli/start-command/application/RunPreflightChecksUseCase.js +0 -376
- package/frigg-cli/start-command/infrastructure/DatabaseAdapter.js +0 -591
- package/frigg-cli/start-command/infrastructure/DockerAdapter.js +0 -306
- package/frigg-cli/start-command/presentation/InteractivePromptAdapter.js +0 -329
- package/frigg-cli/templates/backend/.env.example +0 -62
- package/frigg-cli/templates/backend/.eslintrc.json +0 -12
- package/frigg-cli/templates/backend/.prettierrc +0 -6
- package/frigg-cli/templates/backend/docker-compose.yml +0 -22
- package/frigg-cli/templates/backend/index.js +0 -96
- package/frigg-cli/templates/backend/infrastructure.js +0 -12
- package/frigg-cli/templates/backend/jest.config.js +0 -17
- package/frigg-cli/templates/backend/package.json +0 -50
- package/frigg-cli/templates/backend/src/api-modules/.gitkeep +0 -10
- package/frigg-cli/templates/backend/src/base/.gitkeep +0 -7
- package/frigg-cli/templates/backend/src/integrations/.gitkeep +0 -10
- package/frigg-cli/templates/backend/src/integrations/ExampleIntegration.js +0 -65
- package/frigg-cli/templates/backend/src/utils/.gitkeep +0 -7
- package/frigg-cli/templates/backend/test/setup.js +0 -30
- package/frigg-cli/templates/backend/ui-extensions/.gitkeep +0 -0
- package/frigg-cli/templates/backend/ui-extensions/README.md +0 -77
- package/frigg-cli/utils/__tests__/repo-detection.test.js +0 -436
- package/frigg-cli/utils/output.js +0 -382
- package/frigg-cli/validate-command/__tests__/adapters/validate-command.test.js +0 -205
- package/frigg-cli/validate-command/__tests__/application/validate-app-use-case.test.js +0 -104
- package/frigg-cli/validate-command/__tests__/domain/fix-suggestion.test.js +0 -153
- package/frigg-cli/validate-command/__tests__/domain/validation-error.test.js +0 -162
- package/frigg-cli/validate-command/__tests__/domain/validation-result.test.js +0 -152
- package/frigg-cli/validate-command/__tests__/infrastructure/api-module-validator.test.js +0 -332
- package/frigg-cli/validate-command/__tests__/infrastructure/app-definition-validator.test.js +0 -191
- package/frigg-cli/validate-command/__tests__/infrastructure/integration-class-validator.test.js +0 -146
- package/frigg-cli/validate-command/__tests__/infrastructure/template-validation.test.js +0 -155
- package/frigg-cli/validate-command/adapters/cli/validate-command.js +0 -199
- package/frigg-cli/validate-command/application/use-cases/validate-app-use-case.js +0 -35
- package/frigg-cli/validate-command/domain/entities/validation-result.js +0 -74
- package/frigg-cli/validate-command/domain/value-objects/fix-suggestion.js +0 -74
- package/frigg-cli/validate-command/domain/value-objects/validation-error.js +0 -68
- package/frigg-cli/validate-command/infrastructure/validators/api-module-validator.js +0 -181
- package/frigg-cli/validate-command/infrastructure/validators/app-definition-validator.js +0 -128
- package/frigg-cli/validate-command/infrastructure/validators/integration-class-validator.js +0 -113
- package/infrastructure/domains/admin-scripts/admin-script-builder.js +0 -200
- package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +0 -499
- package/infrastructure/domains/admin-scripts/index.js +0 -5
- package/infrastructure/jest.config.js +0 -16
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Output - Unified CLI output and interaction utilities
|
|
3
|
-
*
|
|
4
|
-
* Provides consistent formatting, colors, and interactive prompts across all CLI commands.
|
|
5
|
-
* Replaces direct usage of chalk, console.log, inquirer, and readline.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* const output = require('./utils/output');
|
|
9
|
-
*
|
|
10
|
-
* output.success('Module installed successfully');
|
|
11
|
-
* output.error('Failed to connect to database', error);
|
|
12
|
-
* output.info('Starting deployment...');
|
|
13
|
-
*
|
|
14
|
-
* const spinner = output.spinner('Downloading dependencies...');
|
|
15
|
-
* // do work
|
|
16
|
-
* spinner.succeed('Dependencies downloaded');
|
|
17
|
-
*
|
|
18
|
-
* const answer = await output.prompt({
|
|
19
|
-
* type: 'confirm',
|
|
20
|
-
* name: 'continue',
|
|
21
|
-
* message: 'Continue with installation?'
|
|
22
|
-
* });
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
const chalk = require('chalk');
|
|
26
|
-
const { select, input, confirm, checkbox, password } = require('@inquirer/prompts');
|
|
27
|
-
|
|
28
|
-
class Output {
|
|
29
|
-
/**
|
|
30
|
-
* Display a success message with a checkmark
|
|
31
|
-
* @param {string} message - Success message to display
|
|
32
|
-
*/
|
|
33
|
-
success(message) {
|
|
34
|
-
console.log(chalk.green('✓'), chalk.green(message));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Display an error message with an X mark
|
|
39
|
-
* @param {string} message - Error message to display
|
|
40
|
-
* @param {Error} [error] - Optional error object to display
|
|
41
|
-
*/
|
|
42
|
-
error(message, error) {
|
|
43
|
-
console.error(chalk.red('✗'), chalk.red(message));
|
|
44
|
-
if (error && process.env.DEBUG) {
|
|
45
|
-
console.error(chalk.gray(error.stack || error.message));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Display an info message with an info icon
|
|
51
|
-
* @param {string} message - Info message to display
|
|
52
|
-
*/
|
|
53
|
-
info(message) {
|
|
54
|
-
console.log(chalk.blue('ℹ'), message);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Display a warning message
|
|
59
|
-
* @param {string} message - Warning message to display
|
|
60
|
-
*/
|
|
61
|
-
warn(message) {
|
|
62
|
-
console.warn(chalk.yellow('⚠'), chalk.yellow(message));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Display a debug message (only when DEBUG env var is set)
|
|
67
|
-
* @param {string} message - Debug message to display
|
|
68
|
-
*/
|
|
69
|
-
debug(message) {
|
|
70
|
-
if (process.env.DEBUG) {
|
|
71
|
-
console.log(chalk.gray('🐛'), chalk.gray(message));
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Display a header/title
|
|
77
|
-
* @param {string} title - Title to display
|
|
78
|
-
*/
|
|
79
|
-
header(title) {
|
|
80
|
-
console.log('');
|
|
81
|
-
console.log(chalk.bold.cyan(title));
|
|
82
|
-
console.log(chalk.cyan('─'.repeat(title.length)));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Display a section separator
|
|
87
|
-
*/
|
|
88
|
-
separator() {
|
|
89
|
-
console.log(chalk.gray('─'.repeat(50)));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Display a blank line
|
|
94
|
-
*/
|
|
95
|
-
newline() {
|
|
96
|
-
console.log('');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Display a table of data
|
|
101
|
-
* @param {Array<Object>} data - Array of objects to display
|
|
102
|
-
* @param {Array<string>} [columns] - Column keys to display (defaults to all)
|
|
103
|
-
*/
|
|
104
|
-
table(data, columns) {
|
|
105
|
-
if (!data || data.length === 0) {
|
|
106
|
-
this.info('No data to display');
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const keys = columns || Object.keys(data[0]);
|
|
111
|
-
|
|
112
|
-
// Calculate column widths
|
|
113
|
-
const widths = {};
|
|
114
|
-
keys.forEach(key => {
|
|
115
|
-
widths[key] = Math.max(
|
|
116
|
-
key.length,
|
|
117
|
-
...data.map(row => String(row[key] || '').length)
|
|
118
|
-
);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Print header
|
|
122
|
-
const headerRow = keys.map(key =>
|
|
123
|
-
chalk.bold(key.padEnd(widths[key]))
|
|
124
|
-
).join(' ');
|
|
125
|
-
console.log(headerRow);
|
|
126
|
-
console.log(keys.map(key =>
|
|
127
|
-
'─'.repeat(widths[key])
|
|
128
|
-
).join(' '));
|
|
129
|
-
|
|
130
|
-
// Print rows
|
|
131
|
-
data.forEach(row => {
|
|
132
|
-
const dataRow = keys.map(key =>
|
|
133
|
-
String(row[key] || '').padEnd(widths[key])
|
|
134
|
-
).join(' ');
|
|
135
|
-
console.log(dataRow);
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Display key-value pairs
|
|
141
|
-
* @param {Object} data - Object with key-value pairs
|
|
142
|
-
*/
|
|
143
|
-
keyValue(data) {
|
|
144
|
-
const maxKeyLength = Math.max(...Object.keys(data).map(k => k.length));
|
|
145
|
-
|
|
146
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
147
|
-
const formattedKey = chalk.gray(`${key.padEnd(maxKeyLength)}:`);
|
|
148
|
-
console.log(`${formattedKey} ${value}`);
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Display JSON with syntax highlighting
|
|
154
|
-
* @param {Object} data - Data to display as JSON
|
|
155
|
-
* @param {number} [indent=2] - Indentation level
|
|
156
|
-
*/
|
|
157
|
-
json(data, indent = 2) {
|
|
158
|
-
const json = JSON.stringify(data, null, indent);
|
|
159
|
-
// Basic syntax highlighting
|
|
160
|
-
const highlighted = json
|
|
161
|
-
.replace(/"([^"]+)":/g, chalk.blue('"$1"') + ':') // keys
|
|
162
|
-
.replace(/: "([^"]+)"/g, ': ' + chalk.green('"$1"')) // string values
|
|
163
|
-
.replace(/: (\d+)/g, ': ' + chalk.yellow('$1')) // numbers
|
|
164
|
-
.replace(/: (true|false|null)/g, ': ' + chalk.magenta('$1')); // booleans/null
|
|
165
|
-
|
|
166
|
-
console.log(highlighted);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Create a spinner for long-running operations
|
|
171
|
-
* @param {string} text - Spinner text
|
|
172
|
-
* @returns {Object} Spinner object with update/succeed/fail/stop methods
|
|
173
|
-
*/
|
|
174
|
-
spinner(text) {
|
|
175
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
176
|
-
let frameIndex = 0;
|
|
177
|
-
let interval = null;
|
|
178
|
-
let currentText = text;
|
|
179
|
-
|
|
180
|
-
const render = () => {
|
|
181
|
-
process.stdout.write(`\r${chalk.cyan(frames[frameIndex])} ${currentText}`);
|
|
182
|
-
frameIndex = (frameIndex + 1) % frames.length;
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const clear = () => {
|
|
186
|
-
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const start = () => {
|
|
190
|
-
if (interval) return;
|
|
191
|
-
interval = setInterval(render, 80);
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const stop = () => {
|
|
195
|
-
if (interval) {
|
|
196
|
-
clearInterval(interval);
|
|
197
|
-
interval = null;
|
|
198
|
-
clear();
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
// Auto-start
|
|
203
|
-
start();
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
update: (newText) => {
|
|
207
|
-
currentText = newText;
|
|
208
|
-
},
|
|
209
|
-
succeed: (message) => {
|
|
210
|
-
stop();
|
|
211
|
-
this.success(message || currentText);
|
|
212
|
-
},
|
|
213
|
-
fail: (message) => {
|
|
214
|
-
stop();
|
|
215
|
-
this.error(message || currentText);
|
|
216
|
-
},
|
|
217
|
-
stop: () => {
|
|
218
|
-
stop();
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Prompt user with a question (uses @inquirer/prompts)
|
|
225
|
-
* @param {Object} question - Question configuration
|
|
226
|
-
* @returns {Promise<any>} User's answer
|
|
227
|
-
*/
|
|
228
|
-
async prompt(question) {
|
|
229
|
-
const { type, name, message, choices, initial, validate } = question;
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
switch (type) {
|
|
233
|
-
case 'select':
|
|
234
|
-
return await select({ message, choices, default: initial });
|
|
235
|
-
|
|
236
|
-
case 'input':
|
|
237
|
-
return await input({ message, default: initial, validate });
|
|
238
|
-
|
|
239
|
-
case 'confirm':
|
|
240
|
-
return await confirm({ message, default: initial });
|
|
241
|
-
|
|
242
|
-
case 'checkbox':
|
|
243
|
-
return await checkbox({ message, choices, validate });
|
|
244
|
-
|
|
245
|
-
case 'password':
|
|
246
|
-
return await password({ message, validate });
|
|
247
|
-
|
|
248
|
-
default:
|
|
249
|
-
throw new Error(`Unknown prompt type: ${type}`);
|
|
250
|
-
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
// User cancelled (Ctrl+C)
|
|
253
|
-
if (error.message === 'User force closed the prompt') {
|
|
254
|
-
this.warn('Operation cancelled by user');
|
|
255
|
-
process.exit(0);
|
|
256
|
-
}
|
|
257
|
-
throw error;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Prompt for confirmation
|
|
263
|
-
* @param {string} message - Confirmation message
|
|
264
|
-
* @param {boolean} [defaultValue=false] - Default value
|
|
265
|
-
* @returns {Promise<boolean>} User's answer
|
|
266
|
-
*/
|
|
267
|
-
async confirm(message, defaultValue = false) {
|
|
268
|
-
return this.prompt({
|
|
269
|
-
type: 'confirm',
|
|
270
|
-
message,
|
|
271
|
-
initial: defaultValue
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Prompt for text input
|
|
277
|
-
* @param {string} message - Input message
|
|
278
|
-
* @param {string} [defaultValue] - Default value
|
|
279
|
-
* @param {Function} [validate] - Validation function
|
|
280
|
-
* @returns {Promise<string>} User's input
|
|
281
|
-
*/
|
|
282
|
-
async input(message, defaultValue, validate) {
|
|
283
|
-
return this.prompt({
|
|
284
|
-
type: 'input',
|
|
285
|
-
message,
|
|
286
|
-
initial: defaultValue,
|
|
287
|
-
validate
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Prompt for selection from a list
|
|
293
|
-
* @param {string} message - Selection message
|
|
294
|
-
* @param {Array<Object|string>} choices - Array of choices
|
|
295
|
-
* @param {any} [defaultValue] - Default value
|
|
296
|
-
* @returns {Promise<any>} Selected value
|
|
297
|
-
*/
|
|
298
|
-
async select(message, choices, defaultValue) {
|
|
299
|
-
// Normalize choices to {name, value} format
|
|
300
|
-
const normalizedChoices = choices.map(choice => {
|
|
301
|
-
if (typeof choice === 'string') {
|
|
302
|
-
return { name: choice, value: choice };
|
|
303
|
-
}
|
|
304
|
-
return choice;
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
return this.prompt({
|
|
308
|
-
type: 'select',
|
|
309
|
-
message,
|
|
310
|
-
choices: normalizedChoices,
|
|
311
|
-
initial: defaultValue
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Prompt for multiple selections
|
|
317
|
-
* @param {string} message - Selection message
|
|
318
|
-
* @param {Array<Object|string>} choices - Array of choices
|
|
319
|
-
* @returns {Promise<Array>} Selected values
|
|
320
|
-
*/
|
|
321
|
-
async checkbox(message, choices) {
|
|
322
|
-
// Normalize choices to {name, value, checked} format
|
|
323
|
-
const normalizedChoices = choices.map(choice => {
|
|
324
|
-
if (typeof choice === 'string') {
|
|
325
|
-
return { name: choice, value: choice, checked: false };
|
|
326
|
-
}
|
|
327
|
-
return { ...choice, checked: choice.checked || false };
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
return this.prompt({
|
|
331
|
-
type: 'checkbox',
|
|
332
|
-
message,
|
|
333
|
-
choices: normalizedChoices
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Prompt for password input
|
|
339
|
-
* @param {string} message - Password prompt message
|
|
340
|
-
* @param {Function} [validate] - Validation function
|
|
341
|
-
* @returns {Promise<string>} User's password
|
|
342
|
-
*/
|
|
343
|
-
async password(message, validate) {
|
|
344
|
-
return this.prompt({
|
|
345
|
-
type: 'password',
|
|
346
|
-
message,
|
|
347
|
-
validate
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Display a progress bar
|
|
353
|
-
* @param {number} current - Current progress (0-100)
|
|
354
|
-
* @param {number} total - Total (usually 100)
|
|
355
|
-
* @param {string} [message] - Optional message
|
|
356
|
-
*/
|
|
357
|
-
progress(current, total = 100, message = '') {
|
|
358
|
-
const percentage = Math.round((current / total) * 100);
|
|
359
|
-
const barLength = 40;
|
|
360
|
-
const filledLength = Math.round((barLength * current) / total);
|
|
361
|
-
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
|
|
362
|
-
|
|
363
|
-
process.stdout.write(
|
|
364
|
-
`\r${chalk.cyan(bar)} ${chalk.bold(`${percentage}%`)} ${message}`
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
if (current >= total) {
|
|
368
|
-
console.log(''); // New line when complete
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Log raw message without formatting (for compatibility)
|
|
374
|
-
* @param {...any} args - Arguments to log
|
|
375
|
-
*/
|
|
376
|
-
log(...args) {
|
|
377
|
-
console.log(...args);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Export a singleton instance
|
|
382
|
-
module.exports = new Output();
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const {
|
|
5
|
-
createValidateCommand,
|
|
6
|
-
formatConsoleOutput,
|
|
7
|
-
autoDetectFriggApp,
|
|
8
|
-
findBackendPathInDir,
|
|
9
|
-
findBackendPath
|
|
10
|
-
} = require('../../adapters/cli/validate-command');
|
|
11
|
-
const { ValidationResult } = require('../../domain/entities/validation-result');
|
|
12
|
-
const { ValidationError } = require('../../domain/value-objects/validation-error');
|
|
13
|
-
const { FixSuggestion } = require('../../domain/value-objects/fix-suggestion');
|
|
14
|
-
const { Command } = require('commander');
|
|
15
|
-
|
|
16
|
-
describe('validateCommand', () => {
|
|
17
|
-
let mockOutput;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
mockOutput = {
|
|
21
|
-
info: jest.fn(),
|
|
22
|
-
success: jest.fn(),
|
|
23
|
-
error: jest.fn(),
|
|
24
|
-
warn: jest.fn(),
|
|
25
|
-
log: jest.fn()
|
|
26
|
-
};
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('createValidateCommand', () => {
|
|
30
|
-
it('registers validate command on program', () => {
|
|
31
|
-
const program = new Command();
|
|
32
|
-
createValidateCommand(program);
|
|
33
|
-
const validateCmd = program.commands.find(c => c.name() === 'validate');
|
|
34
|
-
expect(validateCmd).toBeDefined();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('has required options', () => {
|
|
38
|
-
const program = new Command();
|
|
39
|
-
createValidateCommand(program);
|
|
40
|
-
const validateCmd = program.commands.find(c => c.name() === 'validate');
|
|
41
|
-
const options = validateCmd.options.map(o => o.long);
|
|
42
|
-
expect(options).toContain('--format');
|
|
43
|
-
expect(options).toContain('--verbose');
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe('formatConsoleOutput', () => {
|
|
48
|
-
it('outputs success for valid result', () => {
|
|
49
|
-
const result = ValidationResult.create();
|
|
50
|
-
formatConsoleOutput(result, {}, mockOutput);
|
|
51
|
-
expect(mockOutput.success).toHaveBeenCalled();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('outputs errors for invalid result', () => {
|
|
55
|
-
const error = ValidationError.create({
|
|
56
|
-
path: 'integrations',
|
|
57
|
-
message: 'Missing integrations',
|
|
58
|
-
severity: 'error'
|
|
59
|
-
});
|
|
60
|
-
const result = ValidationResult.create({ errors: [error] });
|
|
61
|
-
formatConsoleOutput(result, {}, mockOutput);
|
|
62
|
-
const logCalls = mockOutput.log.mock.calls.flat().join(' ');
|
|
63
|
-
expect(logCalls).toContain('Missing integrations');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('outputs warnings', () => {
|
|
67
|
-
const warning = ValidationError.create({
|
|
68
|
-
path: 'database',
|
|
69
|
-
message: 'No database configured',
|
|
70
|
-
severity: 'warning'
|
|
71
|
-
});
|
|
72
|
-
const result = ValidationResult.create({ errors: [warning] });
|
|
73
|
-
formatConsoleOutput(result, {}, mockOutput);
|
|
74
|
-
expect(mockOutput.warn).toHaveBeenCalled();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('shows fix suggestions in verbose mode', () => {
|
|
78
|
-
const fix = FixSuggestion.create({ action: 'add', description: 'Add database configuration' });
|
|
79
|
-
const error = ValidationError.create({
|
|
80
|
-
path: 'database',
|
|
81
|
-
message: 'Missing config',
|
|
82
|
-
severity: 'error',
|
|
83
|
-
fix
|
|
84
|
-
});
|
|
85
|
-
const result = ValidationResult.create({ errors: [error] });
|
|
86
|
-
formatConsoleOutput(result, { verbose: true }, mockOutput);
|
|
87
|
-
const logCalls = mockOutput.log.mock.calls.flat().join(' ');
|
|
88
|
-
expect(logCalls).toContain('Add database configuration');
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('findBackendPathInDir', () => {
|
|
93
|
-
let tmpDir;
|
|
94
|
-
|
|
95
|
-
beforeEach(() => {
|
|
96
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frigg-test-'));
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
afterEach(() => {
|
|
100
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('finds backend/index.js', () => {
|
|
104
|
-
const backendDir = path.join(tmpDir, 'backend');
|
|
105
|
-
fs.mkdirSync(backendDir);
|
|
106
|
-
fs.writeFileSync(path.join(backendDir, 'index.js'), 'module.exports = {}');
|
|
107
|
-
expect(findBackendPathInDir(tmpDir)).toBe(backendDir);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('finds index.js with integrations keyword', () => {
|
|
111
|
-
fs.writeFileSync(path.join(tmpDir, 'index.js'), 'module.exports = { integrations: [] }');
|
|
112
|
-
expect(findBackendPathInDir(tmpDir)).toBe(tmpDir);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('finds index.js with Definition keyword', () => {
|
|
116
|
-
fs.writeFileSync(path.join(tmpDir, 'index.js'), 'module.exports = { Definition: {} }');
|
|
117
|
-
expect(findBackendPathInDir(tmpDir)).toBe(tmpDir);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('returns null for non-frigg directory', () => {
|
|
121
|
-
fs.writeFileSync(path.join(tmpDir, 'index.js'), 'console.log("hello")');
|
|
122
|
-
expect(findBackendPathInDir(tmpDir)).toBeNull();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('returns null for empty directory', () => {
|
|
126
|
-
expect(findBackendPathInDir(tmpDir)).toBeNull();
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe('findBackendPath', () => {
|
|
131
|
-
let tmpDir;
|
|
132
|
-
|
|
133
|
-
beforeEach(() => {
|
|
134
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frigg-test-'));
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
afterEach(() => {
|
|
138
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('finds backend subdirectory with package.json', () => {
|
|
142
|
-
const backendDir = path.join(tmpDir, 'backend');
|
|
143
|
-
fs.mkdirSync(backendDir);
|
|
144
|
-
fs.writeFileSync(path.join(backendDir, 'package.json'), '{}');
|
|
145
|
-
expect(findBackendPath(tmpDir)).toBe(backendDir);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('finds backend subdirectory with index.js', () => {
|
|
149
|
-
const backendDir = path.join(tmpDir, 'backend');
|
|
150
|
-
fs.mkdirSync(backendDir);
|
|
151
|
-
fs.writeFileSync(path.join(backendDir, 'index.js'), 'module.exports = {}');
|
|
152
|
-
expect(findBackendPath(tmpDir)).toBe(backendDir);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('returns path itself if it has package.json', () => {
|
|
156
|
-
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{}');
|
|
157
|
-
expect(findBackendPath(tmpDir)).toBe(tmpDir);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('returns null for non-backend directory', () => {
|
|
161
|
-
expect(findBackendPath(tmpDir)).toBeNull();
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe('autoDetectFriggApp', () => {
|
|
166
|
-
let tmpDir;
|
|
167
|
-
|
|
168
|
-
beforeEach(() => {
|
|
169
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frigg-test-'));
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
afterEach(() => {
|
|
173
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('detects frigg app in current directory', () => {
|
|
177
|
-
const backendDir = path.join(tmpDir, 'backend');
|
|
178
|
-
fs.mkdirSync(backendDir);
|
|
179
|
-
fs.writeFileSync(path.join(backendDir, 'index.js'), 'module.exports = {}');
|
|
180
|
-
const result = autoDetectFriggApp(tmpDir);
|
|
181
|
-
expect(result).toEqual({
|
|
182
|
-
appRoot: tmpDir,
|
|
183
|
-
backendPath: backendDir
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('detects frigg app in parent directory', () => {
|
|
188
|
-
const backendDir = path.join(tmpDir, 'backend');
|
|
189
|
-
const subDir = path.join(tmpDir, 'src', 'components');
|
|
190
|
-
fs.mkdirSync(backendDir);
|
|
191
|
-
fs.mkdirSync(subDir, { recursive: true });
|
|
192
|
-
fs.writeFileSync(path.join(backendDir, 'index.js'), 'module.exports = {}');
|
|
193
|
-
const result = autoDetectFriggApp(subDir);
|
|
194
|
-
expect(result).toEqual({
|
|
195
|
-
appRoot: tmpDir,
|
|
196
|
-
backendPath: backendDir
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('returns null when no frigg app found', () => {
|
|
201
|
-
const result = autoDetectFriggApp(tmpDir);
|
|
202
|
-
expect(result).toBeNull();
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
});
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
const { ValidateAppUseCase } = require('../../application/use-cases/validate-app-use-case');
|
|
2
|
-
|
|
3
|
-
describe('ValidateAppUseCase', () => {
|
|
4
|
-
let useCase;
|
|
5
|
-
let mockAppDefinitionValidator;
|
|
6
|
-
let mockIntegrationClassValidator;
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
mockAppDefinitionValidator = {
|
|
10
|
-
validate: jest.fn()
|
|
11
|
-
};
|
|
12
|
-
mockIntegrationClassValidator = {
|
|
13
|
-
validate: jest.fn()
|
|
14
|
-
};
|
|
15
|
-
useCase = new ValidateAppUseCase({
|
|
16
|
-
appDefinitionValidator: mockAppDefinitionValidator,
|
|
17
|
-
integrationClassValidator: mockIntegrationClassValidator
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe('execute', () => {
|
|
22
|
-
it('validates app definition structure', async () => {
|
|
23
|
-
const { ValidationResult } = require('../../domain/entities/validation-result');
|
|
24
|
-
mockAppDefinitionValidator.validate.mockReturnValue(ValidationResult.create());
|
|
25
|
-
mockIntegrationClassValidator.validate.mockReturnValue(ValidationResult.create());
|
|
26
|
-
|
|
27
|
-
const definition = { integrations: [] };
|
|
28
|
-
await useCase.execute({ definition });
|
|
29
|
-
|
|
30
|
-
expect(mockAppDefinitionValidator.validate).toHaveBeenCalledWith(definition);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('validates each integration class', async () => {
|
|
34
|
-
const { ValidationResult } = require('../../domain/entities/validation-result');
|
|
35
|
-
mockAppDefinitionValidator.validate.mockReturnValue(ValidationResult.create());
|
|
36
|
-
mockIntegrationClassValidator.validate.mockReturnValue(ValidationResult.create());
|
|
37
|
-
|
|
38
|
-
class Int1 { static Definition = { name: 'int1' }; }
|
|
39
|
-
class Int2 { static Definition = { name: 'int2' }; }
|
|
40
|
-
const definition = { integrations: [Int1, Int2] };
|
|
41
|
-
|
|
42
|
-
await useCase.execute({ definition });
|
|
43
|
-
|
|
44
|
-
expect(mockIntegrationClassValidator.validate).toHaveBeenCalledWith(Int1, 0);
|
|
45
|
-
expect(mockIntegrationClassValidator.validate).toHaveBeenCalledWith(Int2, 1);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('merges all validation results', async () => {
|
|
49
|
-
const { ValidationResult } = require('../../domain/entities/validation-result');
|
|
50
|
-
const { ValidationError } = require('../../domain/value-objects/validation-error');
|
|
51
|
-
|
|
52
|
-
const appError = ValidationError.create({ path: 'database', message: 'missing', severity: 'error' });
|
|
53
|
-
const intError = ValidationError.create({ path: 'integrations[0]', message: 'invalid', severity: 'error' });
|
|
54
|
-
|
|
55
|
-
mockAppDefinitionValidator.validate.mockReturnValue(
|
|
56
|
-
ValidationResult.create({ errors: [appError] })
|
|
57
|
-
);
|
|
58
|
-
mockIntegrationClassValidator.validate.mockReturnValue(
|
|
59
|
-
ValidationResult.create({ errors: [intError] })
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
class Int1 { static Definition = { name: 'int1' }; }
|
|
63
|
-
const definition = { integrations: [Int1] };
|
|
64
|
-
|
|
65
|
-
const result = await useCase.execute({ definition });
|
|
66
|
-
|
|
67
|
-
expect(result.getErrors()).toHaveLength(2);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('returns valid result when no errors', async () => {
|
|
71
|
-
const { ValidationResult } = require('../../domain/entities/validation-result');
|
|
72
|
-
mockAppDefinitionValidator.validate.mockReturnValue(ValidationResult.create());
|
|
73
|
-
mockIntegrationClassValidator.validate.mockReturnValue(ValidationResult.create());
|
|
74
|
-
|
|
75
|
-
const definition = { integrations: [] };
|
|
76
|
-
const result = await useCase.execute({ definition });
|
|
77
|
-
|
|
78
|
-
expect(result.isValid()).toBe(true);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('adds context with definition metadata', async () => {
|
|
82
|
-
const { ValidationResult } = require('../../domain/entities/validation-result');
|
|
83
|
-
mockAppDefinitionValidator.validate.mockReturnValue(ValidationResult.create());
|
|
84
|
-
|
|
85
|
-
const definition = { integrations: [] };
|
|
86
|
-
const result = await useCase.execute({ definition, appPath: '/app/backend' });
|
|
87
|
-
|
|
88
|
-
expect(result.getContext()).toMatchObject({ appPath: '/app/backend' });
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('error handling', () => {
|
|
93
|
-
it('handles validator throwing error', async () => {
|
|
94
|
-
mockAppDefinitionValidator.validate.mockImplementation(() => {
|
|
95
|
-
throw new Error('Validator crashed');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const definition = { integrations: [] };
|
|
99
|
-
|
|
100
|
-
await expect(useCase.execute({ definition }))
|
|
101
|
-
.rejects.toThrow('Validator crashed');
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
});
|