@friggframework/devtools 2.0.0--canary.546.74db90f.0 → 2.0.0--canary.545.e7becd9.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__/application/use-cases/AddApiModuleToIntegrationUseCase.test.js +326 -0
- package/frigg-cli/__tests__/application/use-cases/CreateApiModuleUseCase.test.js +337 -0
- package/frigg-cli/__tests__/domain/entities/ApiModule.test.js +373 -0
- package/frigg-cli/__tests__/domain/entities/AppDefinition.test.js +313 -0
- package/frigg-cli/__tests__/domain/services/IntegrationValidator.test.js +269 -0
- package/frigg-cli/__tests__/domain/value-objects/IntegrationName.test.js +82 -0
- package/frigg-cli/__tests__/infrastructure/adapters/IntegrationJsUpdater.test.js +408 -0
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemApiModuleRepository.test.js +583 -0
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemAppDefinitionRepository.test.js +314 -0
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemIntegrationRepository.test.js +383 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +1 -1
- package/frigg-cli/__tests__/unit/commands/doctor.test.js +0 -2
- package/frigg-cli/__tests__/unit/commands/init.test.js +406 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +23 -19
- package/frigg-cli/__tests__/unit/commands/provider-dispatch.test.js +383 -0
- package/frigg-cli/__tests__/unit/commands/repair.test.js +275 -0
- package/frigg-cli/__tests__/unit/dependencies.test.js +2 -2
- package/frigg-cli/__tests__/unit/start-command/application/RunPreflightChecksUseCase.test.js +411 -0
- package/frigg-cli/__tests__/unit/start-command/infrastructure/DatabaseAdapter.test.js +405 -0
- package/frigg-cli/__tests__/unit/start-command/infrastructure/DockerAdapter.test.js +496 -0
- package/frigg-cli/__tests__/unit/start-command/presentation/InteractivePromptAdapter.test.js +474 -0
- package/frigg-cli/__tests__/unit/utils/output.test.js +196 -0
- package/frigg-cli/application/use-cases/AddApiModuleToIntegrationUseCase.js +93 -0
- package/frigg-cli/application/use-cases/CreateApiModuleUseCase.js +93 -0
- package/frigg-cli/application/use-cases/CreateIntegrationUseCase.js +103 -0
- package/frigg-cli/build-command/index.js +123 -11
- package/frigg-cli/container.js +172 -0
- package/frigg-cli/deploy-command/index.js +83 -1
- package/frigg-cli/docs/OUTPUT_MIGRATION_GUIDE.md +286 -0
- package/frigg-cli/doctor-command/index.js +37 -16
- package/frigg-cli/domain/entities/ApiModule.js +272 -0
- package/frigg-cli/domain/entities/AppDefinition.js +227 -0
- package/frigg-cli/domain/entities/Integration.js +198 -0
- package/frigg-cli/domain/exceptions/DomainException.js +24 -0
- package/frigg-cli/domain/ports/IApiModuleRepository.js +53 -0
- package/frigg-cli/domain/ports/IAppDefinitionRepository.js +43 -0
- package/frigg-cli/domain/ports/IIntegrationRepository.js +61 -0
- package/frigg-cli/domain/services/IntegrationValidator.js +185 -0
- package/frigg-cli/domain/value-objects/IntegrationId.js +42 -0
- package/frigg-cli/domain/value-objects/IntegrationName.js +60 -0
- package/frigg-cli/domain/value-objects/SemanticVersion.js +70 -0
- package/frigg-cli/generate-iam-command.js +21 -1
- package/frigg-cli/index.js +21 -6
- package/frigg-cli/index.test.js +7 -2
- package/frigg-cli/infrastructure/UnitOfWork.js +46 -0
- package/frigg-cli/infrastructure/adapters/BackendJsUpdater.js +197 -0
- package/frigg-cli/infrastructure/adapters/FileSystemAdapter.js +224 -0
- package/frigg-cli/infrastructure/adapters/IntegrationJsUpdater.js +249 -0
- package/frigg-cli/infrastructure/adapters/SchemaValidator.js +92 -0
- package/frigg-cli/infrastructure/repositories/FileSystemApiModuleRepository.js +373 -0
- package/frigg-cli/infrastructure/repositories/FileSystemAppDefinitionRepository.js +116 -0
- package/frigg-cli/infrastructure/repositories/FileSystemIntegrationRepository.js +277 -0
- package/frigg-cli/init-command/backend-first-handler.js +124 -42
- package/frigg-cli/init-command/index.js +2 -1
- package/frigg-cli/init-command/template-handler.js +13 -3
- package/frigg-cli/install-command/backend-js.js +3 -3
- package/frigg-cli/install-command/environment-variables.js +16 -19
- package/frigg-cli/install-command/environment-variables.test.js +12 -13
- package/frigg-cli/install-command/index.js +14 -9
- package/frigg-cli/install-command/integration-file.js +3 -3
- package/frigg-cli/install-command/validate-package.js +5 -9
- package/frigg-cli/jest.config.js +4 -1
- package/frigg-cli/package-lock.json +16226 -0
- package/frigg-cli/repair-command/index.js +121 -128
- package/frigg-cli/start-command/application/RunPreflightChecksUseCase.js +376 -0
- package/frigg-cli/start-command/index.js +324 -2
- package/frigg-cli/start-command/infrastructure/DatabaseAdapter.js +591 -0
- package/frigg-cli/start-command/infrastructure/DockerAdapter.js +306 -0
- package/frigg-cli/start-command/presentation/InteractivePromptAdapter.js +329 -0
- package/frigg-cli/templates/backend/.env.example +62 -0
- package/frigg-cli/templates/backend/.eslintrc.json +12 -0
- package/frigg-cli/templates/backend/.prettierrc +6 -0
- package/frigg-cli/templates/backend/docker-compose.yml +22 -0
- package/frigg-cli/templates/backend/index.js +96 -0
- package/frigg-cli/templates/backend/infrastructure.js +12 -0
- package/frigg-cli/templates/backend/jest.config.js +17 -0
- package/frigg-cli/templates/backend/package.json +50 -0
- package/frigg-cli/templates/backend/src/api-modules/.gitkeep +10 -0
- package/frigg-cli/templates/backend/src/base/.gitkeep +7 -0
- package/frigg-cli/templates/backend/src/integrations/.gitkeep +10 -0
- package/frigg-cli/templates/backend/src/integrations/ExampleIntegration.js +65 -0
- package/frigg-cli/templates/backend/src/utils/.gitkeep +7 -0
- package/frigg-cli/templates/backend/test/setup.js +30 -0
- package/frigg-cli/templates/backend/ui-extensions/.gitkeep +0 -0
- package/frigg-cli/templates/backend/ui-extensions/README.md +77 -0
- package/frigg-cli/ui-command/index.js +58 -36
- package/frigg-cli/utils/__tests__/provider-helper.test.js +55 -0
- package/frigg-cli/utils/__tests__/repo-detection.test.js +436 -0
- package/frigg-cli/utils/output.js +382 -0
- package/frigg-cli/utils/provider-helper.js +75 -0
- package/frigg-cli/utils/repo-detection.js +85 -37
- package/frigg-cli/validate-command/__tests__/adapters/validate-command.test.js +205 -0
- package/frigg-cli/validate-command/__tests__/application/validate-app-use-case.test.js +104 -0
- package/frigg-cli/validate-command/__tests__/domain/fix-suggestion.test.js +153 -0
- package/frigg-cli/validate-command/__tests__/domain/validation-error.test.js +162 -0
- package/frigg-cli/validate-command/__tests__/domain/validation-result.test.js +152 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/api-module-validator.test.js +332 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/app-definition-validator.test.js +191 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/integration-class-validator.test.js +146 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/template-validation.test.js +155 -0
- package/frigg-cli/validate-command/adapters/cli/validate-command.js +199 -0
- package/frigg-cli/validate-command/application/use-cases/validate-app-use-case.js +35 -0
- package/frigg-cli/validate-command/domain/entities/validation-result.js +74 -0
- package/frigg-cli/validate-command/domain/value-objects/fix-suggestion.js +74 -0
- package/frigg-cli/validate-command/domain/value-objects/validation-error.js +68 -0
- package/frigg-cli/validate-command/infrastructure/validators/api-module-validator.js +181 -0
- package/frigg-cli/validate-command/infrastructure/validators/app-definition-validator.js +128 -0
- package/frigg-cli/validate-command/infrastructure/validators/integration-class-validator.js +113 -0
- package/infrastructure/create-frigg-infrastructure.js +93 -0
- package/infrastructure/docs/iam-policy-templates.md +1 -1
- package/infrastructure/domains/admin-scripts/admin-script-builder.js +200 -0
- package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +499 -0
- package/infrastructure/domains/admin-scripts/index.js +5 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +2 -4
- package/infrastructure/domains/networking/vpc-resolver.test.js +1 -1
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +4 -7
- package/infrastructure/domains/shared/resource-discovery.js +5 -5
- package/infrastructure/domains/shared/types/app-definition.js +21 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +1 -1
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +10 -1
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +2 -2
- package/infrastructure/infrastructure-composer.js +2 -0
- package/infrastructure/infrastructure-composer.test.js +2 -2
- package/infrastructure/jest.config.js +16 -0
- package/management-ui/README.md +245 -109
- package/package.json +8 -7
- package/frigg-cli/install-command/logger.js +0 -12
|
@@ -0,0 +1,382 @@
|
|
|
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();
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Provider Helper
|
|
3
|
+
*
|
|
4
|
+
* Loads the appDefinition from the user's project and resolves the
|
|
5
|
+
* corresponding provider plugin package. Used by CLI commands to
|
|
6
|
+
* delegate to the correct provider (AWS, Netlify, etc.).
|
|
7
|
+
*/
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load the appDefinition and resolve the provider plugin for CLI commands.
|
|
13
|
+
*
|
|
14
|
+
* This is a lightweight loader for the CLI context — it reads the backend
|
|
15
|
+
* index.js directly (no need for the full core app-definition-loader which
|
|
16
|
+
* searches for the nearest backend package.json at runtime).
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} [options]
|
|
19
|
+
* @param {string} [options.cwd] - Working directory (default: process.cwd())
|
|
20
|
+
* @returns {{ appDefinition: Object, provider: Object, providerName: string } | null}
|
|
21
|
+
* Returns null if no appDefinition is found (caller should fall back to default behavior).
|
|
22
|
+
*/
|
|
23
|
+
function loadProviderForCli(options = {}) {
|
|
24
|
+
const cwd = options.cwd || process.cwd();
|
|
25
|
+
const appDefinition = loadCliAppDefinition(cwd);
|
|
26
|
+
|
|
27
|
+
if (!appDefinition) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const providerName = appDefinition.provider || 'aws';
|
|
32
|
+
|
|
33
|
+
// For 'aws', return null provider — CLI commands fall back to existing behavior
|
|
34
|
+
if (providerName === 'aws') {
|
|
35
|
+
return { appDefinition, provider: null, providerName: 'aws' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// For other providers, resolve the package
|
|
39
|
+
const {
|
|
40
|
+
resolveProvider,
|
|
41
|
+
} = require('@friggframework/core/providers/resolve-provider');
|
|
42
|
+
|
|
43
|
+
const provider = resolveProvider(appDefinition);
|
|
44
|
+
return { appDefinition, provider, providerName };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Load the appDefinition from the backend directory.
|
|
49
|
+
* Tries backend/index.js then index.js in the current directory.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} cwd
|
|
52
|
+
* @returns {Object|null}
|
|
53
|
+
*/
|
|
54
|
+
function loadCliAppDefinition(cwd) {
|
|
55
|
+
// Try backend/index.js first (standard Frigg app structure)
|
|
56
|
+
const backendIndexPath = path.join(cwd, 'backend', 'index.js');
|
|
57
|
+
const rootIndexPath = path.join(cwd, 'index.js');
|
|
58
|
+
|
|
59
|
+
for (const indexPath of [backendIndexPath, rootIndexPath]) {
|
|
60
|
+
if (fs.existsSync(indexPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const exported = require(indexPath);
|
|
63
|
+
if (exported.Definition) {
|
|
64
|
+
return exported.Definition;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Failed to load, try next
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { loadProviderForCli, loadCliAppDefinition };
|
|
@@ -37,17 +37,70 @@ async function isFriggRepository(directory) {
|
|
|
37
37
|
friggDependencies: []
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
// Check for @friggframework
|
|
40
|
+
// Check for @friggframework/core v2.0+ specifically (REQUIRED for Frigg apps)
|
|
41
|
+
// Check both root package.json and backend/package.json for workspace projects
|
|
41
42
|
const allDeps = {
|
|
42
43
|
...packageJson.dependencies,
|
|
43
|
-
...packageJson.devDependencies
|
|
44
|
-
...packageJson.peerDependencies
|
|
44
|
+
...packageJson.devDependencies
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
// Check root package.json for core dependency
|
|
48
|
+
let coreVersion = allDeps['@friggframework/core'];
|
|
49
|
+
let hasFriggCore = coreVersion && (
|
|
50
|
+
coreVersion === 'next' ||
|
|
51
|
+
coreVersion.includes('2.0.0') ||
|
|
52
|
+
coreVersion.includes('next') ||
|
|
53
|
+
coreVersion.startsWith('^2.') ||
|
|
54
|
+
coreVersion.startsWith('~2.') ||
|
|
55
|
+
coreVersion.startsWith('2.')
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// If not in root, check backend/package.json for workspace projects
|
|
59
|
+
if (!hasFriggCore) {
|
|
60
|
+
const backendPackagePath = path.join(directory, 'backend', 'package.json');
|
|
61
|
+
if (fs.existsSync(backendPackagePath)) {
|
|
62
|
+
try {
|
|
63
|
+
const backendPackageJson = await fs.readJson(backendPackagePath);
|
|
64
|
+
const backendDeps = {
|
|
65
|
+
...backendPackageJson.dependencies,
|
|
66
|
+
...backendPackageJson.devDependencies
|
|
67
|
+
};
|
|
68
|
+
coreVersion = backendDeps['@friggframework/core'];
|
|
69
|
+
hasFriggCore = coreVersion && (
|
|
70
|
+
coreVersion === 'next' ||
|
|
71
|
+
coreVersion.includes('2.0.0') ||
|
|
72
|
+
coreVersion.includes('next') ||
|
|
73
|
+
coreVersion.startsWith('^2.') ||
|
|
74
|
+
coreVersion.startsWith('~2.') ||
|
|
75
|
+
coreVersion.startsWith('2.')
|
|
76
|
+
);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
// Ignore errors reading backend package.json
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (hasFriggCore) {
|
|
84
|
+
indicators.hasFriggDependencies = true;
|
|
85
|
+
indicators.friggDependencies.push('@friggframework/core');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Also track other Frigg dependencies for reference
|
|
47
89
|
for (const dep in allDeps) {
|
|
48
|
-
if (dep.startsWith('@friggframework/')) {
|
|
49
|
-
|
|
50
|
-
|
|
90
|
+
if (dep.startsWith('@friggframework/') && dep !== '@friggframework/core') {
|
|
91
|
+
const version = allDeps[dep];
|
|
92
|
+
const isV2Plus = version && (
|
|
93
|
+
version === 'next' ||
|
|
94
|
+
version.includes('2.0.0') ||
|
|
95
|
+
version.includes('next') ||
|
|
96
|
+
version.startsWith('^2.') ||
|
|
97
|
+
version.startsWith('~2.') ||
|
|
98
|
+
version.startsWith('2.')
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (isV2Plus) {
|
|
102
|
+
indicators.friggDependencies.push(dep);
|
|
103
|
+
}
|
|
51
104
|
}
|
|
52
105
|
}
|
|
53
106
|
|
|
@@ -136,42 +189,33 @@ async function isFriggRepository(directory) {
|
|
|
136
189
|
}
|
|
137
190
|
}
|
|
138
191
|
|
|
139
|
-
// A directory is considered a Frigg
|
|
140
|
-
// 1.
|
|
141
|
-
// 2. Frigg
|
|
142
|
-
// 3.
|
|
143
|
-
// 4. Frigg-specific scripts in package.json OR
|
|
144
|
-
// 5. Serverless config with explicit Frigg references AND proper structure
|
|
145
|
-
//
|
|
146
|
-
// For Zapier apps, we require explicit Frigg indicators
|
|
147
|
-
const hasFriggIndicators = indicators.hasFriggDependencies ||
|
|
148
|
-
indicators.hasFriggConfig ||
|
|
149
|
-
indicators.hasFriggDirectories ||
|
|
150
|
-
indicators.hasFriggScripts ||
|
|
151
|
-
hasFriggServerlessIndicators;
|
|
152
|
-
|
|
153
|
-
// Determine if it's a Frigg repository
|
|
154
|
-
let isFriggRepo = false;
|
|
155
|
-
|
|
156
|
-
if (isZapierApp) {
|
|
157
|
-
// For Zapier apps, require explicit Frigg dependencies or config
|
|
158
|
-
isFriggRepo = indicators.hasFriggDependencies || indicators.hasFriggConfig;
|
|
159
|
-
} else {
|
|
160
|
-
// For non-Zapier apps, any Frigg indicator is sufficient
|
|
161
|
-
isFriggRepo = hasFriggIndicators;
|
|
162
|
-
}
|
|
192
|
+
// STRICT VALIDATION: A directory is considered a Frigg app repository ONLY if:
|
|
193
|
+
// 1. Has @friggframework/core v2.0+ as a direct dependency (MANDATORY)
|
|
194
|
+
// 2. Has actual Frigg app structure (index.js with Definition OR backend/serverless.yml)
|
|
195
|
+
// 3. Is NOT a framework package itself (no @friggframework/* package name)
|
|
163
196
|
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
197
|
+
// Check for Frigg app structure indicators
|
|
198
|
+
const hasRootIndexJs = fs.existsSync(path.join(directory, 'index.js'));
|
|
199
|
+
const hasBackendIndexJs = fs.existsSync(path.join(directory, 'backend', 'index.js'));
|
|
200
|
+
const hasBackendServerless = fs.existsSync(path.join(directory, 'backend', 'serverless.yml'));
|
|
201
|
+
const hasAppStructure = hasRootIndexJs || hasBackendIndexJs || hasBackendServerless;
|
|
202
|
+
|
|
203
|
+
// Determine if it's a Frigg repository with STRICT criteria
|
|
204
|
+
const isFriggRepo = indicators.hasFriggDependencies && hasAppStructure;
|
|
168
205
|
|
|
169
206
|
if (isFriggRepo) {
|
|
207
|
+
// IMPORTANT: If backend/ has a Frigg app structure, use backend/ as the path
|
|
208
|
+
// This handles workspace projects where the actual app is in backend/
|
|
209
|
+
let actualPath = directory;
|
|
210
|
+
if (hasBackendIndexJs || hasBackendServerless) {
|
|
211
|
+
actualPath = path.join(directory, 'backend');
|
|
212
|
+
}
|
|
213
|
+
|
|
170
214
|
return {
|
|
171
215
|
isFriggRepo: true,
|
|
172
216
|
repoInfo: {
|
|
173
217
|
name: packageJson.name || path.basename(directory),
|
|
174
|
-
path:
|
|
218
|
+
path: actualPath,
|
|
175
219
|
version: packageJson.version,
|
|
176
220
|
framework: detectFramework(directory, existingFrontendDirs),
|
|
177
221
|
hasBackend: fs.existsSync(path.join(directory, 'backend')),
|
|
@@ -245,12 +289,16 @@ async function discoverFriggRepositories(options = {}) {
|
|
|
245
289
|
searchPaths = [
|
|
246
290
|
process.cwd(),
|
|
247
291
|
path.join(os.homedir(), 'Documents'),
|
|
292
|
+
path.join(os.homedir(), 'Documents', 'GitHub'), // Common GitHub Desktop location
|
|
248
293
|
path.join(os.homedir(), 'Projects'),
|
|
249
294
|
path.join(os.homedir(), 'Development'),
|
|
250
295
|
path.join(os.homedir(), 'dev'),
|
|
251
|
-
path.join(os.homedir(), 'Code')
|
|
296
|
+
path.join(os.homedir(), 'Code'),
|
|
297
|
+
path.join(os.homedir(), 'GitHub'), // Alternative GitHub location
|
|
298
|
+
path.join(os.homedir(), 'repos'), // Common repos folder
|
|
299
|
+
path.join(os.homedir(), 'src') // Common source folder
|
|
252
300
|
],
|
|
253
|
-
maxDepth =
|
|
301
|
+
maxDepth = 4, // Increased to handle deeper nested projects (e.g., org/apps/project)
|
|
254
302
|
excludePatterns = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage']
|
|
255
303
|
} = options;
|
|
256
304
|
|