@friggframework/devtools 2.0.0--canary.549.70ef06a.0 → 2.0.0--canary.545.ccb5010.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.
- package/frigg-cli/README.md +1 -1
- package/frigg-cli/__tests__/application/use-cases/AddApiModuleToIntegrationUseCase.test.js +326 -0
- package/frigg-cli/__tests__/application/use-cases/CreateApiModuleUseCase.test.js +337 -0
- package/frigg-cli/__tests__/domain/entities/ApiModule.test.js +373 -0
- package/frigg-cli/__tests__/domain/entities/AppDefinition.test.js +313 -0
- package/frigg-cli/__tests__/domain/services/IntegrationValidator.test.js +269 -0
- package/frigg-cli/__tests__/domain/value-objects/IntegrationName.test.js +82 -0
- package/frigg-cli/__tests__/infrastructure/adapters/IntegrationJsUpdater.test.js +408 -0
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemApiModuleRepository.test.js +583 -0
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemAppDefinitionRepository.test.js +314 -0
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemIntegrationRepository.test.js +383 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +1 -1
- package/frigg-cli/__tests__/unit/commands/doctor.test.js +0 -2
- package/frigg-cli/__tests__/unit/commands/init.test.js +406 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +23 -19
- package/frigg-cli/__tests__/unit/commands/provider-dispatch.test.js +383 -0
- package/frigg-cli/__tests__/unit/commands/repair.test.js +275 -0
- package/frigg-cli/__tests__/unit/dependencies.test.js +2 -2
- package/frigg-cli/__tests__/unit/start-command/application/RunPreflightChecksUseCase.test.js +411 -0
- package/frigg-cli/__tests__/unit/start-command/infrastructure/DatabaseAdapter.test.js +405 -0
- package/frigg-cli/__tests__/unit/start-command/infrastructure/DockerAdapter.test.js +496 -0
- package/frigg-cli/__tests__/unit/start-command/presentation/InteractivePromptAdapter.test.js +474 -0
- package/frigg-cli/__tests__/unit/utils/output.test.js +196 -0
- package/frigg-cli/application/use-cases/AddApiModuleToIntegrationUseCase.js +93 -0
- package/frigg-cli/application/use-cases/CreateApiModuleUseCase.js +93 -0
- package/frigg-cli/application/use-cases/CreateIntegrationUseCase.js +103 -0
- package/frigg-cli/build-command/index.js +123 -11
- package/frigg-cli/container.js +172 -0
- package/frigg-cli/deploy-command/index.js +83 -1
- package/frigg-cli/docs/OUTPUT_MIGRATION_GUIDE.md +286 -0
- package/frigg-cli/doctor-command/index.js +37 -16
- package/frigg-cli/domain/entities/ApiModule.js +272 -0
- package/frigg-cli/domain/entities/AppDefinition.js +227 -0
- package/frigg-cli/domain/entities/Integration.js +198 -0
- package/frigg-cli/domain/exceptions/DomainException.js +24 -0
- package/frigg-cli/domain/ports/IApiModuleRepository.js +53 -0
- package/frigg-cli/domain/ports/IAppDefinitionRepository.js +43 -0
- package/frigg-cli/domain/ports/IIntegrationRepository.js +61 -0
- package/frigg-cli/domain/services/IntegrationValidator.js +185 -0
- package/frigg-cli/domain/value-objects/IntegrationId.js +42 -0
- package/frigg-cli/domain/value-objects/IntegrationName.js +60 -0
- package/frigg-cli/domain/value-objects/SemanticVersion.js +70 -0
- package/frigg-cli/generate-iam-command.js +21 -1
- package/frigg-cli/index.js +21 -6
- package/frigg-cli/index.test.js +7 -2
- package/frigg-cli/infrastructure/UnitOfWork.js +46 -0
- package/frigg-cli/infrastructure/adapters/BackendJsUpdater.js +197 -0
- package/frigg-cli/infrastructure/adapters/FileSystemAdapter.js +224 -0
- package/frigg-cli/infrastructure/adapters/IntegrationJsUpdater.js +249 -0
- package/frigg-cli/infrastructure/adapters/SchemaValidator.js +92 -0
- package/frigg-cli/infrastructure/repositories/FileSystemApiModuleRepository.js +373 -0
- package/frigg-cli/infrastructure/repositories/FileSystemAppDefinitionRepository.js +116 -0
- package/frigg-cli/infrastructure/repositories/FileSystemIntegrationRepository.js +277 -0
- package/frigg-cli/init-command/backend-first-handler.js +124 -42
- package/frigg-cli/init-command/index.js +2 -1
- package/frigg-cli/init-command/template-handler.js +13 -3
- package/frigg-cli/install-command/backend-js.js +3 -3
- package/frigg-cli/install-command/environment-variables.js +16 -19
- package/frigg-cli/install-command/environment-variables.test.js +12 -13
- package/frigg-cli/install-command/index.js +14 -9
- package/frigg-cli/install-command/integration-file.js +3 -3
- package/frigg-cli/install-command/validate-package.js +5 -9
- package/frigg-cli/jest.config.js +4 -1
- package/frigg-cli/package-lock.json +16226 -0
- package/frigg-cli/repair-command/index.js +121 -128
- package/frigg-cli/start-command/application/RunPreflightChecksUseCase.js +376 -0
- package/frigg-cli/start-command/index.js +324 -2
- package/frigg-cli/start-command/infrastructure/DatabaseAdapter.js +591 -0
- package/frigg-cli/start-command/infrastructure/DockerAdapter.js +306 -0
- package/frigg-cli/start-command/presentation/InteractivePromptAdapter.js +329 -0
- package/frigg-cli/templates/backend/.env.example +62 -0
- package/frigg-cli/templates/backend/.eslintrc.json +12 -0
- package/frigg-cli/templates/backend/.prettierrc +6 -0
- package/frigg-cli/templates/backend/docker-compose.yml +22 -0
- package/frigg-cli/templates/backend/index.js +96 -0
- package/frigg-cli/templates/backend/infrastructure.js +12 -0
- package/frigg-cli/templates/backend/jest.config.js +17 -0
- package/frigg-cli/templates/backend/package.json +50 -0
- package/frigg-cli/templates/backend/src/api-modules/.gitkeep +10 -0
- package/frigg-cli/templates/backend/src/base/.gitkeep +7 -0
- package/frigg-cli/templates/backend/src/integrations/.gitkeep +10 -0
- package/frigg-cli/templates/backend/src/integrations/ExampleIntegration.js +65 -0
- package/frigg-cli/templates/backend/src/utils/.gitkeep +7 -0
- package/frigg-cli/templates/backend/test/setup.js +30 -0
- package/frigg-cli/templates/backend/ui-extensions/.gitkeep +0 -0
- package/frigg-cli/templates/backend/ui-extensions/README.md +77 -0
- package/frigg-cli/ui-command/index.js +58 -36
- package/frigg-cli/utils/__tests__/provider-helper.test.js +55 -0
- package/frigg-cli/utils/__tests__/repo-detection.test.js +436 -0
- package/frigg-cli/utils/output.js +382 -0
- package/frigg-cli/utils/provider-helper.js +75 -0
- package/frigg-cli/utils/repo-detection.js +85 -37
- package/frigg-cli/validate-command/__tests__/adapters/validate-command.test.js +205 -0
- package/frigg-cli/validate-command/__tests__/application/validate-app-use-case.test.js +104 -0
- package/frigg-cli/validate-command/__tests__/domain/fix-suggestion.test.js +153 -0
- package/frigg-cli/validate-command/__tests__/domain/validation-error.test.js +162 -0
- package/frigg-cli/validate-command/__tests__/domain/validation-result.test.js +152 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/api-module-validator.test.js +332 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/app-definition-validator.test.js +191 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/integration-class-validator.test.js +146 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/template-validation.test.js +155 -0
- package/frigg-cli/validate-command/adapters/cli/validate-command.js +199 -0
- package/frigg-cli/validate-command/application/use-cases/validate-app-use-case.js +35 -0
- package/frigg-cli/validate-command/domain/entities/validation-result.js +74 -0
- package/frigg-cli/validate-command/domain/value-objects/fix-suggestion.js +74 -0
- package/frigg-cli/validate-command/domain/value-objects/validation-error.js +68 -0
- package/frigg-cli/validate-command/infrastructure/validators/api-module-validator.js +181 -0
- package/frigg-cli/validate-command/infrastructure/validators/app-definition-validator.js +145 -0
- package/frigg-cli/validate-command/infrastructure/validators/integration-class-validator.js +113 -0
- package/infrastructure/create-frigg-infrastructure.js +93 -0
- package/infrastructure/docs/iam-policy-templates.md +1 -1
- package/infrastructure/domains/admin-scripts/admin-script-builder.js +200 -0
- package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +499 -0
- package/infrastructure/domains/admin-scripts/index.js +5 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +2 -4
- package/infrastructure/domains/networking/vpc-resolver.test.js +1 -1
- package/infrastructure/domains/shared/resource-discovery.js +5 -5
- package/infrastructure/domains/shared/types/app-definition.js +35 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +1 -1
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +10 -1
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +2 -2
- package/infrastructure/infrastructure-composer.js +2 -0
- package/infrastructure/infrastructure-composer.test.js +2 -2
- package/infrastructure/jest.config.js +16 -0
- package/management-ui/README.md +245 -109
- package/package.json +8 -7
- package/frigg-cli/install-command/logger.js +0 -12
|
@@ -207,7 +207,7 @@ describe('CLI Command: build', () => {
|
|
|
207
207
|
await buildCommand({ stage: 'dev' });
|
|
208
208
|
|
|
209
209
|
expect(consoleLogSpy).toHaveBeenCalledWith('Building the serverless application...');
|
|
210
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('
|
|
210
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('Packaging serverless application...');
|
|
211
211
|
});
|
|
212
212
|
|
|
213
213
|
it('should construct complete valid serverless command', async () => {
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
* Tests stack listing, selection, and health check orchestration
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { describe, test, expect, jest, beforeEach } = require('@jest/globals');
|
|
7
|
-
|
|
8
6
|
describe('Doctor Command - Stack Listing and Selection', () => {
|
|
9
7
|
let mockCloudFormationClient;
|
|
10
8
|
let mockSelect;
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the init command and BackendFirstHandler
|
|
3
|
+
* TDD: These tests define the expected behavior for frigg init
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
|
|
9
|
+
// Mock dependencies before requiring the modules
|
|
10
|
+
jest.mock('@inquirer/prompts', () => ({
|
|
11
|
+
select: jest.fn(),
|
|
12
|
+
confirm: jest.fn(),
|
|
13
|
+
multiselect: jest.fn()
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.mock('../../../utils/npm-registry', () => ({
|
|
17
|
+
searchApiModules: jest.fn().mockResolvedValue([]),
|
|
18
|
+
getModulesByType: jest.fn().mockResolvedValue({})
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
jest.mock('@friggframework/schemas', () => ({
|
|
22
|
+
validateAppDefinition: jest.fn().mockReturnValue({ valid: true, errors: [] }),
|
|
23
|
+
formatErrors: jest.fn().mockReturnValue('')
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
const { select, confirm, multiselect } = require('@inquirer/prompts');
|
|
27
|
+
const BackendFirstHandler = require('../../../init-command/backend-first-handler');
|
|
28
|
+
|
|
29
|
+
describe('BackendFirstHandler', () => {
|
|
30
|
+
let tempDir;
|
|
31
|
+
let targetPath;
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
// Create a real temporary directory for each test
|
|
35
|
+
tempDir = global.TestHelpers.createTempDir();
|
|
36
|
+
targetPath = path.join(tempDir, 'test-frigg-app');
|
|
37
|
+
|
|
38
|
+
// Reset all mocks
|
|
39
|
+
jest.clearAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(async () => {
|
|
43
|
+
// Clean up
|
|
44
|
+
global.TestHelpers.cleanupTempDir(tempDir);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('constructor', () => {
|
|
48
|
+
test('initializes with target path and options', () => {
|
|
49
|
+
const handler = new BackendFirstHandler(targetPath, { verbose: true });
|
|
50
|
+
|
|
51
|
+
expect(handler.targetPath).toBe(targetPath);
|
|
52
|
+
expect(handler.appName).toBe('test-frigg-app');
|
|
53
|
+
expect(handler.options.verbose).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('sets templates directory correctly', () => {
|
|
57
|
+
const handler = new BackendFirstHandler(targetPath);
|
|
58
|
+
|
|
59
|
+
expect(handler.templatesDir).toContain('templates');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('selectDeploymentMode', () => {
|
|
64
|
+
test('returns mode from options if provided', async () => {
|
|
65
|
+
const handler = new BackendFirstHandler(targetPath, { mode: 'standalone' });
|
|
66
|
+
|
|
67
|
+
const mode = await handler.selectDeploymentMode();
|
|
68
|
+
|
|
69
|
+
expect(mode).toBe('standalone');
|
|
70
|
+
expect(select).not.toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('prompts user if mode not provided', async () => {
|
|
74
|
+
select.mockResolvedValue('embedded');
|
|
75
|
+
const handler = new BackendFirstHandler(targetPath, {});
|
|
76
|
+
|
|
77
|
+
const mode = await handler.selectDeploymentMode();
|
|
78
|
+
|
|
79
|
+
expect(mode).toBe('embedded');
|
|
80
|
+
expect(select).toHaveBeenCalledWith(expect.objectContaining({
|
|
81
|
+
message: expect.stringContaining('deploy')
|
|
82
|
+
}));
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('getProjectConfiguration', () => {
|
|
87
|
+
test('collects all required configuration options', async () => {
|
|
88
|
+
const handler = new BackendFirstHandler(targetPath, { frontend: false });
|
|
89
|
+
|
|
90
|
+
// Mock all prompts in correct order
|
|
91
|
+
// 1. appPurpose
|
|
92
|
+
select.mockResolvedValueOnce('own-app');
|
|
93
|
+
// 2. needsCustomApiModule (only asked when appPurpose === 'own-app')
|
|
94
|
+
confirm.mockResolvedValueOnce(true);
|
|
95
|
+
// 3. includeIntegrations
|
|
96
|
+
confirm.mockResolvedValueOnce(false);
|
|
97
|
+
// 4. serverlessProvider (only for standalone)
|
|
98
|
+
select.mockResolvedValueOnce('aws');
|
|
99
|
+
// 5. installDependencies
|
|
100
|
+
confirm.mockResolvedValueOnce(true);
|
|
101
|
+
// 6. initializeGit
|
|
102
|
+
confirm.mockResolvedValueOnce(true);
|
|
103
|
+
|
|
104
|
+
const config = await handler.getProjectConfiguration('standalone');
|
|
105
|
+
|
|
106
|
+
expect(config.deploymentMode).toBe('standalone');
|
|
107
|
+
expect(config.appPurpose).toBe('own-app');
|
|
108
|
+
expect(config.needsCustomApiModule).toBe(true);
|
|
109
|
+
expect(config.installDependencies).toBe(true);
|
|
110
|
+
expect(config.initializeGit).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('asks about demo frontend when not disabled', async () => {
|
|
114
|
+
const handler = new BackendFirstHandler(targetPath, { frontend: undefined });
|
|
115
|
+
|
|
116
|
+
// Mock prompts in correct order:
|
|
117
|
+
// 1. appPurpose
|
|
118
|
+
select.mockResolvedValueOnce('exploring');
|
|
119
|
+
// 2. includeIntegrations
|
|
120
|
+
confirm.mockResolvedValueOnce(false);
|
|
121
|
+
// 3. includeDemoFrontend (asked when frontend !== false)
|
|
122
|
+
confirm.mockResolvedValueOnce(true);
|
|
123
|
+
// 4. frontendFramework (asked when includeDemoFrontend is true)
|
|
124
|
+
select.mockResolvedValueOnce('react');
|
|
125
|
+
// 5. demoAuthMode (asked when includeDemoFrontend is true)
|
|
126
|
+
select.mockResolvedValueOnce('mock');
|
|
127
|
+
// 6. serverlessProvider (for standalone mode)
|
|
128
|
+
select.mockResolvedValueOnce('local');
|
|
129
|
+
// 7. installDependencies
|
|
130
|
+
confirm.mockResolvedValueOnce(true);
|
|
131
|
+
// 8. initializeGit
|
|
132
|
+
confirm.mockResolvedValueOnce(true);
|
|
133
|
+
|
|
134
|
+
const config = await handler.getProjectConfiguration('standalone');
|
|
135
|
+
|
|
136
|
+
expect(config.includeDemoFrontend).toBe(true);
|
|
137
|
+
expect(config.frontendFramework).toBe('react');
|
|
138
|
+
expect(config.demoAuthMode).toBe('mock');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('skips demo frontend question when frontend is false', async () => {
|
|
142
|
+
const handler = new BackendFirstHandler(targetPath, { frontend: false });
|
|
143
|
+
|
|
144
|
+
select.mockResolvedValueOnce('exploring') // appPurpose
|
|
145
|
+
.mockResolvedValueOnce('local'); // serverlessProvider
|
|
146
|
+
confirm.mockResolvedValueOnce(false) // includeIntegrations
|
|
147
|
+
.mockResolvedValueOnce(true) // installDependencies
|
|
148
|
+
.mockResolvedValueOnce(true); // initializeGit
|
|
149
|
+
|
|
150
|
+
const config = await handler.getProjectConfiguration('standalone');
|
|
151
|
+
|
|
152
|
+
expect(config.includeDemoFrontend).toBeUndefined();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('createProject', () => {
|
|
157
|
+
test('creates target directory if it does not exist', async () => {
|
|
158
|
+
const handler = new BackendFirstHandler(targetPath, { force: true });
|
|
159
|
+
|
|
160
|
+
// Create minimal config
|
|
161
|
+
const config = {
|
|
162
|
+
deploymentMode: 'standalone',
|
|
163
|
+
installDependencies: false,
|
|
164
|
+
initializeGit: false,
|
|
165
|
+
serverlessProvider: 'local'
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Mock that templates exist
|
|
169
|
+
const templatesDir = handler.templatesDir;
|
|
170
|
+
const backendTemplateDir = path.join(templatesDir, 'backend');
|
|
171
|
+
|
|
172
|
+
// We expect the directory to be created
|
|
173
|
+
await handler.ensureSafeDirectory();
|
|
174
|
+
|
|
175
|
+
expect(fs.existsSync(targetPath)).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('throws error when directory is not empty without force flag', async () => {
|
|
179
|
+
// Create target directory with a file in it
|
|
180
|
+
await fs.ensureDir(targetPath);
|
|
181
|
+
await fs.writeFile(path.join(targetPath, 'existing-file.js'), 'content');
|
|
182
|
+
|
|
183
|
+
const handler = new BackendFirstHandler(targetPath, { force: false });
|
|
184
|
+
|
|
185
|
+
await expect(handler.ensureSafeDirectory())
|
|
186
|
+
.rejects
|
|
187
|
+
.toThrow('Directory not empty');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('allows non-empty directory with force flag', async () => {
|
|
191
|
+
// Create target directory with a file in it
|
|
192
|
+
await fs.ensureDir(targetPath);
|
|
193
|
+
await fs.writeFile(path.join(targetPath, 'existing-file.js'), 'content');
|
|
194
|
+
|
|
195
|
+
const handler = new BackendFirstHandler(targetPath, { force: true });
|
|
196
|
+
|
|
197
|
+
// Should not throw
|
|
198
|
+
await handler.ensureSafeDirectory();
|
|
199
|
+
|
|
200
|
+
expect(fs.existsSync(targetPath)).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('allows allowed files without force flag', async () => {
|
|
204
|
+
// Create target directory with allowed files
|
|
205
|
+
await fs.ensureDir(targetPath);
|
|
206
|
+
await fs.writeFile(path.join(targetPath, '.git'), '');
|
|
207
|
+
await fs.writeFile(path.join(targetPath, '.gitignore'), '');
|
|
208
|
+
await fs.writeFile(path.join(targetPath, 'README.md'), '');
|
|
209
|
+
|
|
210
|
+
const handler = new BackendFirstHandler(targetPath, { force: false });
|
|
211
|
+
|
|
212
|
+
// Should not throw for allowed files
|
|
213
|
+
await handler.ensureSafeDirectory();
|
|
214
|
+
|
|
215
|
+
expect(fs.existsSync(targetPath)).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('createStandaloneProject', () => {
|
|
220
|
+
// Note: These tests rely on the real backend template in templates/backend
|
|
221
|
+
// If the template doesn't exist, tests will be skipped
|
|
222
|
+
|
|
223
|
+
test('creates package.json with correct scripts', async () => {
|
|
224
|
+
const handler = new BackendFirstHandler(targetPath, { force: true });
|
|
225
|
+
await fs.ensureDir(targetPath);
|
|
226
|
+
|
|
227
|
+
const config = {
|
|
228
|
+
serverlessProvider: 'aws',
|
|
229
|
+
starterIntegrations: [],
|
|
230
|
+
installDependencies: false
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
await handler.createStandaloneProject(config);
|
|
234
|
+
|
|
235
|
+
const packageJson = await fs.readJSON(path.join(targetPath, 'package.json'));
|
|
236
|
+
|
|
237
|
+
expect(packageJson.name).toBe('test-frigg-app');
|
|
238
|
+
expect(packageJson.scripts).toHaveProperty('start');
|
|
239
|
+
expect(packageJson.scripts).toHaveProperty('build');
|
|
240
|
+
expect(packageJson.scripts).toHaveProperty('deploy');
|
|
241
|
+
expect(packageJson.scripts).toHaveProperty('test');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('adds selected integrations as dependencies', async () => {
|
|
245
|
+
const handler = new BackendFirstHandler(targetPath, { force: true });
|
|
246
|
+
await fs.ensureDir(targetPath);
|
|
247
|
+
|
|
248
|
+
const config = {
|
|
249
|
+
serverlessProvider: 'aws',
|
|
250
|
+
starterIntegrations: ['salesforce', 'hubspot'],
|
|
251
|
+
installDependencies: false
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
await handler.createStandaloneProject(config);
|
|
255
|
+
|
|
256
|
+
const packageJson = await fs.readJSON(path.join(targetPath, 'package.json'));
|
|
257
|
+
|
|
258
|
+
expect(packageJson.dependencies).toHaveProperty('@friggframework/api-module-salesforce');
|
|
259
|
+
expect(packageJson.dependencies).toHaveProperty('@friggframework/api-module-hubspot');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('includes @friggframework/core as dependency', async () => {
|
|
263
|
+
const handler = new BackendFirstHandler(targetPath, { force: true });
|
|
264
|
+
await fs.ensureDir(targetPath);
|
|
265
|
+
|
|
266
|
+
const config = {
|
|
267
|
+
serverlessProvider: 'local',
|
|
268
|
+
starterIntegrations: [],
|
|
269
|
+
installDependencies: false
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
await handler.createStandaloneProject(config);
|
|
273
|
+
|
|
274
|
+
const packageJson = await fs.readJSON(path.join(targetPath, 'package.json'));
|
|
275
|
+
|
|
276
|
+
expect(packageJson.dependencies).toHaveProperty('@friggframework/core');
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('createEmbeddedProject', () => {
|
|
281
|
+
// Note: These tests rely on the real backend template in templates/backend
|
|
282
|
+
// If the template doesn't exist, tests will be skipped
|
|
283
|
+
|
|
284
|
+
test('creates frigg-integration subdirectory', async () => {
|
|
285
|
+
const handler = new BackendFirstHandler(targetPath, { force: true });
|
|
286
|
+
await fs.ensureDir(targetPath);
|
|
287
|
+
|
|
288
|
+
const config = {
|
|
289
|
+
installDependencies: false
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
await handler.createEmbeddedProject(config);
|
|
293
|
+
|
|
294
|
+
const integrationDir = path.join(targetPath, 'frigg-integration');
|
|
295
|
+
expect(fs.existsSync(integrationDir)).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('creates FRIGG_INTEGRATION.md guide', async () => {
|
|
299
|
+
const handler = new BackendFirstHandler(targetPath, { force: true });
|
|
300
|
+
await fs.ensureDir(targetPath);
|
|
301
|
+
|
|
302
|
+
const config = {
|
|
303
|
+
installDependencies: false
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
await handler.createEmbeddedProject(config);
|
|
307
|
+
|
|
308
|
+
const guidePath = path.join(targetPath, 'FRIGG_INTEGRATION.md');
|
|
309
|
+
expect(fs.existsSync(guidePath)).toBe(true);
|
|
310
|
+
|
|
311
|
+
const content = await fs.readFile(guidePath, 'utf8');
|
|
312
|
+
expect(content).toContain('# Frigg Integration Guide');
|
|
313
|
+
expect(content).toContain('@friggframework/core');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('getIntegrationClassName', () => {
|
|
318
|
+
test('converts known integrations to class names', () => {
|
|
319
|
+
const handler = new BackendFirstHandler(targetPath);
|
|
320
|
+
|
|
321
|
+
expect(handler.getIntegrationClassName('salesforce')).toBe('SalesforceIntegration');
|
|
322
|
+
expect(handler.getIntegrationClassName('hubspot')).toBe('HubSpotIntegration');
|
|
323
|
+
expect(handler.getIntegrationClassName('slack')).toBe('SlackIntegration');
|
|
324
|
+
expect(handler.getIntegrationClassName('google-sheets')).toBe('GoogleSheetsIntegration');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('generates class name for unknown integrations', () => {
|
|
328
|
+
const handler = new BackendFirstHandler(targetPath);
|
|
329
|
+
|
|
330
|
+
expect(handler.getIntegrationClassName('custom-api')).toBe('Custom-apiIntegration');
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe('isUsingYarn', () => {
|
|
335
|
+
test('returns true when npm_config_user_agent contains yarn', () => {
|
|
336
|
+
const originalEnv = process.env.npm_config_user_agent;
|
|
337
|
+
process.env.npm_config_user_agent = 'yarn/1.22.0';
|
|
338
|
+
|
|
339
|
+
const handler = new BackendFirstHandler(targetPath);
|
|
340
|
+
|
|
341
|
+
expect(handler.isUsingYarn()).toBe(true);
|
|
342
|
+
|
|
343
|
+
process.env.npm_config_user_agent = originalEnv;
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test('returns false when using npm', () => {
|
|
347
|
+
const originalEnv = process.env.npm_config_user_agent;
|
|
348
|
+
process.env.npm_config_user_agent = 'npm/8.0.0';
|
|
349
|
+
|
|
350
|
+
const handler = new BackendFirstHandler(targetPath);
|
|
351
|
+
|
|
352
|
+
expect(handler.isUsingYarn()).toBe(false);
|
|
353
|
+
|
|
354
|
+
process.env.npm_config_user_agent = originalEnv;
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe('initCommand', () => {
|
|
360
|
+
const { initCommand } = require('../../../init-command');
|
|
361
|
+
let tempDir;
|
|
362
|
+
|
|
363
|
+
beforeEach(() => {
|
|
364
|
+
tempDir = global.TestHelpers.createTempDir();
|
|
365
|
+
jest.clearAllMocks();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
afterEach(() => {
|
|
369
|
+
global.TestHelpers.cleanupTempDir(tempDir);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test('validates project name - uppercase names are allowed in npm', async () => {
|
|
373
|
+
// Note: npm actually allows uppercase names now, they get lowercased
|
|
374
|
+
// The validate-npm-package-name package allows uppercase
|
|
375
|
+
const validName = path.join(tempDir, 'Valid-Name');
|
|
376
|
+
|
|
377
|
+
// Mock prompts to allow the command to proceed
|
|
378
|
+
select.mockResolvedValue('standalone');
|
|
379
|
+
confirm.mockResolvedValue(false);
|
|
380
|
+
|
|
381
|
+
// This should not throw for package name validation
|
|
382
|
+
// It may fail for other reasons like missing templates
|
|
383
|
+
try {
|
|
384
|
+
await initCommand(validName, { mode: 'standalone' });
|
|
385
|
+
} catch (e) {
|
|
386
|
+
// Expected to fail for missing template, not for name validation
|
|
387
|
+
expect(e.message).not.toContain('npm naming restrictions');
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('checks Node version', async () => {
|
|
392
|
+
const projectPath = path.join(tempDir, 'valid-project');
|
|
393
|
+
|
|
394
|
+
// Mock prompts to return quickly
|
|
395
|
+
select.mockResolvedValue('standalone');
|
|
396
|
+
confirm.mockResolvedValue(false);
|
|
397
|
+
|
|
398
|
+
// This should not throw for invalid Node version (just warn)
|
|
399
|
+
// The test validates checkNodeVersion is called
|
|
400
|
+
try {
|
|
401
|
+
await initCommand(projectPath, { mode: 'standalone' });
|
|
402
|
+
} catch (e) {
|
|
403
|
+
// May fail for other reasons, but shouldn't throw for Node version
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
});
|
|
@@ -26,7 +26,7 @@ jest.mock('../../../install-command/validate-package', () => ({
|
|
|
26
26
|
validatePackageExists: jest.fn(), // External: npm registry
|
|
27
27
|
searchAndSelectPackage: jest.fn() // External: interactive selection
|
|
28
28
|
}));
|
|
29
|
-
jest.mock('@friggframework/core', () => ({
|
|
29
|
+
jest.mock('@friggframework/core/utils', () => ({
|
|
30
30
|
findNearestBackendPackageJson: jest.fn(),
|
|
31
31
|
validateBackendPath: jest.fn()
|
|
32
32
|
}));
|
|
@@ -43,13 +43,13 @@ const { installPackage } = require('../../../install-command/install-package');
|
|
|
43
43
|
const { commitChanges } = require('../../../install-command/commit-changes');
|
|
44
44
|
const { handleEnvVariables } = require('../../../install-command/environment-variables');
|
|
45
45
|
const { validatePackageExists, searchAndSelectPackage } = require('../../../install-command/validate-package');
|
|
46
|
-
const { findNearestBackendPackageJson, validateBackendPath } = require('@friggframework/core');
|
|
46
|
+
const { findNearestBackendPackageJson, validateBackendPath } = require('@friggframework/core/utils');
|
|
47
47
|
const { installCommand } = require('../../../install-command');
|
|
48
|
+
const output = require('../../../utils/output');
|
|
48
49
|
|
|
49
50
|
describe('CLI Command: install', () => {
|
|
50
51
|
let processExitSpy;
|
|
51
|
-
|
|
52
|
-
let consoleErrorSpy;
|
|
52
|
+
|
|
53
53
|
const mockBackendPath = '/mock/backend/package.json';
|
|
54
54
|
const mockBackendDir = '/mock/backend';
|
|
55
55
|
|
|
@@ -59,9 +59,14 @@ describe('CLI Command: install', () => {
|
|
|
59
59
|
// Mock process.exit to prevent actual exit
|
|
60
60
|
processExitSpy = jest.spyOn(process, 'exit').mockImplementation();
|
|
61
61
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
// Mock output module
|
|
63
|
+
output.success = jest.fn();
|
|
64
|
+
output.error = jest.fn();
|
|
65
|
+
output.spinner = jest.fn().mockReturnValue({
|
|
66
|
+
start: jest.fn(),
|
|
67
|
+
succeed: jest.fn(),
|
|
68
|
+
fail: jest.fn()
|
|
69
|
+
});
|
|
65
70
|
|
|
66
71
|
// Setup fs-extra mocks - Let Frigg code run, just mock I/O
|
|
67
72
|
fs.ensureDirSync = jest.fn();
|
|
@@ -98,8 +103,7 @@ describe('CLI Command: install', () => {
|
|
|
98
103
|
|
|
99
104
|
afterEach(() => {
|
|
100
105
|
processExitSpy.mockRestore();
|
|
101
|
-
|
|
102
|
-
consoleErrorSpy.mockRestore();
|
|
106
|
+
|
|
103
107
|
jest.resetModules(); // Clear module cache after each test
|
|
104
108
|
});
|
|
105
109
|
|
|
@@ -205,9 +209,9 @@ describe('CLI Command: install', () => {
|
|
|
205
209
|
it('should log info messages during installation', async () => {
|
|
206
210
|
await installCommand('slack');
|
|
207
211
|
|
|
208
|
-
// Verify
|
|
209
|
-
expect(
|
|
210
|
-
expect.stringContaining('
|
|
212
|
+
// Verify output.spinner was used for installation progress
|
|
213
|
+
expect(output.spinner).toHaveBeenCalledWith(
|
|
214
|
+
expect.stringContaining('Installing integration for Slack')
|
|
211
215
|
);
|
|
212
216
|
});
|
|
213
217
|
|
|
@@ -334,8 +338,8 @@ describe('CLI Command: install', () => {
|
|
|
334
338
|
|
|
335
339
|
await installCommand('slack');
|
|
336
340
|
|
|
337
|
-
// Verify error logged via
|
|
338
|
-
expect(
|
|
341
|
+
// Verify error logged via output.error
|
|
342
|
+
expect(output.error).toHaveBeenCalledWith('An error occurred:', error);
|
|
339
343
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
340
344
|
});
|
|
341
345
|
|
|
@@ -345,7 +349,7 @@ describe('CLI Command: install', () => {
|
|
|
345
349
|
|
|
346
350
|
await installCommand('slack');
|
|
347
351
|
|
|
348
|
-
expect(
|
|
352
|
+
expect(output.error).toHaveBeenCalledWith('An error occurred:', error);
|
|
349
353
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
350
354
|
});
|
|
351
355
|
|
|
@@ -357,7 +361,7 @@ describe('CLI Command: install', () => {
|
|
|
357
361
|
|
|
358
362
|
await installCommand('slack');
|
|
359
363
|
|
|
360
|
-
expect(
|
|
364
|
+
expect(output.error).toHaveBeenCalledWith('An error occurred:', error);
|
|
361
365
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
362
366
|
});
|
|
363
367
|
|
|
@@ -370,7 +374,7 @@ describe('CLI Command: install', () => {
|
|
|
370
374
|
|
|
371
375
|
await installCommand('slack');
|
|
372
376
|
|
|
373
|
-
expect(
|
|
377
|
+
expect(output.error).toHaveBeenCalledWith('An error occurred:', expect.any(Error));
|
|
374
378
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
375
379
|
});
|
|
376
380
|
|
|
@@ -383,7 +387,7 @@ describe('CLI Command: install', () => {
|
|
|
383
387
|
|
|
384
388
|
await installCommand('slack');
|
|
385
389
|
|
|
386
|
-
expect(
|
|
390
|
+
expect(output.error).toHaveBeenCalledWith('An error occurred:', expect.any(Error));
|
|
387
391
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
388
392
|
});
|
|
389
393
|
|
|
@@ -393,7 +397,7 @@ describe('CLI Command: install', () => {
|
|
|
393
397
|
|
|
394
398
|
await installCommand('slack');
|
|
395
399
|
|
|
396
|
-
expect(
|
|
400
|
+
expect(output.error).toHaveBeenCalledWith('An error occurred:', error);
|
|
397
401
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
398
402
|
});
|
|
399
403
|
});
|