Skip to content

Commit

Permalink
cli: hook up a basic generate command
Browse files Browse the repository at this point in the history
  • Loading branch information
josephjclark committed Feb 21, 2024
1 parent d5a740b commit b353c8c
Show file tree
Hide file tree
Showing 5 changed files with 419 additions and 1 deletion.
2 changes: 2 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import deployCommand from './deploy/command';
import docgenCommand from './docgen/command';
import docsCommand from './docs/command';
import executeCommand from './execute/command';
import generateCommand from './generate/command';
import metadataCommand from './metadata/command';
import pullCommand from './pull/command';
import { Opts } from './options';
Expand All @@ -26,6 +27,7 @@ export const cmd = y
.command(metadataCommand as any)
.command(docgenCommand as any)
.command(pullCommand as any)
.command(generateCommand as any)
.command({
command: 'version',
describe:
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import test from './test/handler';
import deploy from './deploy/handler';
import docgen from './docgen/handler';
import docs from './docs/handler';
import generateAdaptor from './generate/adaptor';
import metadata from './metadata/handler';
import pull from './pull/handler';
import { clean, install, pwd, list } from './repo/handler';
Expand All @@ -21,6 +22,7 @@ export type CommandList =
| 'docgen'
| 'docs'
| 'execute'
| 'generate'
| 'metadata'
| 'pull'
| 'repo-clean'
Expand All @@ -37,6 +39,7 @@ const handlers = {
deploy,
docgen,
docs,
['generate-adaptor']: generateAdaptor,
metadata,
pull,
['repo-clean']: clean,
Expand Down Expand Up @@ -81,7 +84,7 @@ const parse = async (options: Opts, log?: Logger) => {
// TODO it would be nice to do this in the repoDir option, but
// the logger isn't available yet
if (
!/^(pull|deploy|test|version)$/.test(options.command!) &&
!/^(pull|deploy|test|version|generate-adaptor)$/.test(options.command!) &&
!options.repoDir
) {
logger.warn(
Expand Down
115 changes: 115 additions & 0 deletions packages/cli/src/generate/adaptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Handler to generate adaptor code
*/

import { Opts } from '../options';
import { Logger } from '../util';
import mailchimp from './mailchimp-spec.json' assert { type: 'json' };

// TODO: really I just want one domain here
const endpoints = {
signature: 'http://localhost:8001/generate_signature',
code: 'http://localhost:8002/generate_code/',
};

type AdaptorGenOptions = Pick<
Opts,
| 'command'
| 'path' // path to spec - we proably want to override the description
| 'log' // same log rules
| 'logJson'
| 'monorepoPath' // maybe use the monorepo (or use env var)
| 'outputPath' // where to output to. Defaults to monorepo or as sibling of the spec
> & {
adaptor: string;

// TODO spec overrides
};

// spec.spec is silly, so what is this object?
type Spec = {
spec: any; // OpenAPI spec

instruction: string; // for now... but we'll use endpoints later

endpoints?: string[]; // TODO not supported yet

model?: string; // TODO not supported yet
};

const generateAdaptor = async (opts: any, logger: Logger) => {
logger.success('** GENERATE ADAPTOR**');
// if we're using the monorepo, and no adaptor with this name exists
// prompt to generate it
// humm is that worth it? it'll create a git diff anyway

// TODO load spec from path
// gonna hard code it right now
const spec = {
spec: mailchimp, // post as open_api_spec
instruction: 'Create an OpenFn function that accesses the /goals endpoint',
};

const sig = await generateSignature(spec, logger);
const code = await generateCode(spec, sig, logger);

// Now we need to output to disk

return { sig, code };
};

export default generateAdaptor;

// throw if the spec is missing anything
const validateSpec = () => {};

// this will generate a basic package for the adaptor
// what is a good way to do this?
const generatePackageTemplate = () => {
// package json
// src
// Adaptor.js
// index.js
// readme.md
};

const convertSpec = (spec: Spec) =>
JSON.stringify({
open_api_spec: spec.spec,
instruction: spec.instruction,
model: 'gpt3_turbo',
});

const generateSignature = async (spec: Spec, logger: Logger) => {
// generate signature
const result = await fetch(endpoints.signature, {
method: 'POST',
body: convertSpec(spec),
headers: {
['Content-Type']: 'application/json',
},
});
const json = await result.json();
logger.success('Generated signature:\n', json.signature);

// TODO write output

return json.signature;
};

const generateCode = async (spec: Spec, signature: string, logger: Logger) => {
const result = await fetch(endpoints.code, {
method: 'POST',
body: JSON.stringify({
// TODO why doesn't code gen use the spec??
signature,
model: 'gpt3_turbo',
}),
headers: {
['Content-Type']: 'application/json',
},
});
const json = await result.json();
logger.success('Generated code:\n', json.implementation);
return json.implementation;
};
49 changes: 49 additions & 0 deletions packages/cli/src/generate/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import yargs from 'yargs';
import * as o from '../options';
import type { Opts } from '../options';
import { build, ensure } from '../util/command-builders';

export type TestOptions = Pick<Opts, 'stateStdin' | 'log' | 'logJson'>;

const options = [o.log, o.logJson, o.useAdaptorsMonorepo, o.outputPath];

options.push({
name: 'adaptor',
yargs: {
description: 'The name of the adaptor to generate',
},
} as o.CLIOption);

// Adaptor generation subcommand
const adaptor = {
command: 'adaptor',
desc: 'Generate adaptor code',
handler: ensure('generate-adaptor', options),
builder: (yargs) =>
build(options, yargs)
.example(
'generate adaptor ./spec.json',
'Generate adaptor code based on spec.json'
)
.positional('path', {
describe: 'The path spec.json',
demandOption: true,
}),
} as yargs.CommandModule<{}>;

export default {
command: 'generate [subcommand]',
desc: 'Generate code (only adaptors supported now)',
handler: () => {
// TODO: better error handling
console.error('ERROR: invalid command');
console.error('Try:\n\n openfn generate adaptor\n');
},
builder: (yargs: yargs.Argv) =>
yargs
.command(adaptor)
.example(
'generate adaptor ./spec.json',
'Generate adaptor code based on spec.json'
),
} as yargs.CommandModule<{}>;
Loading

0 comments on commit b353c8c

Please sign in to comment.