-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #322 from friggframework/FRI-418_Explore-ability-t…
…o-have-a-CLI-as-part-of-devtools-package CLI for Frigg - Install command for now
- Loading branch information
Showing
15 changed files
with
1,095 additions
and
79 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
const fs = require('fs-extra'); | ||
const path = require('path'); | ||
const { logInfo } = require('./logger'); | ||
const INTEGRATIONS_DIR = 'src/integrations'; | ||
const BACKEND_JS = 'backend.js'; | ||
|
||
function updateBackendJsFile(backendPath, apiModuleName) { | ||
const backendJsPath = path.join(path.dirname(backendPath), BACKEND_JS); | ||
logInfo(`Updating backend.js: ${backendJsPath}`); | ||
updateBackendJs(backendJsPath, apiModuleName); | ||
} | ||
|
||
function updateBackendJs(backendJsPath, apiModuleName) { | ||
const backendJsContent = fs.readFileSync(backendJsPath, 'utf-8'); | ||
const importStatement = `const ${apiModuleName}Integration = require('./${INTEGRATIONS_DIR}/${apiModuleName}Integration');\n`; | ||
|
||
if (!backendJsContent.includes(importStatement)) { | ||
const updatedContent = backendJsContent.replace( | ||
/(integrations\s*:\s*\[)([\s\S]*?)(\])/, | ||
`$1\n ${apiModuleName}Integration,$2$3` | ||
); | ||
fs.writeFileSync(backendJsPath, importStatement + updatedContent); | ||
} else { | ||
logInfo( | ||
`Import statement for ${apiModuleName}Integration already exists in backend.js` | ||
); | ||
} | ||
} | ||
|
||
module.exports = { | ||
updateBackendJsFile, | ||
updateBackendJs, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
const fs = require('fs-extra'); | ||
const path = require('path'); | ||
const PACKAGE_JSON = 'package.json'; | ||
|
||
function findNearestBackendPackageJson() { | ||
let currentDir = process.cwd(); | ||
while (currentDir !== path.parse(currentDir).root) { | ||
const packageJsonPath = path.join(currentDir, 'backend', PACKAGE_JSON); | ||
if (fs.existsSync(packageJsonPath)) { | ||
return packageJsonPath; | ||
} | ||
currentDir = path.dirname(currentDir); | ||
} | ||
return null; | ||
} | ||
|
||
function validateBackendPath(backendPath) { | ||
if (!backendPath) { | ||
throw new Error('Could not find a backend package.json file.'); | ||
} | ||
} | ||
|
||
module.exports = { | ||
findNearestBackendPackageJson, | ||
validateBackendPath, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const { execSync } = require('child_process'); | ||
const path = require('path'); | ||
|
||
function commitChanges(backendPath, apiModuleName) { | ||
const apiModulePath = path.join(path.dirname(backendPath), 'src', 'integrations', `${apiModuleName}Integration.js`); | ||
try { | ||
execSync(`git add ${apiModulePath}`); | ||
execSync(`git commit -m "Add ${apiModuleName}Integration to ${apiModuleName}Integration.js"`); | ||
} catch (error) { | ||
throw new Error('Failed to commit changes:', error); | ||
} | ||
} | ||
|
||
module.exports = { | ||
commitChanges, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
const fs = require('fs'); | ||
const dotenv = require('dotenv'); | ||
const { readFileSync, writeFileSync, existsSync } = require('fs'); | ||
const { logInfo } = require('./logger'); | ||
const { resolve } = require('node:path'); | ||
const inquirer = require('inquirer'); | ||
|
||
const { parse } = require('@babel/parser'); | ||
const traverse = require('@babel/traverse').default; | ||
|
||
const extractRawEnvVariables = (modulePath) => { | ||
const filePath = resolve(modulePath, 'definition.js'); | ||
|
||
const fileContent = fs.readFileSync(filePath, 'utf-8'); | ||
const ast = parse(fileContent, { | ||
sourceType: 'module', | ||
plugins: ['jsx', 'typescript'], // Add more plugins if needed | ||
}); | ||
|
||
const envVariables = {}; | ||
|
||
traverse(ast, { | ||
ObjectProperty(path) { | ||
if (path.node.key.name === 'env') { | ||
path.node.value.properties.forEach((prop) => { | ||
const key = prop.key.name; | ||
if (prop.value.type === 'MemberExpression') { | ||
const property = prop.value.property.name; | ||
envVariables[key] = `${property}`; | ||
} else if (prop.value.type === 'TemplateLiteral') { | ||
// Handle template literals | ||
const expressions = prop.value.expressions.map((exp) => | ||
exp.type === 'MemberExpression' | ||
? `${exp.property.name}` | ||
: exp.name | ||
); | ||
envVariables[key] = expressions.join(''); | ||
} | ||
}); | ||
} | ||
}, | ||
}); | ||
|
||
return envVariables; | ||
}; | ||
const handleEnvVariables = async (backendPath, modulePath) => { | ||
logInfo('Searching for missing environment variables...'); | ||
const Definition = { env: extractRawEnvVariables(modulePath) }; | ||
if (Definition && Definition.env) { | ||
console.log('Here is Definition.env:', Definition.env); | ||
const envVars = Object.values(Definition.env); | ||
|
||
console.log( | ||
'Found the following environment variables in the API module:', | ||
envVars | ||
); | ||
|
||
const localEnvPath = resolve(backendPath, '../.env'); | ||
const localDevConfigPath = resolve( | ||
backendPath, | ||
'../src/configs/dev.json' | ||
); | ||
|
||
// Load local .env variables | ||
let localEnvVars = {}; | ||
if (existsSync(localEnvPath)) { | ||
localEnvVars = dotenv.parse(readFileSync(localEnvPath, 'utf8')); | ||
} | ||
|
||
// Load local dev.json variables | ||
let localDevConfig = {}; | ||
if (existsSync(localDevConfigPath)) { | ||
localDevConfig = JSON.parse( | ||
readFileSync(localDevConfigPath, 'utf8') | ||
); | ||
} | ||
|
||
const missingEnvVars = envVars.filter( | ||
(envVar) => !localEnvVars[envVar] && !localDevConfig[envVar] | ||
); | ||
|
||
logInfo(`Missing environment variables: ${missingEnvVars.join(', ')}`); | ||
|
||
if (missingEnvVars.length > 0) { | ||
const { addEnvVars } = await inquirer.prompt([ | ||
{ | ||
type: 'confirm', | ||
name: 'addEnvVars', | ||
message: `The following environment variables are required: ${missingEnvVars.join( | ||
', ' | ||
)}. Do you want to add them now?`, | ||
}, | ||
]); | ||
|
||
if (addEnvVars) { | ||
const envValues = {}; | ||
for (const envVar of missingEnvVars) { | ||
const { value } = await inquirer.prompt([ | ||
{ | ||
type: 'input', | ||
name: 'value', | ||
message: `Enter value for ${envVar}:`, | ||
}, | ||
]); | ||
envValues[envVar] = value; | ||
} | ||
|
||
// Add the envValues to the local .env file if it exists | ||
if (existsSync(localEnvPath)) { | ||
const envContent = Object.entries(envValues) | ||
.map(([key, value]) => `${key}=${value}`) | ||
.join('\n'); | ||
fs.appendFileSync(localEnvPath, `\n${envContent}`); | ||
} | ||
|
||
// Add the envValues to the local dev.json file if it exists | ||
if (existsSync(localDevConfigPath)) { | ||
const updatedDevConfig = { | ||
...localDevConfig, | ||
...envValues, | ||
}; | ||
writeFileSync( | ||
localDevConfigPath, | ||
JSON.stringify(updatedDevConfig, null, 2) | ||
); | ||
} | ||
} else { | ||
logInfo("Edit whenever you're able, safe travels friend!"); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
module.exports = { handleEnvVariables }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
const { handleEnvVariables } = require('./environmentVariables'); | ||
const { logInfo } = require('./logger'); | ||
const inquirer = require('inquirer'); | ||
const fs = require('fs'); | ||
const dotenv = require('dotenv'); | ||
const { resolve } = require('node:path'); | ||
const { parse } = require('@babel/parser'); | ||
const traverse = require('@babel/traverse'); | ||
|
||
jest.mock('inquirer'); | ||
jest.mock('fs'); | ||
jest.mock('dotenv'); | ||
jest.mock('./logger'); | ||
jest.mock('@babel/parser'); | ||
jest.mock('@babel/traverse'); | ||
|
||
describe('handleEnvVariables', () => { | ||
const backendPath = '/mock/backend/path'; | ||
const modulePath = '/mock/module/path'; | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
fs.readFileSync.mockReturnValue(` | ||
const Definition = { | ||
env: { | ||
client_id: process.env.GOOGLE_CALENDAR_CLIENT_ID, | ||
client_secret: process.env.GOOGLE_CALENDAR_CLIENT_SECRET, | ||
redirect_uri: \`\${process.env.REDIRECT_URI}/google-calendar\`, | ||
scope: process.env.GOOGLE_CALENDAR_SCOPE, | ||
} | ||
}; | ||
`); | ||
parse.mockReturnValue({}); | ||
traverse.default.mockImplementation((ast, visitor) => { | ||
visitor.ObjectProperty({ | ||
node: { | ||
key: { name: 'env' }, | ||
value: { | ||
properties: [ | ||
{ key: { name: 'client_id' }, value: { type: 'MemberExpression', object: { name: 'process' }, property: { name: 'GOOGLE_CALENDAR_CLIENT_ID' } } }, | ||
{ key: { name: 'client_secret' }, value: { type: 'MemberExpression', object: { name: 'process' }, property: { name: 'GOOGLE_CALENDAR_CLIENT_SECRET' } } }, | ||
{ key: { name: 'redirect_uri' }, value: { type: 'MemberExpression', object: { name: 'process' }, property: { name: 'REDIRECT_URI' } } }, | ||
{ key: { name: 'scope' }, value: { type: 'MemberExpression', object: { name: 'process' }, property: { name: 'GOOGLE_CALENDAR_SCOPE' } } }, | ||
] | ||
} | ||
} | ||
}); | ||
}); | ||
}); | ||
|
||
it('should identify and handle missing environment variables', async () => { | ||
const localEnvPath = resolve(backendPath, '../.env'); | ||
const localDevConfigPath = resolve(backendPath, '../src/configs/dev.json'); | ||
|
||
fs.existsSync.mockImplementation((path) => path === localEnvPath || path === localDevConfigPath); | ||
dotenv.parse.mockReturnValue({}); | ||
fs.readFileSync.mockImplementation((path) => { | ||
if (path === resolve(modulePath, 'index.js')) return 'mock module content'; | ||
if (path === localEnvPath) return ''; | ||
if (path === localDevConfigPath) return '{}'; | ||
return ''; | ||
}); | ||
|
||
inquirer.prompt.mockResolvedValueOnce({ addEnvVars: true }) | ||
.mockResolvedValueOnce({ value: 'client_id_value' }) | ||
.mockResolvedValueOnce({ value: 'client_secret_value' }) | ||
.mockResolvedValueOnce({ value: 'redirect_uri_value' }) | ||
.mockResolvedValueOnce({ value: 'scope_value' }); | ||
|
||
await handleEnvVariables(backendPath, modulePath); | ||
|
||
expect(logInfo).toHaveBeenCalledWith('Searching for missing environment variables...'); | ||
expect(logInfo).toHaveBeenCalledWith('Missing environment variables: GOOGLE_CALENDAR_CLIENT_ID, GOOGLE_CALENDAR_CLIENT_SECRET, REDIRECT_URI, GOOGLE_CALENDAR_SCOPE'); | ||
expect(inquirer.prompt).toHaveBeenCalledTimes(5); | ||
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'); | ||
expect(fs.writeFileSync).toHaveBeenCalledWith( | ||
localDevConfigPath, | ||
JSON.stringify({ | ||
GOOGLE_CALENDAR_CLIENT_ID: 'client_id_value', | ||
GOOGLE_CALENDAR_CLIENT_SECRET: 'client_secret_value', | ||
REDIRECT_URI: 'redirect_uri_value', | ||
GOOGLE_CALENDAR_SCOPE: 'scope_value' | ||
}, null, 2) | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#!/usr/bin/env node | ||
|
||
const { Command } = require('commander'); | ||
const { installCommand } = require('./installCommand'); | ||
|
||
const program = new Command(); | ||
program | ||
.command('install [apiModuleName]') | ||
.description('Install an API module') | ||
.action(installCommand); | ||
|
||
program.parse(process.argv); | ||
|
||
module.exports = { installCommand }; |
Oops, something went wrong.