@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,306 @@
1
+ /**
2
+ * DockerAdapter - Infrastructure adapter for Docker operations
3
+ *
4
+ * Provides methods to interact with Docker and Docker Compose
5
+ * Used by pre-flight checks to verify database infrastructure is ready
6
+ */
7
+
8
+ const { exec } = require('child_process');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ class DockerAdapter {
13
+ /**
14
+ * Check if Docker CLI is installed
15
+ * @returns {Promise<boolean>} True if docker command is available
16
+ */
17
+ async isDockerInstalled() {
18
+ return new Promise((resolve) => {
19
+ exec('docker --version', (error) => {
20
+ resolve(!error);
21
+ });
22
+ });
23
+ }
24
+
25
+ /**
26
+ * Check if Docker daemon is running
27
+ * @returns {Promise<boolean>} True if docker daemon is responsive
28
+ */
29
+ async isDockerRunning() {
30
+ return new Promise((resolve) => {
31
+ exec('docker info', (error) => {
32
+ resolve(!error);
33
+ });
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Find docker-compose file in project directory or parent
39
+ * @param {string} projectPath - Path to search in
40
+ * @returns {Promise<string|null>} Path to compose file or null if not found
41
+ */
42
+ async findDockerComposeFile(projectPath) {
43
+ const composeFileNames = [
44
+ 'docker-compose.yml',
45
+ 'docker-compose.yaml',
46
+ 'compose.yml',
47
+ 'compose.yaml'
48
+ ];
49
+
50
+ // Search in project path first
51
+ for (const fileName of composeFileNames) {
52
+ const filePath = path.join(projectPath, fileName);
53
+ if (fs.existsSync(filePath)) {
54
+ return filePath;
55
+ }
56
+ }
57
+
58
+ // Search in parent directory (for backend paths)
59
+ const parentPath = path.dirname(projectPath);
60
+ if (parentPath !== projectPath) {
61
+ for (const fileName of composeFileNames) {
62
+ const filePath = path.join(parentPath, fileName);
63
+ if (fs.existsSync(filePath)) {
64
+ return filePath;
65
+ }
66
+ }
67
+ }
68
+
69
+ return null;
70
+ }
71
+
72
+ /**
73
+ * Start Docker Compose services
74
+ * @param {string} composePath - Path to docker-compose file
75
+ * @returns {Promise<{success: boolean, error?: string, output?: string}>}
76
+ */
77
+ async startDockerCompose(composePath) {
78
+ return new Promise((resolve) => {
79
+ const cmd = `docker compose -f ${composePath} up -d`;
80
+ const options = { cwd: path.dirname(composePath) };
81
+
82
+ exec(cmd, options, (error, stdout, stderr) => {
83
+ if (error) {
84
+ resolve({
85
+ success: false,
86
+ error: stderr || error.message
87
+ });
88
+ } else {
89
+ resolve({
90
+ success: true,
91
+ output: stdout
92
+ });
93
+ }
94
+ });
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Stop Docker Compose services
100
+ * @param {string} composePath - Path to docker-compose file
101
+ * @returns {Promise<{success: boolean, error?: string, output?: string}>}
102
+ */
103
+ async stopDockerCompose(composePath) {
104
+ return new Promise((resolve) => {
105
+ const cmd = `docker compose -f ${composePath} down`;
106
+ const options = { cwd: path.dirname(composePath) };
107
+
108
+ exec(cmd, options, (error, stdout, stderr) => {
109
+ if (error) {
110
+ resolve({
111
+ success: false,
112
+ error: stderr || error.message
113
+ });
114
+ } else {
115
+ resolve({
116
+ success: true,
117
+ output: stdout
118
+ });
119
+ }
120
+ });
121
+ });
122
+ }
123
+
124
+ /**
125
+ * Start Docker Desktop application
126
+ * @returns {Promise<{success: boolean, error?: string}>}
127
+ */
128
+ async startDockerDesktop() {
129
+ return new Promise((resolve) => {
130
+ let cmd;
131
+ const platform = process.platform;
132
+
133
+ if (platform === 'darwin') {
134
+ cmd = 'open -a "Docker Desktop"';
135
+ } else if (platform === 'win32') {
136
+ cmd = 'start "" "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"';
137
+ } else {
138
+ // Linux - try systemctl first, fall back to service
139
+ cmd = 'systemctl start docker || service docker start';
140
+ }
141
+
142
+ exec(cmd, (error) => {
143
+ if (error) {
144
+ resolve({
145
+ success: false,
146
+ error: error.message
147
+ });
148
+ } else {
149
+ resolve({ success: true });
150
+ }
151
+ });
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Get list of running Docker Compose services
157
+ * @param {string} composePath - Path to docker-compose file
158
+ * @returns {Promise<string[]>} List of running service names
159
+ */
160
+ async getDockerComposeServices(composePath) {
161
+ return new Promise((resolve) => {
162
+ const cmd = `docker compose -f ${composePath} ps --services --filter "status=running"`;
163
+ const options = { cwd: path.dirname(composePath) };
164
+
165
+ exec(cmd, options, (error, stdout) => {
166
+ if (error) {
167
+ resolve([]);
168
+ } else {
169
+ const services = stdout
170
+ .split('\n')
171
+ .map(s => s.trim())
172
+ .filter(s => s.length > 0);
173
+ resolve(services);
174
+ }
175
+ });
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Check if a specific service is running
181
+ * @param {string} composePath - Path to docker-compose file
182
+ * @param {string} serviceName - Name of the service to check
183
+ * @returns {Promise<boolean>} True if service is running
184
+ */
185
+ async isServiceRunning(composePath, serviceName) {
186
+ const services = await this.getDockerComposeServices(composePath);
187
+ return services.includes(serviceName);
188
+ }
189
+
190
+ /**
191
+ * Wait for Docker to become ready after starting
192
+ * @param {object} options - Wait options
193
+ * @param {number} options.maxAttempts - Maximum number of attempts (default: 30)
194
+ * @param {number} options.intervalMs - Interval between attempts in ms (default: 1000)
195
+ * @returns {Promise<boolean>} True if Docker became ready, false if timed out
196
+ */
197
+ async waitForDockerReady(options = {}) {
198
+ const { maxAttempts = 30, intervalMs = 1000 } = options;
199
+
200
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
201
+ const isRunning = await this.isDockerRunning();
202
+ if (isRunning) {
203
+ return true;
204
+ }
205
+ await this._sleep(intervalMs);
206
+ }
207
+
208
+ return false;
209
+ }
210
+
211
+ /**
212
+ * Wait for LocalStack to be ready by polling health endpoint
213
+ * @param {object} options - Wait options
214
+ * @param {number} options.maxAttempts - Maximum number of attempts (default: 30)
215
+ * @param {number} options.intervalMs - Interval between attempts in ms (default: 2000)
216
+ * @param {string} options.endpoint - LocalStack endpoint (default: http://localhost:4566)
217
+ * @returns {Promise<{ready: boolean, services?: object, error?: string}>}
218
+ */
219
+ async waitForLocalStack(options = {}) {
220
+ const {
221
+ maxAttempts = 30,
222
+ intervalMs = 2000,
223
+ endpoint = process.env.AWS_ENDPOINT || 'http://localhost:4566'
224
+ } = options;
225
+
226
+ const healthUrl = `${endpoint}/_localstack/health`;
227
+
228
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
229
+ try {
230
+ const result = await this._checkLocalStackHealth(healthUrl);
231
+ if (result.ready) {
232
+ return result;
233
+ }
234
+ // Not ready yet, wait and retry
235
+ if (attempt < maxAttempts) {
236
+ await this._sleep(intervalMs);
237
+ }
238
+ } catch (error) {
239
+ // Connection failed, wait and retry
240
+ if (attempt < maxAttempts) {
241
+ await this._sleep(intervalMs);
242
+ }
243
+ }
244
+ }
245
+
246
+ return {
247
+ ready: false,
248
+ error: `LocalStack not ready after ${maxAttempts} attempts`
249
+ };
250
+ }
251
+
252
+ /**
253
+ * Check LocalStack health endpoint
254
+ * @param {string} healthUrl - URL to health endpoint
255
+ * @returns {Promise<{ready: boolean, services?: object}>}
256
+ */
257
+ async _checkLocalStackHealth(healthUrl) {
258
+ return new Promise((resolve, reject) => {
259
+ const http = require('http');
260
+ const url = new URL(healthUrl);
261
+
262
+ const req = http.get({
263
+ hostname: url.hostname,
264
+ port: url.port,
265
+ path: url.pathname,
266
+ timeout: 5000
267
+ }, (res) => {
268
+ let data = '';
269
+ res.on('data', chunk => data += chunk);
270
+ res.on('end', () => {
271
+ try {
272
+ const health = JSON.parse(data);
273
+ // Check if services are running
274
+ // LocalStack returns: { "services": { "sqs": "running", ... } }
275
+ const services = health.services || {};
276
+ const sqsReady = services.sqs === 'running' || services.sqs === 'available';
277
+
278
+ resolve({
279
+ ready: sqsReady,
280
+ services: services
281
+ });
282
+ } catch (e) {
283
+ resolve({ ready: false });
284
+ }
285
+ });
286
+ });
287
+
288
+ req.on('error', reject);
289
+ req.on('timeout', () => {
290
+ req.destroy();
291
+ reject(new Error('Timeout'));
292
+ });
293
+ });
294
+ }
295
+
296
+ /**
297
+ * Helper to sleep for specified milliseconds
298
+ * @param {number} ms - Milliseconds to sleep
299
+ * @returns {Promise<void>}
300
+ */
301
+ _sleep(ms) {
302
+ return new Promise(resolve => setTimeout(resolve, ms));
303
+ }
304
+ }
305
+
306
+ module.exports = { DockerAdapter };
@@ -0,0 +1,329 @@
1
+ /**
2
+ * InteractivePromptAdapter - Handles prompts in terminal mode or IPC mode
3
+ *
4
+ * Presentation Layer - Adapts prompt interfaces for different contexts:
5
+ * - Terminal mode: Uses @inquirer/prompts for direct CLI interaction
6
+ * - IPC mode: Outputs JSON to stdout and reads responses from stdin
7
+ * (used when Management UI spawns the CLI process)
8
+ */
9
+
10
+ const { confirm, select, input } = require('@inquirer/prompts');
11
+ const { randomUUID } = require('crypto');
12
+
13
+ /**
14
+ * Factory for creating the appropriate prompt adapter
15
+ */
16
+ class InteractivePromptAdapter {
17
+ /**
18
+ * Create a prompt adapter based on mode
19
+ * @param {object} options - Options
20
+ * @param {string} options.mode - 'terminal' or 'ipc'
21
+ * @returns {TerminalPromptAdapter|IpcPromptAdapter}
22
+ */
23
+ static create(options = {}) {
24
+ // Check for IPC mode via environment variable or explicit option
25
+ const isIpcMode = options.mode === 'ipc' || process.env.FRIGG_IPC === 'true';
26
+
27
+ if (isIpcMode) {
28
+ return new IpcPromptAdapter();
29
+ }
30
+
31
+ return new TerminalPromptAdapter();
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Terminal-based prompt adapter using @inquirer/prompts
37
+ */
38
+ class TerminalPromptAdapter {
39
+ /**
40
+ * Show a confirmation prompt
41
+ * @param {object} options - Prompt options
42
+ * @param {string} options.message - Prompt message
43
+ * @param {boolean} options.default - Default value
44
+ * @returns {Promise<boolean>}
45
+ */
46
+ async confirm(options) {
47
+ return confirm({
48
+ message: options.message,
49
+ default: options.default
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Show a selection prompt
55
+ * @param {object} options - Prompt options
56
+ * @param {string} options.message - Prompt message
57
+ * @param {Array} options.choices - Array of {value, name} choices
58
+ * @returns {Promise<string>}
59
+ */
60
+ async select(options) {
61
+ return select({
62
+ message: options.message,
63
+ choices: options.choices
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Show a text input prompt
69
+ * @param {object} options - Prompt options
70
+ * @param {string} options.message - Prompt message
71
+ * @param {string} options.default - Default value
72
+ * @returns {Promise<string>}
73
+ */
74
+ async input(options) {
75
+ return input({
76
+ message: options.message,
77
+ default: options.default
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Prompt user to resolve a failed pre-flight check
83
+ * @param {object} check - Pre-flight check result
84
+ * @returns {Promise<{shouldResolve: boolean, composePath?: string}>}
85
+ */
86
+ async promptForResolution(check) {
87
+ if (!check.canResolve) {
88
+ return { shouldResolve: false };
89
+ }
90
+
91
+ const shouldResolve = await this.confirm({
92
+ message: check.resolution.prompt,
93
+ default: true
94
+ });
95
+
96
+ const result = { shouldResolve };
97
+
98
+ if (check.resolution.composePath) {
99
+ result.composePath = check.resolution.composePath;
100
+ }
101
+
102
+ return result;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * IPC-based prompt adapter for Management UI integration
108
+ * Outputs JSON prompts to stdout, reads responses from stdin
109
+ */
110
+ class IpcPromptAdapter {
111
+ constructor() {
112
+ this._pendingPrompts = new Map();
113
+ this._stdinBuffer = '';
114
+ this._stdinListenerSetup = false;
115
+ }
116
+
117
+ /**
118
+ * Setup stdin listener for IPC responses (lazy initialization)
119
+ */
120
+ _setupStdinListener() {
121
+ if (this._stdinListenerSetup) return;
122
+ this._stdinListenerSetup = true;
123
+
124
+ process.stdin.setEncoding('utf8');
125
+ process.stdin.on('data', (chunk) => {
126
+ this._stdinBuffer += chunk;
127
+
128
+ // Process complete lines
129
+ const lines = this._stdinBuffer.split('\n');
130
+ this._stdinBuffer = lines.pop(); // Keep incomplete line in buffer
131
+
132
+ for (const line of lines) {
133
+ if (!line.trim()) continue;
134
+
135
+ const ipcMessage = this._parseIpcMessage(line);
136
+ if (ipcMessage && ipcMessage.type === 'prompt_response') {
137
+ this._resolvePrompt(ipcMessage.requestId, ipcMessage.response);
138
+ }
139
+ }
140
+ });
141
+
142
+ process.stdin.on('end', () => {
143
+ // stdin closed - resolve any pending prompts with default/false
144
+ for (const [requestId, resolver] of this._pendingPrompts) {
145
+ resolver(false);
146
+ }
147
+ this._pendingPrompts.clear();
148
+ });
149
+
150
+ // Resume stdin to start receiving data
151
+ process.stdin.resume();
152
+ }
153
+
154
+ /**
155
+ * Generate a unique request ID
156
+ * @returns {string}
157
+ */
158
+ _generateRequestId() {
159
+ return `prompt-${randomUUID().split('-')[0]}`;
160
+ }
161
+
162
+ /**
163
+ * Format IPC output message
164
+ * @param {string} type - Message type
165
+ * @param {object} data - Message data
166
+ * @returns {string} JSON string with newline
167
+ */
168
+ _formatIpcOutput(type, data) {
169
+ const message = {
170
+ frigg_ipc: type,
171
+ ...data
172
+ };
173
+ return JSON.stringify(message) + '\n';
174
+ }
175
+
176
+ /**
177
+ * Parse an IPC message from stdin
178
+ * @param {string} message - Raw message string
179
+ * @returns {object|null} Parsed message or null if not IPC
180
+ */
181
+ _parseIpcMessage(message) {
182
+ try {
183
+ const parsed = JSON.parse(message);
184
+ if (!parsed.frigg_ipc) {
185
+ return null;
186
+ }
187
+ return {
188
+ type: parsed.frigg_ipc,
189
+ requestId: parsed.requestId,
190
+ response: parsed.response
191
+ };
192
+ } catch {
193
+ return null;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Resolve a pending prompt with a response
199
+ * @param {string} requestId - Request ID
200
+ * @param {any} response - Response value
201
+ */
202
+ _resolvePrompt(requestId, response) {
203
+ const resolver = this._pendingPrompts.get(requestId);
204
+ if (resolver) {
205
+ resolver(response);
206
+ this._pendingPrompts.delete(requestId);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Handle a response from the Management UI
212
+ * @param {string} requestId - Request ID
213
+ * @param {any} response - Response value
214
+ */
215
+ handleResponse(requestId, response) {
216
+ this._resolvePrompt(requestId, response);
217
+ }
218
+
219
+ /**
220
+ * Show a confirmation prompt via IPC
221
+ * @param {object} options - Prompt options
222
+ * @returns {Promise<boolean>}
223
+ */
224
+ async confirm(options) {
225
+ // Setup stdin listener before sending prompt
226
+ this._setupStdinListener();
227
+
228
+ const requestId = this._generateRequestId();
229
+
230
+ const output = this._formatIpcOutput('prompt_request', {
231
+ requestId,
232
+ prompt: {
233
+ type: 'confirm',
234
+ message: options.message,
235
+ default: options.default
236
+ }
237
+ });
238
+
239
+ process.stdout.write(output);
240
+
241
+ return new Promise((resolve) => {
242
+ this._pendingPrompts.set(requestId, resolve);
243
+ });
244
+ }
245
+
246
+ /**
247
+ * Show a selection prompt via IPC
248
+ * @param {object} options - Prompt options
249
+ * @returns {Promise<string>}
250
+ */
251
+ async select(options) {
252
+ // Setup stdin listener before sending prompt
253
+ this._setupStdinListener();
254
+
255
+ const requestId = this._generateRequestId();
256
+
257
+ const output = this._formatIpcOutput('prompt_request', {
258
+ requestId,
259
+ prompt: {
260
+ type: 'select',
261
+ message: options.message,
262
+ choices: options.choices
263
+ }
264
+ });
265
+
266
+ process.stdout.write(output);
267
+
268
+ return new Promise((resolve) => {
269
+ this._pendingPrompts.set(requestId, resolve);
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Show a text input prompt via IPC
275
+ * @param {object} options - Prompt options
276
+ * @returns {Promise<string>}
277
+ */
278
+ async input(options) {
279
+ // Setup stdin listener before sending prompt
280
+ this._setupStdinListener();
281
+
282
+ const requestId = this._generateRequestId();
283
+
284
+ const output = this._formatIpcOutput('prompt_request', {
285
+ requestId,
286
+ prompt: {
287
+ type: 'input',
288
+ message: options.message,
289
+ default: options.default
290
+ }
291
+ });
292
+
293
+ process.stdout.write(output);
294
+
295
+ return new Promise((resolve) => {
296
+ this._pendingPrompts.set(requestId, resolve);
297
+ });
298
+ }
299
+
300
+ /**
301
+ * Prompt user to resolve a failed pre-flight check via IPC
302
+ * @param {object} check - Pre-flight check result
303
+ * @returns {Promise<{shouldResolve: boolean, composePath?: string}>}
304
+ */
305
+ async promptForResolution(check) {
306
+ if (!check.canResolve) {
307
+ return { shouldResolve: false };
308
+ }
309
+
310
+ const shouldResolve = await this.confirm({
311
+ message: check.resolution.prompt,
312
+ default: true
313
+ });
314
+
315
+ const result = { shouldResolve };
316
+
317
+ if (check.resolution.composePath) {
318
+ result.composePath = check.resolution.composePath;
319
+ }
320
+
321
+ return result;
322
+ }
323
+ }
324
+
325
+ module.exports = {
326
+ InteractivePromptAdapter,
327
+ TerminalPromptAdapter,
328
+ IpcPromptAdapter
329
+ };
@@ -0,0 +1,62 @@
1
+ # Frigg Backend Environment Variables
2
+ # Copy this file to .env and fill in your values
3
+ # Or use the default .env file included with the project
4
+
5
+ # ============================================================================
6
+ # Core Configuration
7
+ # ============================================================================
8
+ FRIGG_BASE_URL=http://localhost:3001
9
+ REDIRECT_URI=http://localhost:3000/redirect
10
+
11
+ # Stage (local, dev, test, staging, production)
12
+ STAGE=local
13
+
14
+ # ============================================================================
15
+ # Database Configuration
16
+ # ============================================================================
17
+ # PostgreSQL (default for new projects - used with Docker)
18
+ DATABASE_URL=postgresql://frigg:frigg@localhost:5432/frigg
19
+ DATABASE_USER=frigg
20
+ DATABASE_PASSWORD=frigg
21
+
22
+ # MongoDB (alternative - uncomment if using MongoDB)
23
+ # MONGO_URI=mongodb://127.0.0.1:27017/frigg
24
+
25
+ # ============================================================================
26
+ # Security Keys
27
+ # ============================================================================
28
+ # Admin API key for management UI auto-connect (REQUIRED for admin features)
29
+ # Generate a secure random string for production
30
+ FRIGG_ADMIN_API_KEY=dev-admin-key-change-in-production
31
+
32
+ # Shared secret API key for header-based authentication (optional)
33
+ # Used when userManagementMode.sharedSecretEnabled is true
34
+ FRIGG_API_KEY=dev-shared-secret-change-in-production
35
+
36
+ # Health check API key (optional - for protected health endpoints)
37
+ HEALTH_API_KEY=dev-health-key
38
+
39
+ # ============================================================================
40
+ # Encryption Configuration
41
+ # ============================================================================
42
+ # AES encryption (for local development - simpler setup)
43
+ AES_KEY_ID=local-dev-key
44
+ AES_KEY=32-character-secret-key-here-!!
45
+
46
+ # AWS KMS encryption (for production - comment out AES and use these)
47
+ # KMS_KEY_ARN=arn:aws:kms:us-east-1:123456789012:key/your-key-id
48
+
49
+ # ============================================================================
50
+ # AWS Configuration (for deployment)
51
+ # ============================================================================
52
+ AWS_REGION=us-east-1
53
+ # S3_BUCKET_NAME=your-s3-bucket
54
+
55
+ # ============================================================================
56
+ # Integration Credentials
57
+ # ============================================================================
58
+ # Add your integration-specific credentials below
59
+ # HUBSPOT_CLIENT_ID=
60
+ # HUBSPOT_CLIENT_SECRET=
61
+ # SALESFORCE_CLIENT_ID=
62
+ # SALESFORCE_CLIENT_SECRET=
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": ["@friggframework/eslint-config"],
3
+ "env": {
4
+ "node": true,
5
+ "jest": true,
6
+ "es2021": true
7
+ },
8
+ "parserOptions": {
9
+ "ecmaVersion": 2021
10
+ },
11
+ "rules": {}
12
+ }