@friggframework/devtools 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.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 (127) 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/resource-discovery.js +5 -5
  118. package/infrastructure/domains/shared/types/app-definition.js +21 -0
  119. package/infrastructure/domains/shared/types/discovery-result.test.js +1 -1
  120. package/infrastructure/domains/shared/utilities/base-definition-factory.js +10 -1
  121. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +2 -2
  122. package/infrastructure/infrastructure-composer.js +2 -0
  123. package/infrastructure/infrastructure-composer.test.js +2 -2
  124. package/infrastructure/jest.config.js +16 -0
  125. package/management-ui/README.md +245 -109
  126. package/package.json +8 -7
  127. package/frigg-cli/install-command/logger.js +0 -12
@@ -0,0 +1,46 @@
1
+ /**
2
+ * UnitOfWork
3
+ * Coordinates transactions across repositories
4
+ */
5
+ class UnitOfWork {
6
+ constructor(fileSystemAdapter) {
7
+ this.fileSystemAdapter = fileSystemAdapter;
8
+ this.repositories = new Map();
9
+ }
10
+
11
+ /**
12
+ * Register a repository
13
+ */
14
+ registerRepository(name, repository) {
15
+ this.repositories.set(name, repository);
16
+ return this;
17
+ }
18
+
19
+ /**
20
+ * Commit all tracked operations
21
+ */
22
+ async commit() {
23
+ try {
24
+ await this.fileSystemAdapter.commit();
25
+ return {success: true};
26
+ } catch (error) {
27
+ throw new Error(`Failed to commit transaction: ${error.message}`);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Rollback all tracked operations
33
+ */
34
+ async rollback() {
35
+ return await this.fileSystemAdapter.rollback();
36
+ }
37
+
38
+ /**
39
+ * Clear tracked operations without commit/rollback
40
+ */
41
+ clear() {
42
+ this.fileSystemAdapter.clear();
43
+ }
44
+ }
45
+
46
+ module.exports = {UnitOfWork};
@@ -0,0 +1,197 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+
4
+ /**
5
+ * BackendJsUpdater
6
+ *
7
+ * Infrastructure service for updating backend.js file with new integrations
8
+ * Uses AST manipulation to safely add integration imports and registrations
9
+ */
10
+ class BackendJsUpdater {
11
+ constructor(fileSystemAdapter, backendPath) {
12
+ this.fileSystemAdapter = fileSystemAdapter;
13
+ this.backendPath = backendPath;
14
+ this.indexJsPath = path.join(backendPath, 'index.js');
15
+ }
16
+
17
+ /**
18
+ * Register an integration in backend/index.js
19
+ * @param {string} integrationName - kebab-case integration name
20
+ * @returns {Promise<void>}
21
+ */
22
+ async registerIntegration(integrationName) {
23
+ if (!await this.fileSystemAdapter.exists(this.indexJsPath)) {
24
+ throw new Error('backend/index.js not found');
25
+ }
26
+
27
+ const className = this._toClassName(integrationName);
28
+ const importPath = `./src/integrations/${className}Integration`;
29
+
30
+ await this.fileSystemAdapter.updateFile(this.indexJsPath, (content) => {
31
+ return this._addIntegration(content, className, integrationName, importPath);
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Remove an integration from backend/index.js
37
+ * @param {string} integrationName
38
+ * @returns {Promise<void>}
39
+ */
40
+ async unregisterIntegration(integrationName) {
41
+ if (!await this.fileSystemAdapter.exists(this.indexJsPath)) {
42
+ throw new Error('backend/index.js not found');
43
+ }
44
+
45
+ const className = this._toClassName(integrationName);
46
+
47
+ await this.fileSystemAdapter.updateFile(this.indexJsPath, (content) => {
48
+ return this._removeIntegration(content, className, integrationName);
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Add integration to backend.js content
54
+ * Simple string manipulation approach (can be replaced with AST parsing if needed)
55
+ *
56
+ * @param {string} content - Current backend.js content
57
+ * @param {string} className - Integration class name
58
+ * @param {string} integrationName - kebab-case name
59
+ * @param {string} importPath - relative import path
60
+ * @returns {string} - Updated content
61
+ */
62
+ _addIntegration(content, className, integrationName, importPath) {
63
+ // Check if integration is already registered
64
+ if (content.includes(`const ${className}Integration`)) {
65
+ console.warn(`Integration ${integrationName} is already registered in backend.js`);
66
+ return content;
67
+ }
68
+
69
+ let updated = content;
70
+
71
+ // 1. Add import statement after other integration imports
72
+ const importRegex = /(const \w+Integration = require\('\.\/src\/integrations\/[^']+'\);)/g;
73
+ const importMatches = [...content.matchAll(importRegex)];
74
+
75
+ if (importMatches.length > 0) {
76
+ // Add after last integration import
77
+ const lastImport = importMatches[importMatches.length - 1];
78
+ const insertIndex = lastImport.index + lastImport[0].length;
79
+ const importStatement = `\nconst ${className}Integration = require('${importPath}');`;
80
+ updated = updated.slice(0, insertIndex) + importStatement + updated.slice(insertIndex);
81
+ } else {
82
+ // No existing imports - add at the top after requires
83
+ const requiresRegex = /const .+ = require\([^)]+\);/g;
84
+ const requireMatches = [...content.matchAll(requiresRegex)];
85
+ if (requireMatches.length > 0) {
86
+ const lastRequire = requireMatches[requireMatches.length - 1];
87
+ const insertIndex = lastRequire.index + lastRequire[0].length;
88
+ const importStatement = `\n\n// Integrations\nconst ${className}Integration = require('${importPath}');`;
89
+ updated = updated.slice(0, insertIndex) + importStatement + updated.slice(insertIndex);
90
+ }
91
+ }
92
+
93
+ // 2. Add to integrations array
94
+ // Look for patterns:
95
+ // - const integrations = [...]
96
+ // - integrations: [...] (inside appDefinition object)
97
+
98
+ // Try standalone array first
99
+ const standaloneArrayRegex = /const integrations = \[([\s\S]*?)\];/;
100
+ let match = updated.match(standaloneArrayRegex);
101
+
102
+ if (match) {
103
+ const currentArray = match[1];
104
+ const newEntry = `\n ${className}Integration,`;
105
+
106
+ // Check if it's an empty array
107
+ if (currentArray.trim() === '') {
108
+ updated = updated.replace(standaloneArrayRegex, `const integrations = [${newEntry}\n];`);
109
+ } else {
110
+ // Add to existing array
111
+ const insertAt = match.index + match[0].length - 2; // Before ];
112
+ updated = updated.slice(0, insertAt) + ',' + newEntry + updated.slice(insertAt);
113
+ }
114
+ } else {
115
+ // Try appDefinition pattern
116
+ const appDefArrayRegex = /integrations:\s*\[([\s\S]*?)\]/;
117
+ match = updated.match(appDefArrayRegex);
118
+
119
+ if (match) {
120
+ const currentArray = match[1];
121
+ const newEntry = `\n ${className}Integration,`;
122
+
123
+ // Check if array is empty or has only comments
124
+ const hasOnlyComments = currentArray.trim() === '' ||
125
+ currentArray.trim().split('\n').every(line => line.trim().startsWith('//'));
126
+
127
+ if (hasOnlyComments) {
128
+ // Replace entire array content
129
+ updated = updated.replace(appDefArrayRegex, `integrations: [${newEntry}\n ]`);
130
+ } else {
131
+ // Add to existing array - find the last entry and add comma if needed
132
+ const lines = currentArray.split('\n');
133
+ const lastNonEmptyLine = lines.reverse().find(line => line.trim() && !line.trim().startsWith('//'));
134
+ const needsComma = lastNonEmptyLine && !lastNonEmptyLine.trim().endsWith(',');
135
+ const comma = needsComma ? ',' : '';
136
+
137
+ const insertAt = match.index + match[0].length - 1; // Before ]
138
+ updated = updated.slice(0, insertAt) + comma + newEntry + '\n ' + updated.slice(insertAt);
139
+ }
140
+ } else {
141
+ // No integrations array found - this is a problem
142
+ console.warn('Could not find integrations array in backend/index.js');
143
+ }
144
+ }
145
+
146
+ return updated;
147
+ }
148
+
149
+ /**
150
+ * Remove integration from backend.js content
151
+ *
152
+ * @param {string} content
153
+ * @param {string} className
154
+ * @param {string} integrationName
155
+ * @returns {string}
156
+ */
157
+ _removeIntegration(content, className, integrationName) {
158
+ let updated = content;
159
+
160
+ // 1. Remove import statement
161
+ const importRegex = new RegExp(`\\nconst ${className}Integration = require\\([^)]+\\);`, 'g');
162
+ updated = updated.replace(importRegex, '');
163
+
164
+ // 2. Remove from integrations array
165
+ const arrayEntryRegex = new RegExp(`,?\\s*${className}Integration,?`, 'g');
166
+ updated = updated.replace(arrayEntryRegex, '');
167
+
168
+ // Clean up extra commas
169
+ updated = updated.replace(/,\s*,/g, ',');
170
+ updated = updated.replace(/\[\s*,/g, '[');
171
+ updated = updated.replace(/,\s*\]/g, ']');
172
+
173
+ return updated;
174
+ }
175
+
176
+ /**
177
+ * Convert kebab-case to ClassName
178
+ * @param {string} kebabCase
179
+ * @returns {string}
180
+ */
181
+ _toClassName(kebabCase) {
182
+ return kebabCase
183
+ .split('-')
184
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
185
+ .join('');
186
+ }
187
+
188
+ /**
189
+ * Check if backend/index.js exists
190
+ * @returns {Promise<boolean>}
191
+ */
192
+ async exists() {
193
+ return await this.fileSystemAdapter.exists(this.indexJsPath);
194
+ }
195
+ }
196
+
197
+ module.exports = {BackendJsUpdater};
@@ -0,0 +1,224 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * FileSystemAdapter
6
+ * Low-level file system operations with atomic write/update and rollback support
7
+ */
8
+ class FileSystemAdapter {
9
+ constructor() {
10
+ this.operations = []; // Track operations for rollback
11
+ }
12
+
13
+ /**
14
+ * Write file atomically (temp file + rename)
15
+ */
16
+ async writeFile(filePath, content) {
17
+ const tempPath = `${filePath}.tmp.${Date.now()}`;
18
+
19
+ try {
20
+ await fs.writeFile(tempPath, content, 'utf-8');
21
+ await fs.rename(tempPath, filePath);
22
+
23
+ this.operations.push({
24
+ type: 'create',
25
+ path: filePath,
26
+ backup: null
27
+ });
28
+
29
+ return {success: true, path: filePath};
30
+ } catch (error) {
31
+ // Clean up temp file on error
32
+ if (await fs.pathExists(tempPath)) {
33
+ await fs.unlink(tempPath);
34
+ }
35
+ throw new Error(`Failed to write file ${filePath}: ${error.message}`);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Update file atomically (backup + write + rename)
41
+ */
42
+ async updateFile(filePath, updateFn) {
43
+ const backupPath = `${filePath}.backup.${Date.now()}`;
44
+
45
+ try {
46
+ // Create backup if file exists
47
+ if (await fs.pathExists(filePath)) {
48
+ await fs.copy(filePath, backupPath);
49
+ }
50
+
51
+ // Read current content
52
+ const currentContent = await fs.pathExists(filePath)
53
+ ? await fs.readFile(filePath, 'utf-8')
54
+ : '';
55
+
56
+ // Apply update function
57
+ const newContent = await updateFn(currentContent);
58
+
59
+ // Write to temp, then rename (atomic)
60
+ const tempPath = `${filePath}.tmp.${Date.now()}`;
61
+ await fs.writeFile(tempPath, newContent, 'utf-8');
62
+ await fs.rename(tempPath, filePath);
63
+
64
+ this.operations.push({
65
+ type: 'update',
66
+ path: filePath,
67
+ backup: backupPath
68
+ });
69
+
70
+ return {success: true, path: filePath};
71
+ } catch (error) {
72
+ // Restore from backup on error
73
+ if (await fs.pathExists(backupPath)) {
74
+ await fs.copy(backupPath, filePath);
75
+ }
76
+ throw new Error(`Failed to update file ${filePath}: ${error.message}`);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Read file content
82
+ */
83
+ async readFile(filePath) {
84
+ try {
85
+ return await fs.readFile(filePath, 'utf-8');
86
+ } catch (error) {
87
+ throw new Error(`Failed to read file ${filePath}: ${error.message}`);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Check if file or directory exists
93
+ */
94
+ async exists(filePath) {
95
+ return await fs.pathExists(filePath);
96
+ }
97
+
98
+ /**
99
+ * Ensure directory exists (create if needed)
100
+ */
101
+ async ensureDirectory(dirPath) {
102
+ if (!await fs.pathExists(dirPath)) {
103
+ await fs.ensureDir(dirPath);
104
+
105
+ this.operations.push({
106
+ type: 'mkdir',
107
+ path: dirPath,
108
+ backup: null
109
+ });
110
+ }
111
+
112
+ return {exists: true};
113
+ }
114
+
115
+ /**
116
+ * List directories in a path
117
+ */
118
+ async listDirectories(dirPath) {
119
+ if (!await fs.pathExists(dirPath)) {
120
+ return [];
121
+ }
122
+
123
+ const entries = await fs.readdir(dirPath, {withFileTypes: true});
124
+ return entries
125
+ .filter(entry => entry.isDirectory())
126
+ .map(entry => entry.name);
127
+ }
128
+
129
+ /**
130
+ * List files in a path (optionally with pattern)
131
+ */
132
+ async listFiles(dirPath, pattern = null) {
133
+ if (!await fs.pathExists(dirPath)) {
134
+ return [];
135
+ }
136
+
137
+ const entries = await fs.readdir(dirPath, {withFileTypes: true});
138
+ let files = entries
139
+ .filter(entry => entry.isFile())
140
+ .map(entry => entry.name);
141
+
142
+ // Apply pattern filter if provided
143
+ if (pattern) {
144
+ const regex = new RegExp(pattern.replace(/\*/g, '.*'));
145
+ files = files.filter(file => regex.test(file));
146
+ }
147
+
148
+ return files;
149
+ }
150
+
151
+
152
+ /**
153
+ * Rollback all tracked operations in reverse order
154
+ */
155
+ async rollback() {
156
+ const errors = [];
157
+
158
+ // Reverse order for rollback
159
+ for (const op of this.operations.reverse()) {
160
+ try {
161
+ switch (op.type) {
162
+ case 'create':
163
+ // Delete created file
164
+ if (await fs.pathExists(op.path)) {
165
+ await fs.unlink(op.path);
166
+ }
167
+ break;
168
+
169
+ case 'update':
170
+ // Restore from backup
171
+ if (op.backup && await fs.pathExists(op.backup)) {
172
+ await fs.copy(op.backup, op.path);
173
+ }
174
+ break;
175
+
176
+ case 'mkdir':
177
+ // Remove empty directory
178
+ if (await fs.pathExists(op.path)) {
179
+ const files = await fs.readdir(op.path);
180
+ if (files.length === 0) {
181
+ await fs.rmdir(op.path);
182
+ }
183
+ }
184
+ break;
185
+ }
186
+ } catch (error) {
187
+ errors.push({
188
+ operation: op,
189
+ error: error.message
190
+ });
191
+ }
192
+ }
193
+
194
+ this.operations = [];
195
+
196
+ return {
197
+ success: errors.length === 0,
198
+ errors
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Commit operations (clean up backups)
204
+ */
205
+ async commit() {
206
+ for (const op of this.operations) {
207
+ if (op.backup && await fs.pathExists(op.backup)) {
208
+ await fs.unlink(op.backup);
209
+ }
210
+ }
211
+
212
+ this.operations = [];
213
+ return {success: true};
214
+ }
215
+
216
+ /**
217
+ * Clear operation tracking without cleanup
218
+ */
219
+ clear() {
220
+ this.operations = [];
221
+ }
222
+ }
223
+
224
+ module.exports = {FileSystemAdapter};