Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"minimatch": "^5.1.2",
"moo": "^0.5.1",
"open": "^8.2.1",
"openai": "^4.11.0",
"openapi-diff": "^0.23.6",
"ora": "^5.4.1",
"parse-diff": "^0.11.1",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const ArchiveCommand = require('./cmds/archive/archive');
const RestoreCommand = require('./cmds/archive/restore');
const CompareCommand = require('./cmds/compare/compare');
const CompareReportCommand = require('./cmds/compare-report/compareReport');
const InstallGitHubAction = require('./cmds/install-github-action/install-github-action');
import UploadCommand from './cmds/upload';
import { default as sqlErrorLog } from './lib/sqlErrorLog';

Expand Down Expand Up @@ -233,6 +234,7 @@ yargs(process.argv.slice(2))
.command(RestoreCommand)
.command(CompareCommand)
.command(CompareReportCommand)
.command(InstallGitHubAction)
.strict()
.demandCommand()
.help().argv;
1 change: 0 additions & 1 deletion packages/cli/src/cmds/agentInstaller/installerUI.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import UI from '../userInteraction';
import chalk from 'chalk';
import { Answers } from 'inquirer';

export enum OverwriteOption {
USE_EXISTING = 'Use existing',
Expand Down
127 changes: 127 additions & 0 deletions packages/cli/src/cmds/install-github-action/install-github-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import yargs from 'yargs';
import { existsSync } from 'fs';
import { warn } from 'console';
import { OpenAI } from 'openai';
import assert from 'assert';
import { readFile } from 'fs/promises';
import { inspect } from 'util';
import * as diff from 'diff';

import { handleWorkingDirectory } from '../../lib/handleWorkingDirectory';
import { findFiles, verbose } from '../../utils';
import UI from '../userInteraction';

export const command = 'install-github-action';
export const describe = 'Install the AppMap GitHub action to a project';

export default async function buildOpenAIApi(): Promise<OpenAI | undefined> {
let gptKey = process.env.OPENAI_API_KEY;
if (!gptKey) {
const gptKey = UI.prompt({
message: 'Enter your OpenAI API key',
mask: '*',
});
if (!gptKey) return;
}

return new OpenAI({ apiKey: gptKey });
}

export const builder = (args: yargs.Argv) => {
args.option('directory', {
describe: 'program working directory',
type: 'string',
alias: 'd',
});

return args.strict();
};

async function installGitHubAction() {
if (!existsSync('.github')) {
warn(`.github directory does not exist`);
return;
}

const workflowFiles = await findFiles('.github', '.yml');
if (workflowFiles.length === 0) {
warn(`No workflows found in .github directory`);
return;
}

let workflowFile: string;
{
const answer = await UI.prompt({
type: 'list',
name: 'action',
message: 'Choose a workflow file:',
choices: workflowFiles,
});
if (!answer) return;

workflowFile = answer.action;
}
const workflowStr = await readFile(workflowFile, 'utf-8');

UI.status = `Suggesting AppMap installation to GitHub Workflow ${workflowFile}`;

const ai = await buildOpenAIApi();
if (!ai) return;

const snippet = `
- name: Prepare bundle for AppMap installation
run: bundle config unset deployment
- name: Install AppMap
id: install-appmap
uses: getappmap/install-action@v1
with:
build-tool: bundler
`

const result = await ai.chat.completions.create({
messages: [
{
role: 'system',
content: 'Respond with a complete copy of the workflow'
},
{
role: 'system',
content: 'Include two line breaks before and after the injected snippet'
},
{
role: 'user',
content:
'I want to add a step to the following GitHub Action workflow that runs after the language tools are installed, but before the tests are run',
},
{
role: 'user',
content: `Inject my workflow snippet which is: SNIPPET\n${snippet}END SNIPPET`
}
{
role: 'user',
content: workflowStr,
},
],
model: 'gpt-3.5-turbo',
});

const response = result.choices
.map((choice) => (assert(choice.message), choice.message.content))
.filter(Boolean)
.join('\n');

UI.success();

const patch = diff.createTwoFilesPatch(workflowFile, workflowFile, workflowStr, response);

warn(patch);
}

export const handler = async (argv: any) => {
verbose(argv.verbose);

const { directory } = argv;
handleWorkingDirectory(directory);

await installGitHubAction();
};
127 changes: 126 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ __metadata:
moo: ^0.5.1
node-fetch: 2.6.7
open: ^8.2.1
openai: ^4.11.0
openapi-diff: ^0.23.6
openapi-types: ^12.1.0
ora: ^5.4.1
Expand Down Expand Up @@ -8360,6 +8361,16 @@ __metadata:
languageName: node
linkType: hard

"@types/node-fetch@npm:^2.6.4":
version: 2.6.6
resolution: "@types/node-fetch@npm:2.6.6"
dependencies:
"@types/node": "*"
form-data: ^4.0.0
checksum: ac66389d9d597ab91f5e5d3724e594965b9f80ae5841ab5da9f0c3bd54ceac084591cfe69b1c413f18bb7efdd97002d05bd7651d58ba0c6c10f804f4fd85e598
languageName: node
linkType: hard

"@types/node@npm:*, @types/node@npm:^17.0.2":
version: 17.0.17
resolution: "@types/node@npm:17.0.17"
Expand Down Expand Up @@ -8395,6 +8406,13 @@ __metadata:
languageName: node
linkType: hard

"@types/node@npm:^18.11.18":
version: 18.18.1
resolution: "@types/node@npm:18.18.1"
checksum: 079085afc8615b91727900628410a2077f7fff51c5c3e20c4ab2d9caae4010b035aac13dae1221155e9d6ca46084aebf68121642140527b4009eee17b716d339
languageName: node
linkType: hard

"@types/normalize-package-data@npm:^2.4.0":
version: 2.4.1
resolution: "@types/normalize-package-data@npm:2.4.1"
Expand Down Expand Up @@ -10238,6 +10256,15 @@ __metadata:
languageName: node
linkType: hard

"abort-controller@npm:^3.0.0":
version: 3.0.0
resolution: "abort-controller@npm:3.0.0"
dependencies:
event-target-shim: ^5.0.0
checksum: 170bdba9b47b7e65906a28c8ce4f38a7a369d78e2271706f020849c1bfe0ee2067d4261df8bbb66eb84f79208fd5b710df759d64191db58cfba7ce8ef9c54b75
languageName: node
linkType: hard

"accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.7":
version: 1.3.8
resolution: "accepts@npm:1.3.8"
Expand Down Expand Up @@ -11951,6 +11978,13 @@ __metadata:
languageName: node
linkType: hard

"base-64@npm:^0.1.0":
version: 0.1.0
resolution: "base-64@npm:0.1.0"
checksum: 5a42938f82372ab5392cbacc85a5a78115cbbd9dbef9f7540fa47d78763a3a8bd7d598475f0d92341f66285afd377509851a9bb5c67bbecb89686e9255d5b3eb
languageName: node
linkType: hard

"base64-js@npm:^1.0.2, base64-js@npm:^1.3.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
Expand Down Expand Up @@ -13013,6 +13047,13 @@ __metadata:
languageName: node
linkType: hard

"charenc@npm:0.0.2":
version: 0.0.2
resolution: "charenc@npm:0.0.2"
checksum: 81dcadbe57e861d527faf6dd3855dc857395a1c4d6781f4847288ab23cffb7b3ee80d57c15bba7252ffe3e5e8019db767757ee7975663ad2ca0939bb8fcaf2e5
languageName: node
linkType: hard

"check-more-types@npm:2.24.0, check-more-types@npm:^2.24.0":
version: 2.24.0
resolution: "check-more-types@npm:2.24.0"
Expand Down Expand Up @@ -14264,6 +14305,13 @@ __metadata:
languageName: node
linkType: hard

"crypt@npm:0.0.2":
version: 0.0.2
resolution: "crypt@npm:0.0.2"
checksum: baf4c7bbe05df656ec230018af8cf7dbe8c14b36b98726939cef008d473f6fe7a4fad906cfea4062c93af516f1550a3f43ceb4d6615329612c6511378ed9fe34
languageName: node
linkType: hard

"crypto-browserify@npm:^3.11.0":
version: 3.12.0
resolution: "crypto-browserify@npm:3.12.0"
Expand Down Expand Up @@ -15654,6 +15702,16 @@ __metadata:
languageName: node
linkType: hard

"digest-fetch@npm:^1.3.0":
version: 1.3.0
resolution: "digest-fetch@npm:1.3.0"
dependencies:
base-64: ^0.1.0
md5: ^2.3.0
checksum: 8ebdb4b9ef02b1ac0da532d25c7d08388f2552813dfadabfe7c4630e944bb4a48093b997fc926440a10e1ccf4912f2ce9adcf2d6687b0518dab8480e08f22f9d
languageName: node
linkType: hard

"dir-glob@npm:^2.0.0, dir-glob@npm:^2.2.2":
version: 2.2.2
resolution: "dir-glob@npm:2.2.2"
Expand Down Expand Up @@ -17371,6 +17429,13 @@ __metadata:
languageName: node
linkType: hard

"event-target-shim@npm:^5.0.0":
version: 5.0.1
resolution: "event-target-shim@npm:5.0.1"
checksum: 1ffe3bb22a6d51bdeb6bf6f7cf97d2ff4a74b017ad12284cc9e6a279e727dc30a5de6bb613e5596ff4dc3e517841339ad09a7eec44266eccb1aa201a30448166
languageName: node
linkType: hard

"eventemitter2@npm:6.4.7":
version: 6.4.7
resolution: "eventemitter2@npm:6.4.7"
Expand Down Expand Up @@ -18249,6 +18314,13 @@ __metadata:
languageName: node
linkType: hard

"form-data-encoder@npm:1.7.2":
version: 1.7.2
resolution: "form-data-encoder@npm:1.7.2"
checksum: aeebd87a1cb009e13cbb5e4e4008e6202ed5f6551eb6d9582ba8a062005178907b90f4887899d3c993de879159b6c0c940af8196725b428b4248cec5af3acf5f
languageName: node
linkType: hard

"form-data@npm:*, form-data@npm:^4.0.0":
version: 4.0.0
resolution: "form-data@npm:4.0.0"
Expand Down Expand Up @@ -18289,6 +18361,16 @@ __metadata:
languageName: node
linkType: hard

"formdata-node@npm:^4.3.2":
version: 4.4.1
resolution: "formdata-node@npm:4.4.1"
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 4.0.0-beta.3
checksum: d91d4f667cfed74827fc281594102c0dabddd03c9f8b426fc97123eedbf73f5060ee43205d89284d6854e2fc5827e030cd352ef68b93beda8decc2d72128c576
languageName: node
linkType: hard

"forwarded@npm:0.2.0":
version: 0.2.0
resolution: "forwarded@npm:0.2.0"
Expand Down Expand Up @@ -20470,7 +20552,7 @@ __metadata:
languageName: node
linkType: hard

"is-buffer@npm:^1.1.5":
"is-buffer@npm:^1.1.5, is-buffer@npm:~1.1.6":
version: 1.1.6
resolution: "is-buffer@npm:1.1.6"
checksum: 4a186d995d8bbf9153b4bd9ff9fd04ae75068fe695d29025d25e592d9488911eeece84eefbd8fa41b8ddcc0711058a71d4c466dcf6f1f6e1d83830052d8ca707
Expand Down Expand Up @@ -25075,6 +25157,17 @@ __metadata:
languageName: node
linkType: hard

"md5@npm:^2.3.0":
version: 2.3.0
resolution: "md5@npm:2.3.0"
dependencies:
charenc: 0.0.2
crypt: 0.0.2
is-buffer: ~1.1.6
checksum: a63cacf4018dc9dee08c36e6f924a64ced735b37826116c905717c41cebeb41a522f7a526ba6ad578f9c80f02cb365033ccd67fe186ffbcc1a1faeb75daa9b6e
languageName: node
linkType: hard

"mdast-squeeze-paragraphs@npm:^4.0.0":
version: 4.0.0
resolution: "mdast-squeeze-paragraphs@npm:4.0.0"
Expand Down Expand Up @@ -26082,6 +26175,13 @@ __metadata:
languageName: node
linkType: hard

"node-domexception@npm:1.0.0":
version: 1.0.0
resolution: "node-domexception@npm:1.0.0"
checksum: ee1d37dd2a4eb26a8a92cd6b64dfc29caec72bff5e1ed9aba80c294f57a31ba4895a60fd48347cf17dd6e766da0ae87d75657dfd1f384ebfa60462c2283f5c7f
languageName: node
linkType: hard

"node-emoji@npm:^1.11.0":
version: 1.11.0
resolution: "node-emoji@npm:1.11.0"
Expand Down Expand Up @@ -27035,6 +27135,24 @@ __metadata:
languageName: node
linkType: hard

"openai@npm:^4.11.0":
version: 4.11.0
resolution: "openai@npm:4.11.0"
dependencies:
"@types/node": ^18.11.18
"@types/node-fetch": ^2.6.4
abort-controller: ^3.0.0
agentkeepalive: ^4.2.1
digest-fetch: ^1.3.0
form-data-encoder: 1.7.2
formdata-node: ^4.3.2
node-fetch: ^2.6.7
bin:
openai: bin/cli
checksum: 4a2609b29d47347f6c4ed03c35787baaa7145b34e57aecbf6b5412f4ac0479e9c46b5ce22d85a972604eef4c0933d8a09c46c2625de8fdad2bd53200888bb04a
languageName: node
linkType: hard

"openapi-diff@npm:^0.23.6":
version: 0.23.6
resolution: "openapi-diff@npm:0.23.6"
Expand Down Expand Up @@ -35167,6 +35285,13 @@ typescript@~4.4.3:
languageName: node
linkType: hard

"web-streams-polyfill@npm:4.0.0-beta.3":
version: 4.0.0-beta.3
resolution: "web-streams-polyfill@npm:4.0.0-beta.3"
checksum: dfec1fbf52b9140e4183a941e380487b6c3d5d3838dd1259be81506c1c9f2abfcf5aeb670aeeecfd9dff4271a6d8fef931b193c7bedfb42542a3b05ff36c0d16
languageName: node
linkType: hard

"webidl-conversions@npm:^3.0.0":
version: 3.0.1
resolution: "webidl-conversions@npm:3.0.1"
Expand Down