@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 +3 -0
- package/CHANGELOG.md +117 -0
- package/README.md +80 -0
- package/frigg-cli/backendJs.js +33 -0
- package/frigg-cli/backendPath.js +26 -0
- package/frigg-cli/commitChanges.js +16 -0
- package/frigg-cli/environmentVariables.js +134 -0
- package/frigg-cli/environmentVariables.test.js +86 -0
- package/frigg-cli/index.js +14 -0
- package/frigg-cli/index.test.js +109 -0
- package/frigg-cli/installCommand.js +57 -0
- package/frigg-cli/installPackage.js +13 -0
- package/frigg-cli/integrationFile.js +30 -0
- package/frigg-cli/logger.js +12 -0
- package/frigg-cli/template.js +90 -0
- package/frigg-cli/validatePackage.js +79 -0
- package/index.js +2 -6
- package/package.json +16 -7
- package/{test-environment → test}/auther-definition-tester.js +9 -5
- package/test/index.js +11 -0
- package/test/mock-api-readme.md +102 -0
- package/test/mock-api.js +284 -0
- package/{test-environment → test}/mock-integration.js +10 -8
- package/migrations/README.md +0 -3
- package/migrations/bump3.txt +0 -0
- package/migrations/jest.config.js +0 -3
- package/test-environment/Authenticator.js +0 -74
- package/test-environment/index.js +0 -25
- /package/{test-environment → test}/auther-definition-method-tester.js +0 -0
- /package/{test-environment → test}/integration-validator.js +0 -0
package/.eslintrc.json
ADDED
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
|
+
});
|