@friggframework/devtools 1.2.0-canary.293.50b9cd8.0 → 1.2.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/.eslintrc.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "@friggframework/eslint-config"
3
+ }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,120 @@
1
+ # v1.2.0 (Tue Aug 06 2024)
2
+
3
+ #### 🚀 Enhancement
4
+
5
+ - CLI for Frigg - Install command for now [#322](https://github.com/friggframework/frigg/pull/322) ([@seanspeaks](https://github.com/seanspeaks))
6
+
7
+ #### 🐛 Bug Fix
8
+
9
+ - Add READMEs that will need updating, but for version releasing [#324](https://github.com/friggframework/frigg/pull/324) ([@seanspeaks](https://github.com/seanspeaks))
10
+ - Add READMEs that will need updating, but for version releasing ([@seanspeaks](https://github.com/seanspeaks))
11
+ - small update to integration testing / tooling [#304](https://github.com/friggframework/frigg/pull/304) ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
12
+ - Added missing dependencies ([@seanspeaks](https://github.com/seanspeaks))
13
+ - Added a missing dependency ([@seanspeaks](https://github.com/seanspeaks))
14
+ - Updated to handle envs properly, also further refactoring, and better templating. ([@seanspeaks](https://github.com/seanspeaks))
15
+ - WIP with help from Tabnine AI chat. ([@seanspeaks](https://github.com/seanspeaks))
16
+ - Bump version to: v1.1.8 \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
17
+ - use the factory methods for creating the mock integration so that everything is set up (mostly events and userActions) ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
18
+ - Bump version to: v1.1.5 \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
19
+
20
+ #### Authors: 2
21
+
22
+ - [@MichaelRyanWebber](https://github.com/MichaelRyanWebber)
23
+ - Sean Matthews ([@seanspeaks](https://github.com/seanspeaks))
24
+
25
+ ---
26
+
27
+ # v1.1.8 (Thu Jul 18 2024)
28
+
29
+ #### 🐛 Bug Fix
30
+
31
+ - Revert open to support commonjs [#319](https://github.com/friggframework/frigg/pull/319) ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
32
+ - Bump version to: v1.1.6 \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
33
+
34
+ #### Authors: 2
35
+
36
+ - [@MichaelRyanWebber](https://github.com/MichaelRyanWebber)
37
+ - Sean Matthews ([@seanspeaks](https://github.com/seanspeaks))
38
+
39
+ ---
40
+
41
+ # v1.1.7 (Mon Jul 15 2024)
42
+
43
+ #### 🐛 Bug Fix
44
+
45
+ - getAuthorizationRequirements() async [#318](https://github.com/friggframework/frigg/pull/318) ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
46
+ - await getAuthorizeRequirements() ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
47
+ - Bump version to: v1.1.6 \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
48
+
49
+ #### Authors: 2
50
+
51
+ - [@MichaelRyanWebber](https://github.com/MichaelRyanWebber)
52
+ - Sean Matthews ([@seanspeaks](https://github.com/seanspeaks))
53
+
54
+ ---
55
+
56
+ # v1.1.6 (Fri Apr 26 2024)
57
+
58
+ #### 🐛 Bug Fix
59
+
60
+ - Small fix to validation errors and cleanup [#307](https://github.com/friggframework/frigg/pull/307) ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
61
+ - also cleanup devtools since all versions will bump ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
62
+ - Bump version to: v1.1.5 \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
63
+
64
+ #### Authors: 2
65
+
66
+ - [@MichaelRyanWebber](https://github.com/MichaelRyanWebber)
67
+ - Sean Matthews ([@seanspeaks](https://github.com/seanspeaks))
68
+
69
+ ---
70
+
71
+ # v1.1.5 (Tue Apr 09 2024)
72
+
73
+ #### 🐛 Bug Fix
74
+
75
+ - update router to include options and refresh [#301](https://github.com/friggframework/frigg/pull/301) ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
76
+ - Bump version to: v1.1.4 \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
77
+ - Bump version to: v1.1.3 \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
78
+
79
+ #### Authors: 2
80
+
81
+ - [@MichaelRyanWebber](https://github.com/MichaelRyanWebber)
82
+ - Sean Matthews ([@seanspeaks](https://github.com/seanspeaks))
83
+
84
+ ---
85
+
86
+ # v2.0.0 (Sat Mar 30 2024)
87
+
88
+ #### 💥 Breaking Change
89
+
90
+ - revert HEAD to a corrected v1-connectwise release [#283](https://github.com/friggframework/frigg/pull/283) ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
91
+
92
+ #### 🚀 Enhancement
93
+
94
+ - Package redo [#294](https://github.com/friggframework/frigg/pull/294) ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
95
+
96
+ #### 🐛 Bug Fix
97
+
98
+ - delete all node_modules and regenerate lock file to potentially address known bug ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
99
+ - add back the direct dependency on eslint as this might be leading to a deploy issue ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
100
+ - create test, eslint-config and prettier-config packages as base shared dependencies ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
101
+ - slight fix to mock integration module instantiation [#290](https://github.com/friggframework/frigg/pull/290) ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
102
+ - slight fix to mock integration module instantiation ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
103
+ - Publish ([@seanspeaks](https://github.com/seanspeaks))
104
+ - Bump node and npm version for the whole repo (Fix CI) [#274](https://github.com/friggframework/frigg/pull/274) ([@seanspeaks](https://github.com/seanspeaks))
105
+ - Revert main to last release [#284](https://github.com/friggframework/frigg/pull/284) ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
106
+ - Revert "set test environment STAGE to dev" ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
107
+ - Add stage to test env [#282](https://github.com/friggframework/frigg/pull/282) ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
108
+ - set test environment STAGE to dev ([@MichaelRyanWebber](https://github.com/MichaelRyanWebber))
109
+ - Bump independent versions \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
110
+
111
+ #### Authors: 2
112
+
113
+ - [@MichaelRyanWebber](https://github.com/MichaelRyanWebber)
114
+ - Sean Matthews ([@seanspeaks](https://github.com/seanspeaks))
115
+
116
+ ---
117
+
1
118
  # v1.1.0 (Wed Mar 20 2024)
2
119
 
3
120
  #### 🚀 Enhancement
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # Frigg Framework Devtools
2
+
3
+ This package contains development tools and utilities for the Frigg Framework, an open-source serverless framework designed to simplify the development of integrations at scale.
4
+
5
+ ## Contents
6
+
7
+ The devtools package includes the following main components:
8
+
9
+ 1. Frigg CLI
10
+ 2. Migrations
11
+ 3. Test Utilities
12
+
13
+ ## Frigg CLI
14
+
15
+ The Frigg CLI is a command-line interface tool that helps developers manage and install API modules in their Frigg projects.
16
+
17
+ ### Key Features
18
+
19
+ - Install API modules
20
+ - Search for available API modules
21
+ - Automatically update project files
22
+ - Handle environment variables
23
+ - Validate package existence and backend paths
24
+
25
+ ### Usage
26
+
27
+ To use the Frigg CLI, run the following command:
28
+ ```sh
29
+ frigg install <api-module-name>
30
+ ```
31
+
32
+ This command will search for the specified API module, install it, and update your project accordingly.
33
+
34
+ ## Migrations
35
+
36
+ (Add information about migrations here if available)
37
+
38
+ ## Test Utilities
39
+
40
+ The test directory contains utilities to assist with testing in the Frigg Framework.
41
+
42
+ ### Key Features
43
+
44
+ - Integration validator (TODO: implementation details to be added)
45
+ - Mock API functionality
46
+
47
+ ### Mock API
48
+
49
+ The `mock-api.js` file provides functionality to mock API responses for testing purposes. It uses `nock` for HTTP request interception and includes features like:
50
+
51
+ - Caching of authentication tokens
52
+ - Recording and replaying of HTTP requests
53
+ - Jest test state management
54
+
55
+ Usage example:
56
+
57
+ ```javascript
58
+ const { mockApi } = require('@friggframework/devtools/test/mock-api');
59
+
60
+ // Use mockApi in your tests to simulate API responses
61
+ ```
62
+ ## Installation
63
+
64
+ To install the devtools package as a dev dependency, run:
65
+
66
+ ```
67
+ npm install --save-dev @friggframework/devtools
68
+ ```
69
+
70
+ ## Contributing
71
+
72
+ Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
73
+
74
+ ## License
75
+
76
+ This project is licensed under the MIT License - see the [LICENSE.md](../../LICENSE.md) file for details
77
+
78
+ ## Support
79
+
80
+ For support, please open an issue in the main Frigg Framework repository or contact the maintainers directly.
@@ -0,0 +1,33 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { logInfo } = require('./logger');
4
+ const INTEGRATIONS_DIR = 'src/integrations';
5
+ const BACKEND_JS = 'backend.js';
6
+
7
+ function updateBackendJsFile(backendPath, apiModuleName) {
8
+ const backendJsPath = path.join(path.dirname(backendPath), BACKEND_JS);
9
+ logInfo(`Updating backend.js: ${backendJsPath}`);
10
+ updateBackendJs(backendJsPath, apiModuleName);
11
+ }
12
+
13
+ function updateBackendJs(backendJsPath, apiModuleName) {
14
+ const backendJsContent = fs.readFileSync(backendJsPath, 'utf-8');
15
+ const importStatement = `const ${apiModuleName}Integration = require('./${INTEGRATIONS_DIR}/${apiModuleName}Integration');\n`;
16
+
17
+ if (!backendJsContent.includes(importStatement)) {
18
+ const updatedContent = backendJsContent.replace(
19
+ /(integrations\s*:\s*\[)([\s\S]*?)(\])/,
20
+ `$1\n ${apiModuleName}Integration,$2$3`
21
+ );
22
+ fs.writeFileSync(backendJsPath, importStatement + updatedContent);
23
+ } else {
24
+ logInfo(
25
+ `Import statement for ${apiModuleName}Integration already exists in backend.js`
26
+ );
27
+ }
28
+ }
29
+
30
+ module.exports = {
31
+ updateBackendJsFile,
32
+ updateBackendJs,
33
+ };
@@ -0,0 +1,26 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const PACKAGE_JSON = 'package.json';
4
+
5
+ function findNearestBackendPackageJson() {
6
+ let currentDir = process.cwd();
7
+ while (currentDir !== path.parse(currentDir).root) {
8
+ const packageJsonPath = path.join(currentDir, 'backend', PACKAGE_JSON);
9
+ if (fs.existsSync(packageJsonPath)) {
10
+ return packageJsonPath;
11
+ }
12
+ currentDir = path.dirname(currentDir);
13
+ }
14
+ return null;
15
+ }
16
+
17
+ function validateBackendPath(backendPath) {
18
+ if (!backendPath) {
19
+ throw new Error('Could not find a backend package.json file.');
20
+ }
21
+ }
22
+
23
+ module.exports = {
24
+ findNearestBackendPackageJson,
25
+ validateBackendPath,
26
+ };
@@ -0,0 +1,16 @@
1
+ const { execSync } = require('child_process');
2
+ const path = require('path');
3
+
4
+ function commitChanges(backendPath, apiModuleName) {
5
+ const apiModulePath = path.join(path.dirname(backendPath), 'src', 'integrations', `${apiModuleName}Integration.js`);
6
+ try {
7
+ execSync(`git add ${apiModulePath}`);
8
+ execSync(`git commit -m "Add ${apiModuleName}Integration to ${apiModuleName}Integration.js"`);
9
+ } catch (error) {
10
+ throw new Error('Failed to commit changes:', error);
11
+ }
12
+ }
13
+
14
+ module.exports = {
15
+ commitChanges,
16
+ };
@@ -0,0 +1,134 @@
1
+ const fs = require('fs');
2
+ const dotenv = require('dotenv');
3
+ const { readFileSync, writeFileSync, existsSync } = require('fs');
4
+ const { logInfo } = require('./logger');
5
+ const { resolve } = require('node:path');
6
+ const inquirer = require('inquirer');
7
+
8
+ const { parse } = require('@babel/parser');
9
+ const traverse = require('@babel/traverse').default;
10
+
11
+ const extractRawEnvVariables = (modulePath) => {
12
+ const filePath = resolve(modulePath, 'definition.js');
13
+
14
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
15
+ const ast = parse(fileContent, {
16
+ sourceType: 'module',
17
+ plugins: ['jsx', 'typescript'], // Add more plugins if needed
18
+ });
19
+
20
+ const envVariables = {};
21
+
22
+ traverse(ast, {
23
+ ObjectProperty(path) {
24
+ if (path.node.key.name === 'env') {
25
+ path.node.value.properties.forEach((prop) => {
26
+ const key = prop.key.name;
27
+ if (prop.value.type === 'MemberExpression') {
28
+ const property = prop.value.property.name;
29
+ envVariables[key] = `${property}`;
30
+ } else if (prop.value.type === 'TemplateLiteral') {
31
+ // Handle template literals
32
+ const expressions = prop.value.expressions.map((exp) =>
33
+ exp.type === 'MemberExpression'
34
+ ? `${exp.property.name}`
35
+ : exp.name
36
+ );
37
+ envVariables[key] = expressions.join('');
38
+ }
39
+ });
40
+ }
41
+ },
42
+ });
43
+
44
+ return envVariables;
45
+ };
46
+ const handleEnvVariables = async (backendPath, modulePath) => {
47
+ logInfo('Searching for missing environment variables...');
48
+ const Definition = { env: extractRawEnvVariables(modulePath) };
49
+ if (Definition && Definition.env) {
50
+ console.log('Here is Definition.env:', Definition.env);
51
+ const envVars = Object.values(Definition.env);
52
+
53
+ console.log(
54
+ 'Found the following environment variables in the API module:',
55
+ envVars
56
+ );
57
+
58
+ const localEnvPath = resolve(backendPath, '../.env');
59
+ const localDevConfigPath = resolve(
60
+ backendPath,
61
+ '../src/configs/dev.json'
62
+ );
63
+
64
+ // Load local .env variables
65
+ let localEnvVars = {};
66
+ if (existsSync(localEnvPath)) {
67
+ localEnvVars = dotenv.parse(readFileSync(localEnvPath, 'utf8'));
68
+ }
69
+
70
+ // Load local dev.json variables
71
+ let localDevConfig = {};
72
+ if (existsSync(localDevConfigPath)) {
73
+ localDevConfig = JSON.parse(
74
+ readFileSync(localDevConfigPath, 'utf8')
75
+ );
76
+ }
77
+
78
+ const missingEnvVars = envVars.filter(
79
+ (envVar) => !localEnvVars[envVar] && !localDevConfig[envVar]
80
+ );
81
+
82
+ logInfo(`Missing environment variables: ${missingEnvVars.join(', ')}`);
83
+
84
+ if (missingEnvVars.length > 0) {
85
+ const { addEnvVars } = await inquirer.prompt([
86
+ {
87
+ type: 'confirm',
88
+ name: 'addEnvVars',
89
+ message: `The following environment variables are required: ${missingEnvVars.join(
90
+ ', '
91
+ )}. Do you want to add them now?`,
92
+ },
93
+ ]);
94
+
95
+ if (addEnvVars) {
96
+ const envValues = {};
97
+ for (const envVar of missingEnvVars) {
98
+ const { value } = await inquirer.prompt([
99
+ {
100
+ type: 'input',
101
+ name: 'value',
102
+ message: `Enter value for ${envVar}:`,
103
+ },
104
+ ]);
105
+ envValues[envVar] = value;
106
+ }
107
+
108
+ // Add the envValues to the local .env file if it exists
109
+ if (existsSync(localEnvPath)) {
110
+ const envContent = Object.entries(envValues)
111
+ .map(([key, value]) => `${key}=${value}`)
112
+ .join('\n');
113
+ fs.appendFileSync(localEnvPath, `\n${envContent}`);
114
+ }
115
+
116
+ // Add the envValues to the local dev.json file if it exists
117
+ if (existsSync(localDevConfigPath)) {
118
+ const updatedDevConfig = {
119
+ ...localDevConfig,
120
+ ...envValues,
121
+ };
122
+ writeFileSync(
123
+ localDevConfigPath,
124
+ JSON.stringify(updatedDevConfig, null, 2)
125
+ );
126
+ }
127
+ } else {
128
+ logInfo("Edit whenever you're able, safe travels friend!");
129
+ }
130
+ }
131
+ }
132
+ };
133
+
134
+ module.exports = { handleEnvVariables };
@@ -0,0 +1,86 @@
1
+ const { handleEnvVariables } = require('./environmentVariables');
2
+ const { logInfo } = require('./logger');
3
+ const inquirer = require('inquirer');
4
+ const fs = require('fs');
5
+ const dotenv = require('dotenv');
6
+ const { resolve } = require('node:path');
7
+ const { parse } = require('@babel/parser');
8
+ const traverse = require('@babel/traverse');
9
+
10
+ jest.mock('inquirer');
11
+ jest.mock('fs');
12
+ jest.mock('dotenv');
13
+ jest.mock('./logger');
14
+ jest.mock('@babel/parser');
15
+ jest.mock('@babel/traverse');
16
+
17
+ describe('handleEnvVariables', () => {
18
+ const backendPath = '/mock/backend/path';
19
+ const modulePath = '/mock/module/path';
20
+
21
+ beforeEach(() => {
22
+ jest.clearAllMocks();
23
+ fs.readFileSync.mockReturnValue(`
24
+ const Definition = {
25
+ env: {
26
+ client_id: process.env.GOOGLE_CALENDAR_CLIENT_ID,
27
+ client_secret: process.env.GOOGLE_CALENDAR_CLIENT_SECRET,
28
+ redirect_uri: \`\${process.env.REDIRECT_URI}/google-calendar\`,
29
+ scope: process.env.GOOGLE_CALENDAR_SCOPE,
30
+ }
31
+ };
32
+ `);
33
+ parse.mockReturnValue({});
34
+ traverse.default.mockImplementation((ast, visitor) => {
35
+ visitor.ObjectProperty({
36
+ node: {
37
+ key: { name: 'env' },
38
+ value: {
39
+ properties: [
40
+ { key: { name: 'client_id' }, value: { type: 'MemberExpression', object: { name: 'process' }, property: { name: 'GOOGLE_CALENDAR_CLIENT_ID' } } },
41
+ { key: { name: 'client_secret' }, value: { type: 'MemberExpression', object: { name: 'process' }, property: { name: 'GOOGLE_CALENDAR_CLIENT_SECRET' } } },
42
+ { key: { name: 'redirect_uri' }, value: { type: 'MemberExpression', object: { name: 'process' }, property: { name: 'REDIRECT_URI' } } },
43
+ { key: { name: 'scope' }, value: { type: 'MemberExpression', object: { name: 'process' }, property: { name: 'GOOGLE_CALENDAR_SCOPE' } } },
44
+ ]
45
+ }
46
+ }
47
+ });
48
+ });
49
+ });
50
+
51
+ it('should identify and handle missing environment variables', async () => {
52
+ const localEnvPath = resolve(backendPath, '../.env');
53
+ const localDevConfigPath = resolve(backendPath, '../src/configs/dev.json');
54
+
55
+ fs.existsSync.mockImplementation((path) => path === localEnvPath || path === localDevConfigPath);
56
+ dotenv.parse.mockReturnValue({});
57
+ fs.readFileSync.mockImplementation((path) => {
58
+ if (path === resolve(modulePath, 'index.js')) return 'mock module content';
59
+ if (path === localEnvPath) return '';
60
+ if (path === localDevConfigPath) return '{}';
61
+ return '';
62
+ });
63
+
64
+ inquirer.prompt.mockResolvedValueOnce({ addEnvVars: true })
65
+ .mockResolvedValueOnce({ value: 'client_id_value' })
66
+ .mockResolvedValueOnce({ value: 'client_secret_value' })
67
+ .mockResolvedValueOnce({ value: 'redirect_uri_value' })
68
+ .mockResolvedValueOnce({ value: 'scope_value' });
69
+
70
+ await handleEnvVariables(backendPath, modulePath);
71
+
72
+ expect(logInfo).toHaveBeenCalledWith('Searching for missing environment variables...');
73
+ expect(logInfo).toHaveBeenCalledWith('Missing environment variables: GOOGLE_CALENDAR_CLIENT_ID, GOOGLE_CALENDAR_CLIENT_SECRET, REDIRECT_URI, GOOGLE_CALENDAR_SCOPE');
74
+ expect(inquirer.prompt).toHaveBeenCalledTimes(5);
75
+ expect(fs.appendFileSync).toHaveBeenCalledWith(localEnvPath, '\nGOOGLE_CALENDAR_CLIENT_ID=client_id_value\nGOOGLE_CALENDAR_CLIENT_SECRET=client_secret_value\nREDIRECT_URI=redirect_uri_value\nGOOGLE_CALENDAR_SCOPE=scope_value');
76
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
77
+ localDevConfigPath,
78
+ JSON.stringify({
79
+ GOOGLE_CALENDAR_CLIENT_ID: 'client_id_value',
80
+ GOOGLE_CALENDAR_CLIENT_SECRET: 'client_secret_value',
81
+ REDIRECT_URI: 'redirect_uri_value',
82
+ GOOGLE_CALENDAR_SCOPE: 'scope_value'
83
+ }, null, 2)
84
+ );
85
+ });
86
+ });
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const { installCommand } = require('./installCommand');
5
+
6
+ const program = new Command();
7
+ program
8
+ .command('install [apiModuleName]')
9
+ .description('Install an API module')
10
+ .action(installCommand);
11
+
12
+ program.parse(process.argv);
13
+
14
+ module.exports = { installCommand };
@@ -0,0 +1,109 @@
1
+ const { Command } = require('commander');
2
+ const { installCommand } = require('./index');
3
+ const { validatePackageExists } = require('./validatePackage');
4
+ const { findNearestBackendPackageJson, validateBackendPath } = require('./backendPath');
5
+ const { installPackage } = require('./installPackage');
6
+ const { createIntegrationFile } = require('./integrationFile');
7
+ const { updateBackendJsFile } = require('./backendJs');
8
+ const { commitChanges } = require('./commitChanges');
9
+ const { logInfo, logError } = require('./logger');
10
+
11
+ describe('CLI Command Tests', () => {
12
+ it('should successfully install an API module when all steps complete without errors', async () => {
13
+ const mockApiModuleName = 'testModule';
14
+ const mockPackageName = `@friggframework/api-module-${mockApiModuleName}`;
15
+ const mockBackendPath = '/mock/backend/path';
16
+
17
+ jest.mock('./validatePackage', () => ({
18
+ validatePackageExists: jest.fn().mockResolvedValue(true),
19
+ }));
20
+ jest.mock('./backendPath', () => ({
21
+ findNearestBackendPackageJson: jest.fn().mockReturnValue(mockBackendPath),
22
+ validateBackendPath: jest.fn().mockReturnValue(true),
23
+ }));
24
+ jest.mock('./installPackage', () => ({
25
+ installPackage: jest.fn().mockReturnValue(true),
26
+ }));
27
+ jest.mock('./integrationFile', () => ({
28
+ createIntegrationFile: jest.fn().mockReturnValue(true),
29
+ }));
30
+ jest.mock('./backendJs', () => ({
31
+ updateBackendJsFile: jest.fn().mockReturnValue(true),
32
+ }));
33
+ jest.mock('./commitChanges', () => ({
34
+ commitChanges: jest.fn().mockReturnValue(true),
35
+ }));
36
+ jest.mock('./logger', () => ({
37
+ logInfo: jest.fn(),
38
+ logError: jest.fn(),
39
+ }));
40
+
41
+ const program = new Command();
42
+ program
43
+ .command('install <apiModuleName>')
44
+ .description('Install an API module')
45
+ .action(installCommand);
46
+
47
+ await program.parseAsync(['node', 'install', mockApiModuleName]);
48
+
49
+ expect(validatePackageExists).toHaveBeenCalledWith(mockPackageName);
50
+ expect(findNearestBackendPackageJson).toHaveBeenCalled();
51
+ expect(validateBackendPath).toHaveBeenCalledWith(mockBackendPath);
52
+ expect(installPackage).toHaveBeenCalledWith(mockBackendPath, mockPackageName);
53
+ expect(createIntegrationFile).toHaveBeenCalledWith(mockBackendPath, mockApiModuleName);
54
+ expect(updateBackendJsFile).toHaveBeenCalledWith(mockBackendPath, mockApiModuleName);
55
+ expect(commitChanges).toHaveBeenCalledWith(mockBackendPath, mockApiModuleName);
56
+ expect(logInfo).toHaveBeenCalledWith(`Successfully installed ${mockPackageName} and updated the project.`);
57
+ });
58
+
59
+ it('should log an error and exit with code 1 if the package does not exist', async () => {
60
+ const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
61
+ const mockLogError = jest.spyOn(require('./logger'), 'logError').mockImplementation(() => {});
62
+ const mockValidatePackageExists = jest.spyOn(require('./validatePackage'), 'validatePackageExists').mockImplementation(() => {
63
+ throw new Error('Package not found');
64
+ });
65
+
66
+ const program = new Command();
67
+ program
68
+ .command('install <apiModuleName>')
69
+ .description('Install an API module')
70
+ .action(installCommand);
71
+
72
+ await program.parseAsync(['node', 'install', 'nonexistent-package']);
73
+
74
+ expect(mockValidatePackageExists).toHaveBeenCalledWith('@friggframework/api-module-nonexistent-package');
75
+ expect(mockLogError).toHaveBeenCalledWith('An error occurred:', expect.any(Error));
76
+ expect(mockExit).toHaveBeenCalledWith(1);
77
+
78
+ mockExit.mockRestore();
79
+ mockLogError.mockRestore();
80
+ mockValidatePackageExists.mockRestore();
81
+ });
82
+
83
+ it('should log an error and exit with code 1 if the backend path is invalid', async () => {
84
+ const mockLogError = jest.spyOn(require('./logger'), 'logError').mockImplementation(() => {});
85
+ const mockProcessExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
86
+ const mockValidatePackageExists = jest.spyOn(require('./validatePackage'), 'validatePackageExists').mockResolvedValue(true);
87
+ const mockFindNearestBackendPackageJson = jest.spyOn(require('./backendPath'), 'findNearestBackendPackageJson').mockReturnValue('/invalid/path');
88
+ const mockValidateBackendPath = jest.spyOn(require('./backendPath'), 'validateBackendPath').mockImplementation(() => {
89
+ throw new Error('Invalid backend path');
90
+ });
91
+
92
+ const program = new Command();
93
+ program
94
+ .command('install <apiModuleName>')
95
+ .description('Install an API module')
96
+ .action(installCommand);
97
+
98
+ await program.parseAsync(['node', 'install', 'test-module']);
99
+
100
+ expect(mockLogError).toHaveBeenCalledWith('An error occurred:', expect.any(Error));
101
+ expect(mockProcessExit).toHaveBeenCalledWith(1);
102
+
103
+ mockLogError.mockRestore();
104
+ mockProcessExit.mockRestore();
105
+ mockValidatePackageExists.mockRestore();
106
+ mockFindNearestBackendPackageJson.mockRestore();
107
+ mockValidateBackendPath.mockRestore();
108
+ });
109
+ });