@friggframework/devtools 2.0.0-next.47 → 2.0.0-next.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/frigg-cli/README.md +1290 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +548 -0
- package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
- package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
- package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +366 -0
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +304 -0
- package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +287 -0
- package/frigg-cli/build-command/index.js +66 -0
- package/frigg-cli/db-setup-command/index.js +193 -0
- package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
- package/frigg-cli/deploy-command/index.js +302 -0
- package/frigg-cli/doctor-command/index.js +335 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +332 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/generate-iam-command.js +118 -0
- package/frigg-cli/index.js +173 -0
- package/frigg-cli/index.test.js +158 -0
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/install-command/backend-js.js +33 -0
- package/frigg-cli/install-command/commit-changes.js +16 -0
- package/frigg-cli/install-command/environment-variables.js +127 -0
- package/frigg-cli/install-command/environment-variables.test.js +136 -0
- package/frigg-cli/install-command/index.js +54 -0
- package/frigg-cli/install-command/install-package.js +13 -0
- package/frigg-cli/install-command/integration-file.js +30 -0
- package/frigg-cli/install-command/logger.js +12 -0
- package/frigg-cli/install-command/template.js +90 -0
- package/frigg-cli/install-command/validate-package.js +75 -0
- package/frigg-cli/jest.config.js +124 -0
- package/frigg-cli/package.json +63 -0
- package/frigg-cli/repair-command/index.js +564 -0
- package/frigg-cli/start-command/index.js +149 -0
- package/frigg-cli/start-command/start-command.test.js +297 -0
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +25 -0
- package/frigg-cli/utils/database-validator.js +154 -0
- package/frigg-cli/utils/error-messages.js +257 -0
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/create-frigg-infrastructure.js +125 -12
- package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
- package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
- package/infrastructure/domains/shared/resource-discovery.js +31 -2
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +1 -1
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +109 -5
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +310 -4
- package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
- package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
- package/infrastructure/infrastructure-composer.js +22 -0
- package/layers/prisma/.build-complete +3 -0
- package/package.json +18 -7
- package/management-ui/package-lock.json +0 -16517
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2024 Frigg Integration Framework
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Checks if a directory is a Frigg repository
|
|
17
|
+
* @param {string} directory - Path to check
|
|
18
|
+
* @returns {Promise<{isFriggRepo: boolean, repoInfo: object|null}>}
|
|
19
|
+
*/
|
|
20
|
+
async function isFriggRepository(directory) {
|
|
21
|
+
try {
|
|
22
|
+
const packageJsonPath = path.join(directory, 'package.json');
|
|
23
|
+
|
|
24
|
+
// Check if package.json exists
|
|
25
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
26
|
+
return { isFriggRepo: false, repoInfo: null };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
30
|
+
|
|
31
|
+
// Primary indicators of a Frigg repository
|
|
32
|
+
const indicators = {
|
|
33
|
+
hasFriggDependencies: false,
|
|
34
|
+
hasBackendWorkspace: false,
|
|
35
|
+
hasFrontendWorkspace: false,
|
|
36
|
+
hasServerlessConfig: false,
|
|
37
|
+
friggDependencies: []
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Check for @friggframework dependencies
|
|
41
|
+
const allDeps = {
|
|
42
|
+
...packageJson.dependencies,
|
|
43
|
+
...packageJson.devDependencies,
|
|
44
|
+
...packageJson.peerDependencies
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
for (const dep in allDeps) {
|
|
48
|
+
if (dep.startsWith('@friggframework/')) {
|
|
49
|
+
indicators.hasFriggDependencies = true;
|
|
50
|
+
indicators.friggDependencies.push(dep);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check for Frigg-specific files
|
|
55
|
+
const friggConfigFiles = [
|
|
56
|
+
'frigg.config.js',
|
|
57
|
+
'frigg.config.json',
|
|
58
|
+
'.friggrc',
|
|
59
|
+
'.friggrc.json',
|
|
60
|
+
'.friggrc.js'
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
indicators.hasFriggConfig = friggConfigFiles.some(file =>
|
|
64
|
+
fs.existsSync(path.join(directory, file))
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Check for Frigg-specific directories
|
|
68
|
+
const friggDirs = [
|
|
69
|
+
'.frigg',
|
|
70
|
+
'frigg-modules',
|
|
71
|
+
'api-modules'
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
indicators.hasFriggDirectories = friggDirs.some(dir =>
|
|
75
|
+
fs.existsSync(path.join(directory, dir))
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Check for Frigg-specific scripts in package.json
|
|
79
|
+
indicators.hasFriggScripts = false;
|
|
80
|
+
if (packageJson.scripts) {
|
|
81
|
+
const friggScriptPatterns = ['frigg', 'frigg-dev', 'frigg-build', 'frigg-deploy'];
|
|
82
|
+
indicators.hasFriggScripts = Object.keys(packageJson.scripts).some(script =>
|
|
83
|
+
friggScriptPatterns.some(pattern => script.includes(pattern)) ||
|
|
84
|
+
Object.values(packageJson.scripts).some(cmd =>
|
|
85
|
+
typeof cmd === 'string' && cmd.includes('frigg ')
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check for workspace structure
|
|
91
|
+
if (packageJson.workspaces) {
|
|
92
|
+
const workspaces = Array.isArray(packageJson.workspaces)
|
|
93
|
+
? packageJson.workspaces
|
|
94
|
+
: packageJson.workspaces.packages || [];
|
|
95
|
+
|
|
96
|
+
indicators.hasBackendWorkspace = workspaces.some(ws =>
|
|
97
|
+
ws.includes('backend') || ws === 'backend'
|
|
98
|
+
);
|
|
99
|
+
indicators.hasFrontendWorkspace = workspaces.some(ws =>
|
|
100
|
+
ws.includes('frontend') || ws === 'frontend'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check for backend/serverless.yml
|
|
105
|
+
const serverlessPath = path.join(directory, 'backend', 'serverless.yml');
|
|
106
|
+
indicators.hasServerlessConfig = fs.existsSync(serverlessPath);
|
|
107
|
+
|
|
108
|
+
// Check for individual frontend directories (React, Vue, etc.)
|
|
109
|
+
const frontendDirs = ['frontend', 'react', 'vue', 'svelte', 'angular'];
|
|
110
|
+
const existingFrontendDirs = frontendDirs.filter(dir =>
|
|
111
|
+
fs.existsSync(path.join(directory, dir))
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Skip @friggframework packages (they're framework packages, not Frigg apps)
|
|
115
|
+
if (packageJson.name && packageJson.name.startsWith('@friggframework/')) {
|
|
116
|
+
return { isFriggRepo: false, repoInfo: null };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Additional check for Zapier apps that shouldn't be detected as Frigg repos
|
|
120
|
+
const isZapierApp = packageJson.name && (
|
|
121
|
+
packageJson.name.includes('zapier-public') ||
|
|
122
|
+
packageJson.name.includes('zapier-app') ||
|
|
123
|
+
(packageJson.scripts && packageJson.scripts.zapier)
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Check for specific Frigg indicators in serverless.yml
|
|
127
|
+
let hasFriggServerlessIndicators = false;
|
|
128
|
+
if (indicators.hasServerlessConfig) {
|
|
129
|
+
try {
|
|
130
|
+
const serverlessContent = fs.readFileSync(serverlessPath, 'utf8');
|
|
131
|
+
hasFriggServerlessIndicators = serverlessContent.includes('frigg') ||
|
|
132
|
+
serverlessContent.includes('FriggHandler') ||
|
|
133
|
+
serverlessContent.includes('@friggframework');
|
|
134
|
+
} catch (error) {
|
|
135
|
+
// Ignore read errors
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
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
|
+
}
|
|
163
|
+
|
|
164
|
+
// Additional validation for edge cases
|
|
165
|
+
if (isZapierApp && !indicators.hasFriggDependencies && !indicators.hasFriggConfig) {
|
|
166
|
+
return { isFriggRepo: false, repoInfo: null };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (isFriggRepo) {
|
|
170
|
+
return {
|
|
171
|
+
isFriggRepo: true,
|
|
172
|
+
repoInfo: {
|
|
173
|
+
name: packageJson.name || path.basename(directory),
|
|
174
|
+
path: directory,
|
|
175
|
+
version: packageJson.version,
|
|
176
|
+
framework: detectFramework(directory, existingFrontendDirs),
|
|
177
|
+
hasBackend: fs.existsSync(path.join(directory, 'backend')),
|
|
178
|
+
friggDependencies: indicators.friggDependencies,
|
|
179
|
+
workspaces: packageJson.workspaces,
|
|
180
|
+
hasFriggConfig: indicators.hasFriggConfig,
|
|
181
|
+
hasFriggDirectories: indicators.hasFriggDirectories,
|
|
182
|
+
isZapierApp: isZapierApp,
|
|
183
|
+
...indicators
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { isFriggRepo: false, repoInfo: null };
|
|
189
|
+
|
|
190
|
+
} catch (error) {
|
|
191
|
+
return { isFriggRepo: false, repoInfo: null };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Detects the frontend framework used in the Frigg repository
|
|
197
|
+
* @param {string} directory - Repository directory
|
|
198
|
+
* @param {string[]} existingFrontendDirs - List of existing frontend directories
|
|
199
|
+
* @returns {string} Framework name
|
|
200
|
+
*/
|
|
201
|
+
function detectFramework(directory, existingFrontendDirs) {
|
|
202
|
+
// Check for framework-specific directories
|
|
203
|
+
const frameworkDirs = {
|
|
204
|
+
'react': 'React',
|
|
205
|
+
'vue': 'Vue',
|
|
206
|
+
'svelte': 'Svelte',
|
|
207
|
+
'angular': 'Angular'
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
for (const dir of existingFrontendDirs) {
|
|
211
|
+
if (frameworkDirs[dir]) {
|
|
212
|
+
return frameworkDirs[dir];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Check frontend directory for framework indicators
|
|
217
|
+
const frontendPath = path.join(directory, 'frontend');
|
|
218
|
+
if (fs.existsSync(frontendPath)) {
|
|
219
|
+
try {
|
|
220
|
+
const frontendPackageJson = path.join(frontendPath, 'package.json');
|
|
221
|
+
if (fs.existsSync(frontendPackageJson)) {
|
|
222
|
+
const frontendPkg = fs.readJsonSync(frontendPackageJson);
|
|
223
|
+
const deps = { ...frontendPkg.dependencies, ...frontendPkg.devDependencies };
|
|
224
|
+
|
|
225
|
+
if (deps.react) return 'React';
|
|
226
|
+
if (deps.vue) return 'Vue';
|
|
227
|
+
if (deps.svelte) return 'Svelte';
|
|
228
|
+
if (deps['@angular/core']) return 'Angular';
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
// Ignore errors
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return 'Unknown';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Searches for Frigg repositories in common locations
|
|
240
|
+
* @param {Object} options - Search options
|
|
241
|
+
* @returns {Promise<Array>} Array of discovered repositories
|
|
242
|
+
*/
|
|
243
|
+
async function discoverFriggRepositories(options = {}) {
|
|
244
|
+
const {
|
|
245
|
+
searchPaths = [
|
|
246
|
+
process.cwd(),
|
|
247
|
+
path.join(os.homedir(), 'Documents'),
|
|
248
|
+
path.join(os.homedir(), 'Projects'),
|
|
249
|
+
path.join(os.homedir(), 'Development'),
|
|
250
|
+
path.join(os.homedir(), 'dev'),
|
|
251
|
+
path.join(os.homedir(), 'Code')
|
|
252
|
+
],
|
|
253
|
+
maxDepth = 3,
|
|
254
|
+
excludePatterns = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage']
|
|
255
|
+
} = options;
|
|
256
|
+
|
|
257
|
+
const discoveredRepos = [];
|
|
258
|
+
const visited = new Set();
|
|
259
|
+
|
|
260
|
+
for (const searchPath of searchPaths) {
|
|
261
|
+
if (fs.existsSync(searchPath)) {
|
|
262
|
+
await searchDirectory(searchPath, 0, maxDepth, excludePatterns, discoveredRepos, visited);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Remove duplicates and sort by name
|
|
267
|
+
const uniqueRepos = Array.from(
|
|
268
|
+
new Map(discoveredRepos.map(repo => [repo.path, repo])).values()
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
return uniqueRepos.sort((a, b) => a.name.localeCompare(b.name));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Recursively searches a directory for Frigg repositories
|
|
276
|
+
*/
|
|
277
|
+
async function searchDirectory(dirPath, currentDepth, maxDepth, excludePatterns, results, visited) {
|
|
278
|
+
// Avoid infinite loops from symlinks
|
|
279
|
+
const realPath = fs.realpathSync(dirPath);
|
|
280
|
+
if (visited.has(realPath)) return;
|
|
281
|
+
visited.add(realPath);
|
|
282
|
+
|
|
283
|
+
if (currentDepth > maxDepth) return;
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
// Check if current directory is a Frigg repo
|
|
287
|
+
const { isFriggRepo, repoInfo } = await isFriggRepository(dirPath);
|
|
288
|
+
if (isFriggRepo) {
|
|
289
|
+
results.push(repoInfo);
|
|
290
|
+
return; // Don't search inside Frigg repos
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Continue searching subdirectories
|
|
294
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
295
|
+
|
|
296
|
+
for (const entry of entries) {
|
|
297
|
+
if (entry.isDirectory()) {
|
|
298
|
+
const entryName = entry.name;
|
|
299
|
+
|
|
300
|
+
// Skip excluded patterns
|
|
301
|
+
if (excludePatterns.some(pattern => entryName.includes(pattern))) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Skip hidden directories except .git for workspace detection
|
|
306
|
+
if (entryName.startsWith('.') && entryName !== '.git') {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const entryPath = path.join(dirPath, entryName);
|
|
311
|
+
await searchDirectory(entryPath, currentDepth + 1, maxDepth, excludePatterns, results, visited);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
// Silently skip directories we can't access
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Gets the current directory's Frigg repository status
|
|
321
|
+
* @returns {Promise<Object>} Current repository info
|
|
322
|
+
*/
|
|
323
|
+
async function getCurrentRepositoryInfo() {
|
|
324
|
+
const currentDir = process.cwd();
|
|
325
|
+
|
|
326
|
+
// Check current directory
|
|
327
|
+
let { isFriggRepo, repoInfo } = await isFriggRepository(currentDir);
|
|
328
|
+
|
|
329
|
+
if (isFriggRepo) {
|
|
330
|
+
return { ...repoInfo, isCurrent: true };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Check parent directories up to 3 levels
|
|
334
|
+
let checkDir = currentDir;
|
|
335
|
+
for (let i = 0; i < 3; i++) {
|
|
336
|
+
const parentDir = path.dirname(checkDir);
|
|
337
|
+
if (parentDir === checkDir) break; // Reached root
|
|
338
|
+
|
|
339
|
+
const result = await isFriggRepository(parentDir);
|
|
340
|
+
if (result.isFriggRepo) {
|
|
341
|
+
return { ...result.repoInfo, isCurrent: false, currentSubPath: path.relative(parentDir, currentDir) };
|
|
342
|
+
}
|
|
343
|
+
checkDir = parentDir;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Prompts user to select a repository from discovered repos
|
|
351
|
+
* @param {Array} repositories - List of discovered repositories
|
|
352
|
+
* @returns {Promise<Object|null>} Selected repository or null
|
|
353
|
+
*/
|
|
354
|
+
async function promptRepositorySelection(repositories) {
|
|
355
|
+
if (repositories.length === 0) {
|
|
356
|
+
console.log(chalk.yellow('No Frigg repositories found.'));
|
|
357
|
+
console.log(chalk.gray('To create a new Frigg project, run: frigg init <project-name>'));
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (repositories.length === 1) {
|
|
362
|
+
console.log(chalk.green(`Found 1 Frigg repository: ${repositories[0].name}`));
|
|
363
|
+
return repositories[0];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
console.log(chalk.blue(`Found ${repositories.length} Frigg repositories:`));
|
|
367
|
+
console.log();
|
|
368
|
+
|
|
369
|
+
repositories.forEach((repo, index) => {
|
|
370
|
+
const framework = repo.framework !== 'Unknown' ? chalk.gray(`(${repo.framework})`) : '';
|
|
371
|
+
console.log(` ${chalk.cyan((index + 1).toString().padStart(2))}. ${chalk.white(repo.name)} ${framework}`);
|
|
372
|
+
console.log(` ${chalk.gray(repo.path)}`);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
console.log();
|
|
376
|
+
|
|
377
|
+
// For now, return the first one. In a full implementation, you'd use a prompt library
|
|
378
|
+
console.log(chalk.yellow('Auto-selecting first repository. Use interactive selection in future versions.'));
|
|
379
|
+
return repositories[0];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Formats repository information for display
|
|
384
|
+
* @param {Object} repoInfo - Repository information
|
|
385
|
+
* @returns {string} Formatted display string
|
|
386
|
+
*/
|
|
387
|
+
function formatRepositoryInfo(repoInfo) {
|
|
388
|
+
const parts = [
|
|
389
|
+
chalk.white(repoInfo.name),
|
|
390
|
+
repoInfo.version ? chalk.gray(`v${repoInfo.version}`) : '',
|
|
391
|
+
repoInfo.framework !== 'Unknown' ? chalk.blue(`[${repoInfo.framework}]`) : '',
|
|
392
|
+
repoInfo.hasBackend ? chalk.green('[Backend]') : ''
|
|
393
|
+
].filter(Boolean);
|
|
394
|
+
|
|
395
|
+
return parts.join(' ');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
module.exports = {
|
|
399
|
+
isFriggRepository,
|
|
400
|
+
discoverFriggRepositories,
|
|
401
|
+
getCurrentRepositoryInfo,
|
|
402
|
+
promptRepositorySelection,
|
|
403
|
+
formatRepositoryInfo,
|
|
404
|
+
detectFramework
|
|
405
|
+
};
|
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
|
+
const crypto = require('crypto');
|
|
3
4
|
const { composeServerlessDefinition } = require('./infrastructure-composer');
|
|
4
5
|
const { findNearestBackendPackageJson } = require('@friggframework/core');
|
|
5
6
|
|
|
7
|
+
// Filesystem-based cache to persist across osls require cache clears
|
|
8
|
+
const getCachePath = (backendDir) => {
|
|
9
|
+
return path.join(backendDir, '.frigg-infrastructure-cache.json');
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getLockPath = (backendDir) => {
|
|
13
|
+
return path.join(backendDir, '.frigg-infrastructure-lock');
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Check if process is still running
|
|
17
|
+
function isProcessRunning(pid) {
|
|
18
|
+
try {
|
|
19
|
+
process.kill(pid, 0);
|
|
20
|
+
return true;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
6
26
|
async function createFriggInfrastructure() {
|
|
7
27
|
const backendPath = findNearestBackendPackageJson();
|
|
8
28
|
if (!backendPath) {
|
|
@@ -15,20 +35,113 @@ async function createFriggInfrastructure() {
|
|
|
15
35
|
throw new Error('Could not find index.js');
|
|
16
36
|
}
|
|
17
37
|
|
|
18
|
-
const
|
|
19
|
-
const
|
|
38
|
+
const cachePath = getCachePath(backendDir);
|
|
39
|
+
const lockPath = getLockPath(backendDir);
|
|
40
|
+
|
|
41
|
+
// Check for cached infrastructure (filesystem-based for osls require cache clearing)
|
|
42
|
+
if (fs.existsSync(cachePath)) {
|
|
43
|
+
try {
|
|
44
|
+
const cached = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
|
|
45
|
+
// Verify cache is still valid (less than 60 seconds old)
|
|
46
|
+
if (Date.now() - cached.timestamp < 60000) {
|
|
47
|
+
console.log('✓ Using filesystem-cached infrastructure definition');
|
|
48
|
+
return cached.definition;
|
|
49
|
+
} else {
|
|
50
|
+
console.log('⚠️ Cache expired (> 60s), recomposing...');
|
|
51
|
+
fs.removeSync(cachePath);
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.log('⚠️ Invalid cache file, recomposing...', error.message);
|
|
55
|
+
fs.removeSync(cachePath);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check for active composition process
|
|
60
|
+
if (fs.existsSync(lockPath)) {
|
|
61
|
+
const lockPid = parseInt(fs.readFileSync(lockPath, 'utf-8').trim(), 10);
|
|
62
|
+
|
|
63
|
+
if (isProcessRunning(lockPid)) {
|
|
64
|
+
console.log(`⏳ Another composition (PID ${lockPid}) in progress - waiting...`);
|
|
65
|
+
|
|
66
|
+
// Wait up to 30 seconds for the other process to complete
|
|
67
|
+
for (let i = 0; i < 30; i++) {
|
|
68
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
20
69
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
70
|
+
// Check if cache was created by other process
|
|
71
|
+
if (fs.existsSync(cachePath)) {
|
|
72
|
+
try {
|
|
73
|
+
const cached = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
|
|
74
|
+
console.log('✓ Using infrastructure composed by concurrent process');
|
|
75
|
+
return cached.definition;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// Cache file corrupted, continue to compose ourselves
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
28
81
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
82
|
+
// Check if process died
|
|
83
|
+
if (!isProcessRunning(lockPid)) {
|
|
84
|
+
console.log(`⚠ Composition process ${lockPid} terminated - cleaning up stale lock`);
|
|
85
|
+
fs.removeSync(lockPath);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
// Stale lock file
|
|
91
|
+
console.log(`⚠️ Stale lock file detected (PID ${lockPid} not running) - cleaning up`);
|
|
92
|
+
fs.removeSync(lockPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Create lock file with current process PID
|
|
97
|
+
try {
|
|
98
|
+
fs.writeFileSync(lockPath, process.pid.toString());
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.warn('⚠ Could not create lock file:', error.message);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const backend = require(backendFilePath);
|
|
105
|
+
const appDefinition = backend.Definition;
|
|
106
|
+
|
|
107
|
+
const definition = await composeServerlessDefinition(
|
|
108
|
+
appDefinition,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Write cache to filesystem (persists across osls require cache clears)
|
|
112
|
+
try {
|
|
113
|
+
fs.writeFileSync(cachePath, JSON.stringify({
|
|
114
|
+
timestamp: Date.now(),
|
|
115
|
+
definition
|
|
116
|
+
}));
|
|
117
|
+
console.log('✓ Infrastructure definition cached to filesystem');
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.warn('⚠ Could not write cache file:', error.message);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return definition;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
// Clean up partial cache on error
|
|
125
|
+
if (fs.existsSync(cachePath)) {
|
|
126
|
+
try {
|
|
127
|
+
fs.removeSync(cachePath);
|
|
128
|
+
} catch (cleanupError) {
|
|
129
|
+
console.warn('⚠ Could not clean failed cache:', cleanupError.message);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.error('✗ Failed to compose infrastructure:', error.message);
|
|
134
|
+
throw error;
|
|
135
|
+
} finally {
|
|
136
|
+
// Always remove lock file when done (success or failure)
|
|
137
|
+
if (fs.existsSync(lockPath)) {
|
|
138
|
+
try {
|
|
139
|
+
fs.removeSync(lockPath);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.warn('⚠ Could not remove lock file:', error.message);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
32
145
|
}
|
|
33
146
|
|
|
34
147
|
module.exports = { createFriggInfrastructure };
|