@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.
Files changed (128) hide show
  1. package/frigg-cli/README.md +1 -1
  2. package/frigg-cli/__tests__/application/use-cases/AddApiModuleToIntegrationUseCase.test.js +326 -0
  3. package/frigg-cli/__tests__/application/use-cases/CreateApiModuleUseCase.test.js +337 -0
  4. package/frigg-cli/__tests__/domain/entities/ApiModule.test.js +373 -0
  5. package/frigg-cli/__tests__/domain/entities/AppDefinition.test.js +313 -0
  6. package/frigg-cli/__tests__/domain/services/IntegrationValidator.test.js +269 -0
  7. package/frigg-cli/__tests__/domain/value-objects/IntegrationName.test.js +82 -0
  8. package/frigg-cli/__tests__/infrastructure/adapters/IntegrationJsUpdater.test.js +408 -0
  9. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemApiModuleRepository.test.js +583 -0
  10. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemAppDefinitionRepository.test.js +314 -0
  11. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemIntegrationRepository.test.js +383 -0
  12. package/frigg-cli/__tests__/unit/commands/build.test.js +1 -1
  13. package/frigg-cli/__tests__/unit/commands/doctor.test.js +0 -2
  14. package/frigg-cli/__tests__/unit/commands/init.test.js +406 -0
  15. package/frigg-cli/__tests__/unit/commands/install.test.js +23 -19
  16. package/frigg-cli/__tests__/unit/commands/provider-dispatch.test.js +383 -0
  17. package/frigg-cli/__tests__/unit/commands/repair.test.js +275 -0
  18. package/frigg-cli/__tests__/unit/dependencies.test.js +2 -2
  19. package/frigg-cli/__tests__/unit/start-command/application/RunPreflightChecksUseCase.test.js +411 -0
  20. package/frigg-cli/__tests__/unit/start-command/infrastructure/DatabaseAdapter.test.js +405 -0
  21. package/frigg-cli/__tests__/unit/start-command/infrastructure/DockerAdapter.test.js +496 -0
  22. package/frigg-cli/__tests__/unit/start-command/presentation/InteractivePromptAdapter.test.js +474 -0
  23. package/frigg-cli/__tests__/unit/utils/output.test.js +196 -0
  24. package/frigg-cli/application/use-cases/AddApiModuleToIntegrationUseCase.js +93 -0
  25. package/frigg-cli/application/use-cases/CreateApiModuleUseCase.js +93 -0
  26. package/frigg-cli/application/use-cases/CreateIntegrationUseCase.js +103 -0
  27. package/frigg-cli/build-command/index.js +123 -11
  28. package/frigg-cli/container.js +172 -0
  29. package/frigg-cli/deploy-command/index.js +83 -1
  30. package/frigg-cli/docs/OUTPUT_MIGRATION_GUIDE.md +286 -0
  31. package/frigg-cli/doctor-command/index.js +37 -16
  32. package/frigg-cli/domain/entities/ApiModule.js +272 -0
  33. package/frigg-cli/domain/entities/AppDefinition.js +227 -0
  34. package/frigg-cli/domain/entities/Integration.js +198 -0
  35. package/frigg-cli/domain/exceptions/DomainException.js +24 -0
  36. package/frigg-cli/domain/ports/IApiModuleRepository.js +53 -0
  37. package/frigg-cli/domain/ports/IAppDefinitionRepository.js +43 -0
  38. package/frigg-cli/domain/ports/IIntegrationRepository.js +61 -0
  39. package/frigg-cli/domain/services/IntegrationValidator.js +185 -0
  40. package/frigg-cli/domain/value-objects/IntegrationId.js +42 -0
  41. package/frigg-cli/domain/value-objects/IntegrationName.js +60 -0
  42. package/frigg-cli/domain/value-objects/SemanticVersion.js +70 -0
  43. package/frigg-cli/generate-iam-command.js +21 -1
  44. package/frigg-cli/index.js +21 -6
  45. package/frigg-cli/index.test.js +7 -2
  46. package/frigg-cli/infrastructure/UnitOfWork.js +46 -0
  47. package/frigg-cli/infrastructure/adapters/BackendJsUpdater.js +197 -0
  48. package/frigg-cli/infrastructure/adapters/FileSystemAdapter.js +224 -0
  49. package/frigg-cli/infrastructure/adapters/IntegrationJsUpdater.js +249 -0
  50. package/frigg-cli/infrastructure/adapters/SchemaValidator.js +92 -0
  51. package/frigg-cli/infrastructure/repositories/FileSystemApiModuleRepository.js +373 -0
  52. package/frigg-cli/infrastructure/repositories/FileSystemAppDefinitionRepository.js +116 -0
  53. package/frigg-cli/infrastructure/repositories/FileSystemIntegrationRepository.js +277 -0
  54. package/frigg-cli/init-command/backend-first-handler.js +124 -42
  55. package/frigg-cli/init-command/index.js +2 -1
  56. package/frigg-cli/init-command/template-handler.js +13 -3
  57. package/frigg-cli/install-command/backend-js.js +3 -3
  58. package/frigg-cli/install-command/environment-variables.js +16 -19
  59. package/frigg-cli/install-command/environment-variables.test.js +12 -13
  60. package/frigg-cli/install-command/index.js +14 -9
  61. package/frigg-cli/install-command/integration-file.js +3 -3
  62. package/frigg-cli/install-command/validate-package.js +5 -9
  63. package/frigg-cli/jest.config.js +4 -1
  64. package/frigg-cli/package-lock.json +16226 -0
  65. package/frigg-cli/repair-command/index.js +121 -128
  66. package/frigg-cli/start-command/application/RunPreflightChecksUseCase.js +376 -0
  67. package/frigg-cli/start-command/index.js +324 -2
  68. package/frigg-cli/start-command/infrastructure/DatabaseAdapter.js +591 -0
  69. package/frigg-cli/start-command/infrastructure/DockerAdapter.js +306 -0
  70. package/frigg-cli/start-command/presentation/InteractivePromptAdapter.js +329 -0
  71. package/frigg-cli/templates/backend/.env.example +62 -0
  72. package/frigg-cli/templates/backend/.eslintrc.json +12 -0
  73. package/frigg-cli/templates/backend/.prettierrc +6 -0
  74. package/frigg-cli/templates/backend/docker-compose.yml +22 -0
  75. package/frigg-cli/templates/backend/index.js +96 -0
  76. package/frigg-cli/templates/backend/infrastructure.js +12 -0
  77. package/frigg-cli/templates/backend/jest.config.js +17 -0
  78. package/frigg-cli/templates/backend/package.json +50 -0
  79. package/frigg-cli/templates/backend/src/api-modules/.gitkeep +10 -0
  80. package/frigg-cli/templates/backend/src/base/.gitkeep +7 -0
  81. package/frigg-cli/templates/backend/src/integrations/.gitkeep +10 -0
  82. package/frigg-cli/templates/backend/src/integrations/ExampleIntegration.js +65 -0
  83. package/frigg-cli/templates/backend/src/utils/.gitkeep +7 -0
  84. package/frigg-cli/templates/backend/test/setup.js +30 -0
  85. package/frigg-cli/templates/backend/ui-extensions/.gitkeep +0 -0
  86. package/frigg-cli/templates/backend/ui-extensions/README.md +77 -0
  87. package/frigg-cli/ui-command/index.js +58 -36
  88. package/frigg-cli/utils/__tests__/provider-helper.test.js +55 -0
  89. package/frigg-cli/utils/__tests__/repo-detection.test.js +436 -0
  90. package/frigg-cli/utils/output.js +382 -0
  91. package/frigg-cli/utils/provider-helper.js +75 -0
  92. package/frigg-cli/utils/repo-detection.js +85 -37
  93. package/frigg-cli/validate-command/__tests__/adapters/validate-command.test.js +205 -0
  94. package/frigg-cli/validate-command/__tests__/application/validate-app-use-case.test.js +104 -0
  95. package/frigg-cli/validate-command/__tests__/domain/fix-suggestion.test.js +153 -0
  96. package/frigg-cli/validate-command/__tests__/domain/validation-error.test.js +162 -0
  97. package/frigg-cli/validate-command/__tests__/domain/validation-result.test.js +152 -0
  98. package/frigg-cli/validate-command/__tests__/infrastructure/api-module-validator.test.js +332 -0
  99. package/frigg-cli/validate-command/__tests__/infrastructure/app-definition-validator.test.js +191 -0
  100. package/frigg-cli/validate-command/__tests__/infrastructure/integration-class-validator.test.js +146 -0
  101. package/frigg-cli/validate-command/__tests__/infrastructure/template-validation.test.js +155 -0
  102. package/frigg-cli/validate-command/adapters/cli/validate-command.js +199 -0
  103. package/frigg-cli/validate-command/application/use-cases/validate-app-use-case.js +35 -0
  104. package/frigg-cli/validate-command/domain/entities/validation-result.js +74 -0
  105. package/frigg-cli/validate-command/domain/value-objects/fix-suggestion.js +74 -0
  106. package/frigg-cli/validate-command/domain/value-objects/validation-error.js +68 -0
  107. package/frigg-cli/validate-command/infrastructure/validators/api-module-validator.js +181 -0
  108. package/frigg-cli/validate-command/infrastructure/validators/app-definition-validator.js +128 -0
  109. package/frigg-cli/validate-command/infrastructure/validators/integration-class-validator.js +113 -0
  110. package/infrastructure/create-frigg-infrastructure.js +93 -0
  111. package/infrastructure/docs/iam-policy-templates.md +1 -1
  112. package/infrastructure/domains/admin-scripts/admin-script-builder.js +200 -0
  113. package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +499 -0
  114. package/infrastructure/domains/admin-scripts/index.js +5 -0
  115. package/infrastructure/domains/networking/vpc-builder.test.js +2 -4
  116. package/infrastructure/domains/networking/vpc-resolver.test.js +1 -1
  117. package/infrastructure/domains/shared/cloudformation-discovery.test.js +4 -7
  118. package/infrastructure/domains/shared/resource-discovery.js +5 -5
  119. package/infrastructure/domains/shared/types/app-definition.js +21 -0
  120. package/infrastructure/domains/shared/types/discovery-result.test.js +1 -1
  121. package/infrastructure/domains/shared/utilities/base-definition-factory.js +10 -1
  122. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +2 -2
  123. package/infrastructure/infrastructure-composer.js +2 -0
  124. package/infrastructure/infrastructure-composer.test.js +2 -2
  125. package/infrastructure/jest.config.js +16 -0
  126. package/management-ui/README.md +245 -109
  127. package/package.json +8 -7
  128. 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 dependencies
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
- indicators.hasFriggDependencies = true;
50
- indicators.friggDependencies.push(dep);
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 repo if it has:
140
- // 1. Frigg dependencies (MANDATORY - most reliable indicator) OR
141
- // 2. Frigg-specific configuration files OR
142
- // 3. Frigg-specific directories OR
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
- // Additional validation for edge cases
165
- if (isZapierApp && !indicators.hasFriggDependencies && !indicators.hasFriggConfig) {
166
- return { isFriggRepo: false, repoInfo: null };
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: directory,
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 = 3,
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