@friggframework/devtools 2.0.0--canary.517.179491e.0 → 2.0.0--canary.522.893db5d.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 (115) 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/doctor.test.js +0 -2
  13. package/frigg-cli/__tests__/unit/commands/init.test.js +406 -0
  14. package/frigg-cli/__tests__/unit/commands/install.test.js +23 -19
  15. package/frigg-cli/__tests__/unit/commands/repair.test.js +275 -0
  16. package/frigg-cli/__tests__/unit/dependencies.test.js +2 -2
  17. package/frigg-cli/__tests__/unit/start-command/application/RunPreflightChecksUseCase.test.js +411 -0
  18. package/frigg-cli/__tests__/unit/start-command/infrastructure/DatabaseAdapter.test.js +405 -0
  19. package/frigg-cli/__tests__/unit/start-command/infrastructure/DockerAdapter.test.js +496 -0
  20. package/frigg-cli/__tests__/unit/start-command/presentation/InteractivePromptAdapter.test.js +474 -0
  21. package/frigg-cli/__tests__/unit/utils/output.test.js +196 -0
  22. package/frigg-cli/application/use-cases/AddApiModuleToIntegrationUseCase.js +93 -0
  23. package/frigg-cli/application/use-cases/CreateApiModuleUseCase.js +93 -0
  24. package/frigg-cli/application/use-cases/CreateIntegrationUseCase.js +103 -0
  25. package/frigg-cli/container.js +172 -0
  26. package/frigg-cli/docs/OUTPUT_MIGRATION_GUIDE.md +286 -0
  27. package/frigg-cli/doctor-command/index.js +17 -16
  28. package/frigg-cli/domain/entities/ApiModule.js +272 -0
  29. package/frigg-cli/domain/entities/AppDefinition.js +227 -0
  30. package/frigg-cli/domain/entities/Integration.js +198 -0
  31. package/frigg-cli/domain/exceptions/DomainException.js +24 -0
  32. package/frigg-cli/domain/ports/IApiModuleRepository.js +53 -0
  33. package/frigg-cli/domain/ports/IAppDefinitionRepository.js +43 -0
  34. package/frigg-cli/domain/ports/IIntegrationRepository.js +61 -0
  35. package/frigg-cli/domain/services/IntegrationValidator.js +185 -0
  36. package/frigg-cli/domain/value-objects/IntegrationId.js +42 -0
  37. package/frigg-cli/domain/value-objects/IntegrationName.js +60 -0
  38. package/frigg-cli/domain/value-objects/SemanticVersion.js +70 -0
  39. package/frigg-cli/index.js +21 -6
  40. package/frigg-cli/index.test.js +7 -2
  41. package/frigg-cli/infrastructure/UnitOfWork.js +46 -0
  42. package/frigg-cli/infrastructure/adapters/BackendJsUpdater.js +197 -0
  43. package/frigg-cli/infrastructure/adapters/FileSystemAdapter.js +224 -0
  44. package/frigg-cli/infrastructure/adapters/IntegrationJsUpdater.js +249 -0
  45. package/frigg-cli/infrastructure/adapters/SchemaValidator.js +92 -0
  46. package/frigg-cli/infrastructure/repositories/FileSystemApiModuleRepository.js +373 -0
  47. package/frigg-cli/infrastructure/repositories/FileSystemAppDefinitionRepository.js +116 -0
  48. package/frigg-cli/infrastructure/repositories/FileSystemIntegrationRepository.js +277 -0
  49. package/frigg-cli/init-command/backend-first-handler.js +124 -42
  50. package/frigg-cli/init-command/index.js +2 -1
  51. package/frigg-cli/init-command/template-handler.js +13 -3
  52. package/frigg-cli/install-command/backend-js.js +3 -3
  53. package/frigg-cli/install-command/environment-variables.js +16 -19
  54. package/frigg-cli/install-command/environment-variables.test.js +12 -13
  55. package/frigg-cli/install-command/index.js +14 -9
  56. package/frigg-cli/install-command/integration-file.js +3 -3
  57. package/frigg-cli/install-command/validate-package.js +5 -9
  58. package/frigg-cli/jest.config.js +4 -1
  59. package/frigg-cli/package-lock.json +16226 -0
  60. package/frigg-cli/repair-command/index.js +101 -128
  61. package/frigg-cli/start-command/application/RunPreflightChecksUseCase.js +376 -0
  62. package/frigg-cli/start-command/index.js +246 -2
  63. package/frigg-cli/start-command/infrastructure/DatabaseAdapter.js +591 -0
  64. package/frigg-cli/start-command/infrastructure/DockerAdapter.js +306 -0
  65. package/frigg-cli/start-command/presentation/InteractivePromptAdapter.js +329 -0
  66. package/frigg-cli/templates/backend/.env.example +62 -0
  67. package/frigg-cli/templates/backend/.eslintrc.json +12 -0
  68. package/frigg-cli/templates/backend/.prettierrc +6 -0
  69. package/frigg-cli/templates/backend/docker-compose.yml +22 -0
  70. package/frigg-cli/templates/backend/index.js +96 -0
  71. package/frigg-cli/templates/backend/infrastructure.js +12 -0
  72. package/frigg-cli/templates/backend/jest.config.js +17 -0
  73. package/frigg-cli/templates/backend/package.json +50 -0
  74. package/frigg-cli/templates/backend/src/api-modules/.gitkeep +10 -0
  75. package/frigg-cli/templates/backend/src/base/.gitkeep +7 -0
  76. package/frigg-cli/templates/backend/src/integrations/.gitkeep +10 -0
  77. package/frigg-cli/templates/backend/src/integrations/ExampleIntegration.js +65 -0
  78. package/frigg-cli/templates/backend/src/utils/.gitkeep +7 -0
  79. package/frigg-cli/templates/backend/test/setup.js +30 -0
  80. package/frigg-cli/templates/backend/ui-extensions/.gitkeep +0 -0
  81. package/frigg-cli/templates/backend/ui-extensions/README.md +77 -0
  82. package/frigg-cli/ui-command/index.js +58 -36
  83. package/frigg-cli/utils/__tests__/repo-detection.test.js +436 -0
  84. package/frigg-cli/utils/output.js +382 -0
  85. package/frigg-cli/utils/repo-detection.js +85 -37
  86. package/frigg-cli/validate-command/__tests__/adapters/validate-command.test.js +205 -0
  87. package/frigg-cli/validate-command/__tests__/application/validate-app-use-case.test.js +104 -0
  88. package/frigg-cli/validate-command/__tests__/domain/fix-suggestion.test.js +153 -0
  89. package/frigg-cli/validate-command/__tests__/domain/validation-error.test.js +162 -0
  90. package/frigg-cli/validate-command/__tests__/domain/validation-result.test.js +152 -0
  91. package/frigg-cli/validate-command/__tests__/infrastructure/api-module-validator.test.js +332 -0
  92. package/frigg-cli/validate-command/__tests__/infrastructure/app-definition-validator.test.js +191 -0
  93. package/frigg-cli/validate-command/__tests__/infrastructure/integration-class-validator.test.js +146 -0
  94. package/frigg-cli/validate-command/__tests__/infrastructure/template-validation.test.js +155 -0
  95. package/frigg-cli/validate-command/adapters/cli/validate-command.js +199 -0
  96. package/frigg-cli/validate-command/application/use-cases/validate-app-use-case.js +35 -0
  97. package/frigg-cli/validate-command/domain/entities/validation-result.js +74 -0
  98. package/frigg-cli/validate-command/domain/value-objects/fix-suggestion.js +74 -0
  99. package/frigg-cli/validate-command/domain/value-objects/validation-error.js +68 -0
  100. package/frigg-cli/validate-command/infrastructure/validators/api-module-validator.js +181 -0
  101. package/frigg-cli/validate-command/infrastructure/validators/app-definition-validator.js +128 -0
  102. package/frigg-cli/validate-command/infrastructure/validators/integration-class-validator.js +113 -0
  103. package/infrastructure/docs/iam-policy-templates.md +1 -1
  104. package/infrastructure/domains/networking/vpc-builder.test.js +2 -4
  105. package/infrastructure/domains/networking/vpc-resolver.test.js +1 -1
  106. package/infrastructure/domains/shared/cloudformation-discovery.test.js +4 -7
  107. package/infrastructure/domains/shared/resource-discovery.js +5 -5
  108. package/infrastructure/domains/shared/types/discovery-result.test.js +1 -1
  109. package/infrastructure/domains/shared/utilities/base-definition-factory.js +10 -1
  110. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +2 -2
  111. package/infrastructure/infrastructure-composer.test.js +2 -2
  112. package/infrastructure/jest.config.js +16 -0
  113. package/management-ui/README.md +245 -109
  114. package/package.json +8 -7
  115. package/frigg-cli/install-command/logger.js +0 -12
@@ -0,0 +1,277 @@
1
+ const path = require('path');
2
+ const {IIntegrationRepository} = require('../../domain/ports/IIntegrationRepository');
3
+ const {Integration} = require('../../domain/entities/Integration');
4
+ const {IntegrationName} = require('../../domain/value-objects/IntegrationName');
5
+
6
+ /**
7
+ * FileSystemIntegrationRepository
8
+ * Persists Integration entities to the file system
9
+ */
10
+ class FileSystemIntegrationRepository extends IIntegrationRepository {
11
+ constructor(fileSystemAdapter, backendPath, schemaValidator, templateEngine = null) {
12
+ super();
13
+ this.fileSystemAdapter = fileSystemAdapter;
14
+ this.backendPath = backendPath;
15
+ this.schemaValidator = schemaValidator;
16
+ this.templateEngine = templateEngine;
17
+ this.integrationsDir = path.join(backendPath, 'src/integrations');
18
+ }
19
+
20
+ /**
21
+ * Save integration to file system
22
+ */
23
+ async save(integration) {
24
+ // Validate domain entity
25
+ const validation = integration.validate();
26
+ if (!validation.isValid) {
27
+ throw new Error(`Invalid integration: ${validation.errors.join(', ')}`);
28
+ }
29
+
30
+ // Convert to persistence format
31
+ const persistenceData = this._toPersistenceFormat(integration);
32
+
33
+ // Validate against schema
34
+ const schemaValidation = await this.schemaValidator.validate(
35
+ 'integration-definition',
36
+ persistenceData.definition
37
+ );
38
+
39
+ if (!schemaValidation.valid) {
40
+ throw new Error(`Schema validation failed: ${schemaValidation.errors.join(', ')}`);
41
+ }
42
+
43
+ // Ensure integrations directory exists
44
+ await this.fileSystemAdapter.ensureDirectory(this.integrationsDir);
45
+
46
+ // Write single Integration.js file
47
+ // Note: Integration.js should only be written on creation, not on updates
48
+ // to preserve manual edits and module additions
49
+ const integrationJsPath = this._getIntegrationFilePath(integration.name.value);
50
+ const integrationJsExists = await this.fileSystemAdapter.exists(integrationJsPath);
51
+
52
+ if (!integrationJsExists) {
53
+ await this.fileSystemAdapter.writeFile(integrationJsPath, persistenceData.classFile);
54
+ }
55
+
56
+ return integration;
57
+ }
58
+
59
+ /**
60
+ * Find integration by name
61
+ */
62
+ async findByName(name) {
63
+ const nameStr = typeof name === 'string' ? name : name.value;
64
+ const integrationPath = this._getIntegrationFilePath(nameStr);
65
+
66
+ if (!await this.fileSystemAdapter.exists(integrationPath)) {
67
+ return null;
68
+ }
69
+
70
+ // Read the Integration.js file and extract static Definition
71
+ const content = await this.fileSystemAdapter.readFile(integrationPath);
72
+
73
+ // Parse the static Definition from the file
74
+ // This is a simple implementation - could be enhanced with AST parsing
75
+ const definitionMatch = content.match(/static Definition = ({[\s\S]*?});/);
76
+ if (!definitionMatch) {
77
+ return null;
78
+ }
79
+
80
+ try {
81
+ // Evaluate the definition object (be careful - this is simplified)
82
+ const definitionStr = definitionMatch[1];
83
+ // For now, just extract basic info using regex
84
+ const nameMatch = definitionStr.match(/name:\s*['"]([^'"]+)['"]/);
85
+ const versionMatch = definitionStr.match(/version:\s*['"]([^'"]+)['"]/);
86
+
87
+ if (!nameMatch || !versionMatch) {
88
+ return null;
89
+ }
90
+
91
+ return new Integration({
92
+ name: nameMatch[1],
93
+ version: versionMatch[1],
94
+ displayName: nameStr,
95
+ description: '',
96
+ });
97
+ } catch (error) {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Check if integration exists
104
+ */
105
+ async exists(name) {
106
+ const nameStr = typeof name === 'string' ? name : name.value;
107
+ const integrationPath = this._getIntegrationFilePath(nameStr);
108
+ return await this.fileSystemAdapter.exists(integrationPath);
109
+ }
110
+
111
+ /**
112
+ * Get the file path for an integration
113
+ */
114
+ _getIntegrationFilePath(name) {
115
+ const className = this._toClassName(name);
116
+ return path.join(this.integrationsDir, `${className}Integration.js`);
117
+ }
118
+
119
+ /**
120
+ * Convert kebab-case name to ClassName
121
+ */
122
+ _toClassName(name) {
123
+ return name
124
+ .split('-')
125
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
126
+ .join('');
127
+ }
128
+
129
+ /**
130
+ * List all integrations by reading Integration.js files
131
+ */
132
+ async list() {
133
+ if (!await this.fileSystemAdapter.exists(this.integrationsDir)) {
134
+ return [];
135
+ }
136
+
137
+ const files = await this.fileSystemAdapter.listFiles(this.integrationsDir, '*.js');
138
+ const integrations = [];
139
+
140
+ for (const fileName of files) {
141
+ // Only process files matching {Name}Integration.js pattern
142
+ if (!fileName.endsWith('Integration.js')) {
143
+ continue;
144
+ }
145
+
146
+ try {
147
+ // Extract integration name from filename
148
+ const className = fileName.replace('.js', '').replace('Integration', '');
149
+ const kebabName = this._toKebabCase(className);
150
+
151
+ const integration = await this.findByName(kebabName);
152
+ if (integration) {
153
+ integrations.push(integration);
154
+ }
155
+ } catch (error) {
156
+ console.warn(`Failed to load integration ${fileName}:`, error.message);
157
+ }
158
+ }
159
+
160
+ return integrations;
161
+ }
162
+
163
+ /**
164
+ * Convert ClassName to kebab-case
165
+ */
166
+ _toKebabCase(className) {
167
+ return className
168
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
169
+ .toLowerCase();
170
+ }
171
+
172
+ /**
173
+ * Convert domain entity to persistence format
174
+ */
175
+ _toPersistenceFormat(integration) {
176
+ const json = integration.toJSON();
177
+
178
+ return {
179
+ classFile: this._generateIntegrationClass(integration),
180
+ definition: json, // Still needed for schema validation
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Convert persistence data to domain entity
186
+ */
187
+ _toDomainEntity(persistenceData) {
188
+ return new Integration({
189
+ name: persistenceData.name,
190
+ version: persistenceData.version,
191
+ displayName: persistenceData.display?.name,
192
+ description: persistenceData.display?.description,
193
+ type: persistenceData.options?.type || 'custom',
194
+ category: persistenceData.display?.category,
195
+ tags: persistenceData.display?.tags || [],
196
+ entities: persistenceData.entities || {},
197
+ apiModules: [], // Would need to extract from somewhere
198
+ capabilities: persistenceData.capabilities || {},
199
+ requirements: persistenceData.requirements || {},
200
+ options: persistenceData.options || {}
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Generate Integration.js class file
206
+ */
207
+ _generateIntegrationClass(integration) {
208
+ const className = integration.name.value
209
+ .split('-')
210
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
211
+ .join('');
212
+
213
+ const obj = integration.toObject();
214
+
215
+ return `const { IntegrationBase } = require('@friggframework/core');
216
+
217
+ /**
218
+ * ${integration.displayName} Integration
219
+ * ${integration.description || 'No description provided'}
220
+ */
221
+ class ${className}Integration extends IntegrationBase {
222
+ static Definition = {
223
+ name: '${obj.name}',
224
+ version: '${obj.version}',
225
+ supportedVersions: ['${obj.version}'],
226
+ hasUserConfig: false,
227
+
228
+ display: {
229
+ label: '${integration.displayName}',
230
+ description: '${integration.description || 'No description provided'}',
231
+ category: '${integration.category || 'Other'}',
232
+ detailsUrl: '',
233
+ icon: '',
234
+ },
235
+ modules: {
236
+ // Add your API modules here
237
+ // Example:
238
+ // myModule: {
239
+ // definition: myModule.Definition,
240
+ // },
241
+ },
242
+ routes: [
243
+ // Define your integration routes here
244
+ // Example:
245
+ // {
246
+ // path: '/auth',
247
+ // method: 'GET',
248
+ // event: 'AUTH_REQUEST',
249
+ // },
250
+ ],
251
+ };
252
+
253
+ constructor() {
254
+ super();
255
+ this.events = {
256
+ // Define your event handlers here
257
+ // Example:
258
+ // AUTH_REQUEST: {
259
+ // handler: this.authRequest.bind(this),
260
+ // },
261
+ };
262
+ }
263
+
264
+ // TODO: Add your integration methods here
265
+ // Example:
266
+ // async authRequest({ res }) {
267
+ // return res.json({ url: 'https://example.com/auth' });
268
+ // }
269
+ }
270
+
271
+ module.exports = ${className}Integration;
272
+ `;
273
+ }
274
+
275
+ }
276
+
277
+ module.exports = {FileSystemIntegrationRepository};
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
- const { select, confirm, multiselect } = require('@inquirer/prompts');
4
+ const { select, confirm, checkbox } = require('@inquirer/prompts');
5
5
  const { execSync } = require('child_process');
6
6
  const spawn = require('cross-spawn');
7
7
  const npmRegistry = require('../utils/npm-registry');
@@ -35,15 +35,15 @@ class BackendFirstHandler {
35
35
  await this.createProject(deploymentMode, config);
36
36
 
37
37
  console.log(chalk.green('\n✅ Frigg application created successfully!'));
38
-
39
- // If user needs custom API module, prompt to create it
40
- if (config.needsCustomApiModule) {
38
+
39
+ // If user needs custom API module, prompt to create it (skip in --yes mode)
40
+ if (config.needsCustomApiModule && !this.options.yes) {
41
41
  console.log(chalk.cyan('\n🔧 Now let\'s create your custom API module...'));
42
42
  const createModule = await confirm({
43
43
  message: 'Would you like to create your custom API module now?',
44
44
  default: true
45
45
  });
46
-
46
+
47
47
  if (createModule) {
48
48
  console.log(chalk.gray('\n Run this command after setup:'));
49
49
  console.log(chalk.cyan(` cd ${path.relative(process.cwd(), this.targetPath)}`));
@@ -62,6 +62,11 @@ class BackendFirstHandler {
62
62
  return this.options.mode;
63
63
  }
64
64
 
65
+ // If --yes flag is set, use default
66
+ if (this.options.yes) {
67
+ return 'standalone';
68
+ }
69
+
65
70
  const mode = await select({
66
71
  message: 'How will you deploy this Frigg application?',
67
72
  choices: [
@@ -88,6 +93,21 @@ class BackendFirstHandler {
88
93
  async getProjectConfiguration(deploymentMode) {
89
94
  const config = { deploymentMode };
90
95
 
96
+ // If --yes flag is set, use all defaults
97
+ if (this.options.yes) {
98
+ return {
99
+ deploymentMode,
100
+ appPurpose: 'exploring',
101
+ needsCustomApiModule: false,
102
+ includeIntegrations: false,
103
+ starterIntegrations: [],
104
+ includeDemoFrontend: false,
105
+ serverlessProvider: deploymentMode === 'standalone' ? 'aws' : undefined,
106
+ installDependencies: true,
107
+ initializeGit: true,
108
+ };
109
+ }
110
+
91
111
  // Ask about the purpose of this Frigg application
92
112
  config.appPurpose = await select({
93
113
  message: 'What are you building with Frigg?',
@@ -158,7 +178,7 @@ class BackendFirstHandler {
158
178
  }
159
179
  });
160
180
 
161
- config.starterIntegrations = await multiselect({
181
+ config.starterIntegrations = await checkbox({
162
182
  message: 'Select API modules to integrate (space to select, enter to confirm):',
163
183
  choices,
164
184
  instructions: '\n Press <space> to select, <a> to toggle all, <enter> to confirm\n',
@@ -266,63 +286,115 @@ class BackendFirstHandler {
266
286
 
267
287
  /**
268
288
  * Create standalone Frigg service
289
+ *
290
+ * Structure:
291
+ * my-app/
292
+ * ├── package.json # Root - delegates to backend with "cd backend &&"
293
+ * ├── backend/ # Actual Frigg app with its own package.json & node_modules
294
+ * │ ├── index.js
295
+ * │ ├── infrastructure.js
296
+ * │ ├── package.json
297
+ * │ └── src/
298
+ * └── ui-extensions/ # Platform-specific UI extensions
299
+ * └── README.md
269
300
  */
270
301
  async createStandaloneProject(config) {
271
- // Copy backend template
302
+ const backendPath = path.join(this.targetPath, 'backend');
303
+ const uiExtensionsPath = path.join(this.targetPath, 'ui-extensions');
304
+
305
+ // Copy backend template to backend/ subdirectory
272
306
  const backendTemplate = path.join(this.templatesDir, 'backend');
273
- await fs.copy(backendTemplate, this.targetPath);
307
+ await fs.copy(backendTemplate, backendPath);
274
308
 
275
- // Create package.json for standalone mode
276
- const packageJson = {
277
- name: this.appName,
309
+ // Read template package.json
310
+ const templatePackageJsonPath = path.join(backendPath, 'package.json');
311
+ let templatePackageJson = {};
312
+ if (await fs.pathExists(templatePackageJsonPath)) {
313
+ templatePackageJson = await fs.readJSON(templatePackageJsonPath);
314
+ }
315
+
316
+ // Create backend package.json with all dependencies
317
+ const backendPackageJson = {
318
+ name: `${this.appName}-backend`,
278
319
  version: '0.1.0',
279
320
  private: true,
321
+ prettier: templatePackageJson.prettier || '@friggframework/prettier-config',
280
322
  scripts: {
323
+ ...templatePackageJson.scripts,
281
324
  "backend-start": "node infrastructure.js start",
282
325
  "start": "npm run backend-start",
283
326
  "build": "node infrastructure.js package",
284
327
  "deploy": "node infrastructure.js deploy",
285
- "test": "jest"
286
328
  },
287
329
  dependencies: {
288
- "@friggframework/core": "^2.0.0"
330
+ ...templatePackageJson.dependencies,
331
+ "@friggframework/core": "2.0.0-next.58"
332
+ },
333
+ devDependencies: {
334
+ ...templatePackageJson.devDependencies
289
335
  }
290
336
  };
291
337
 
292
- // Add demo frontend if requested
293
- if (config.includeDemoFrontend) {
294
- packageJson.workspaces = ['backend', 'frontend'];
295
- packageJson.scripts['dev'] = 'concurrently "npm run backend-start" "npm run frontend:dev"';
296
- packageJson.scripts['frontend:dev'] = 'cd frontend && npm run dev';
297
-
298
- await this.createDemoFrontend(config);
299
- }
300
-
301
- // Add selected integrations as dependencies
338
+ // Add selected integrations as dependencies to backend
302
339
  if (config.starterIntegrations && config.starterIntegrations.length > 0) {
303
340
  for (const integration of config.starterIntegrations) {
304
- packageJson.dependencies[`@friggframework/api-module-${integration}`] = '^2.0.0';
341
+ backendPackageJson.dependencies[`@friggframework/api-module-${integration}`] = '^2.0.0';
305
342
  }
306
343
  }
307
344
 
345
+ await fs.writeJSON(templatePackageJsonPath, backendPackageJson, { spaces: 2 });
346
+
347
+ // Create root package.json that delegates to backend
348
+ const rootPackageJson = {
349
+ name: this.appName,
350
+ version: '0.1.0',
351
+ private: true,
352
+ scripts: {
353
+ "start": "cd backend && npm run frigg:start",
354
+ "docker:start": "cd backend && npm run docker:start",
355
+ "docker:stop": "cd backend && npm run docker:stop",
356
+ "test": "cd backend && npm test",
357
+ "build": "cd backend && npm run build",
358
+ "deploy": "cd backend && npm run deploy",
359
+ "lint": "cd backend && npm run lint",
360
+ "format": "cd backend && npm run format"
361
+ }
362
+ };
363
+
364
+ // Add demo frontend if requested
365
+ if (config.includeDemoFrontend) {
366
+ rootPackageJson.scripts['dev'] = 'concurrently "cd backend && npm run backend-start" "cd frontend && npm run dev"';
367
+ rootPackageJson.scripts['frontend:dev'] = 'cd frontend && npm run dev';
368
+ rootPackageJson.devDependencies = { concurrently: '^8.2.2' };
369
+
370
+ await this.createDemoFrontend(config);
371
+ }
372
+
308
373
  await fs.writeJSON(
309
374
  path.join(this.targetPath, 'package.json'),
310
- packageJson,
375
+ rootPackageJson,
311
376
  { spaces: 2 }
312
377
  );
313
378
 
379
+ // Create ui-extensions directory with README
380
+ await fs.ensureDir(uiExtensionsPath);
381
+ const uiExtensionsReadme = path.join(this.templatesDir, 'backend', 'ui-extensions', 'README.md');
382
+ if (await fs.pathExists(uiExtensionsReadme)) {
383
+ await fs.copy(uiExtensionsReadme, path.join(uiExtensionsPath, 'README.md'));
384
+ }
385
+
314
386
  // Update index.js with selected integrations
315
387
  if (config.starterIntegrations && config.starterIntegrations.length > 0) {
316
- await this.updateAppDefinition(config.starterIntegrations);
388
+ await this.updateAppDefinition(config.starterIntegrations, backendPath);
317
389
  }
318
390
 
319
391
  // Validate generated app definition against schema
320
- const appDefPath = path.join(this.targetPath, 'index.js');
392
+ const appDefPath = path.join(backendPath, 'index.js');
321
393
  await this.validateGeneratedAppDefinition(appDefPath);
322
394
 
323
395
  // Update serverless.yml based on provider
324
396
  if (config.serverlessProvider === 'aws') {
325
- await this.configureAWSServerless();
397
+ await this.configureAWSServerless(backendPath);
326
398
  }
327
399
  }
328
400
 
@@ -506,9 +578,9 @@ To integrate Frigg into your production application:
506
578
  /**
507
579
  * Configure AWS serverless
508
580
  */
509
- async configureAWSServerless() {
581
+ async configureAWSServerless(targetDir = this.targetPath) {
510
582
  // Update serverless.yml for AWS
511
- const serverlessPath = path.join(this.targetPath, 'serverless.yml');
583
+ const serverlessPath = path.join(targetDir, 'serverless.yml');
512
584
  if (await fs.pathExists(serverlessPath)) {
513
585
  // Keep existing AWS configuration
514
586
  console.log(chalk.gray('AWS Lambda configuration ready'));
@@ -533,23 +605,25 @@ To integrate Frigg into your production application:
533
605
  }
534
606
 
535
607
  /**
536
- * Install dependencies
608
+ * Install dependencies in the backend directory
537
609
  */
538
610
  async installDependencies(config) {
539
- console.log(chalk.blue('\n📦 Installing dependencies...'));
540
-
611
+ console.log(chalk.blue('\n📦 Installing dependencies in backend...'));
612
+
541
613
  const useYarn = this.isUsingYarn();
542
614
  const command = useYarn ? 'yarn' : 'npm';
543
615
  const args = useYarn ? [] : ['install'];
544
616
 
617
+ // Install in backend directory where package.json with dependencies lives
618
+ const backendPath = path.join(this.targetPath, 'backend');
545
619
  const proc = spawn.sync(command, args, {
546
- cwd: this.targetPath,
620
+ cwd: backendPath,
547
621
  stdio: 'inherit'
548
622
  });
549
623
 
550
624
  if (proc.status !== 0) {
551
625
  console.log(chalk.yellow('\n⚠️ Dependency installation failed'));
552
- console.log(chalk.gray(`You can install manually with: ${command} install`));
626
+ console.log(chalk.gray(`You can install manually with: cd backend && ${command} install`));
553
627
  }
554
628
  }
555
629
 
@@ -577,7 +651,7 @@ To integrate Frigg into your production application:
577
651
  * Select from default integrations when npm is unavailable
578
652
  */
579
653
  async selectDefaultIntegrations() {
580
- return await multiselect({
654
+ return await checkbox({
581
655
  message: 'Select starter integrations (space to select, enter to confirm):',
582
656
  choices: [
583
657
  { name: 'Salesforce - CRM integration', value: 'salesforce' },
@@ -596,8 +670,8 @@ To integrate Frigg into your production application:
596
670
  /**
597
671
  * Update index.js with selected integrations
598
672
  */
599
- async updateAppDefinition(integrations) {
600
- const appDefPath = path.join(this.targetPath, 'index.js');
673
+ async updateAppDefinition(integrations, targetDir = this.targetPath) {
674
+ const appDefPath = path.join(targetDir, 'index.js');
601
675
  if (await fs.pathExists(appDefPath)) {
602
676
  let content = await fs.readFile(appDefPath, 'utf8');
603
677
 
@@ -720,14 +794,18 @@ To integrate Frigg into your production application:
720
794
  console.log(chalk.cyan(` cd ${cdPath}\n`));
721
795
 
722
796
  if (deploymentMode === 'standalone') {
723
- console.log(`2. Start the development server:`);
797
+ console.log(`2. Install backend dependencies:`);
798
+ console.log(chalk.cyan(` cd backend && npm install\n`));
799
+
800
+ console.log(`3. Start the development server:`);
724
801
  console.log(chalk.cyan(` npm start\n`));
802
+ console.log(chalk.gray(` (or from backend: npm run frigg:start)\n`));
725
803
 
726
- console.log(`3. Open the Frigg UI for development:`);
804
+ console.log(`4. Open the Frigg UI for development:`);
727
805
  console.log(chalk.cyan(` frigg ui\n`));
728
806
 
729
807
  if (config.serverlessProvider === 'aws') {
730
- console.log(`4. Deploy to AWS Lambda:`);
808
+ console.log(`5. Deploy to AWS Lambda:`);
731
809
  console.log(chalk.cyan(` npm run deploy\n`));
732
810
  }
733
811
  } else {
@@ -747,8 +825,12 @@ To integrate Frigg into your production application:
747
825
  console.log(chalk.gray(' See frontend/README.md for integration guidance.'));
748
826
  }
749
827
 
828
+ console.log(chalk.gray('\n📁 Project Structure:'));
829
+ console.log(chalk.gray(' backend/ - Frigg app (run npm install here)'));
830
+ console.log(chalk.gray(' ui-extensions/ - Platform-specific UI extensions'));
831
+
750
832
  console.log(chalk.green('\n🎉 Happy integrating with Frigg!\n'));
751
- console.log(chalk.gray('Documentation: https://docs.frigg.dev'));
833
+ console.log(chalk.gray('Documentation: https://docs.friggframework.org'));
752
834
  console.log(chalk.gray('Support: https://github.com/friggframework/frigg/issues'));
753
835
  }
754
836
  }
@@ -68,7 +68,8 @@ async function initCommand(projectName, options) {
68
68
  force,
69
69
  verbose,
70
70
  mode: options.mode,
71
- frontend: options.frontend
71
+ frontend: options.frontend,
72
+ yes: options.yes
72
73
  });
73
74
 
74
75
  await handler.initialize();
@@ -75,6 +75,16 @@ class TemplateHandler {
75
75
  return !['node_modules', '.serverless', 'dist', 'build'].includes(basename);
76
76
  }
77
77
  });
78
+
79
+ // Rename .env.default to .env if it exists and .env doesn't already exist
80
+ const envDefaultPath = path.join(target, '.env.default');
81
+ const envPath = path.join(target, '.env');
82
+ if (fs.existsSync(envDefaultPath) && !fs.existsSync(envPath)) {
83
+ await fs.rename(envDefaultPath, envPath);
84
+ } else if (fs.existsSync(envDefaultPath)) {
85
+ // Remove the .env.default if .env already exists
86
+ await fs.remove(envDefaultPath);
87
+ }
78
88
  }
79
89
 
80
90
  /**
@@ -100,14 +110,14 @@ class TemplateHandler {
100
110
  async updateServerlessConfig() {
101
111
  const serverlessPath = path.join(this.targetPath, 'serverless.yml');
102
112
  let serverlessContent = await fs.readFile(serverlessPath, 'utf8');
103
-
113
+
104
114
  // Update service name based on directory name
105
115
  const projectName = path.basename(this.targetPath);
106
116
  serverlessContent = serverlessContent.replace(
107
- /^service: create-frigg-app$/m,
117
+ /^service: frigg-app$/m,
108
118
  `service: ${projectName}`
109
119
  );
110
-
120
+
111
121
  await fs.writeFile(serverlessPath, serverlessContent);
112
122
  }
113
123
 
@@ -1,12 +1,12 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
- const { logInfo } = require('./logger');
3
+ const output = require('../utils/output');
4
4
  const INTEGRATIONS_DIR = 'src/integrations';
5
5
  const BACKEND_JS = 'backend.js';
6
6
 
7
7
  function updateBackendJsFile(backendPath, apiModuleName) {
8
8
  const backendJsPath = path.join(path.dirname(backendPath), BACKEND_JS);
9
- logInfo(`Updating backend.js: ${backendJsPath}`);
9
+ output.debug(`Updating backend.js: ${backendJsPath}`);
10
10
  updateBackendJs(backendJsPath, apiModuleName);
11
11
  }
12
12
 
@@ -21,7 +21,7 @@ function updateBackendJs(backendJsPath, apiModuleName) {
21
21
  );
22
22
  fs.writeFileSync(backendJsPath, importStatement + updatedContent);
23
23
  } else {
24
- logInfo(
24
+ output.debug(
25
25
  `Import statement for ${apiModuleName}Integration already exists in backend.js`
26
26
  );
27
27
  }