Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI: Adding new command 'configuration' #771

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { hideBin } from 'yargs/helpers';

import apolloCommand from './apollo/command';
import compileCommand from './compile/command';
import configurationCommand from './configuration/command';
import deployCommand from './deploy/command';
import docgenCommand from './docgen/command';
import docsCommand from './docs/command';
Expand All @@ -25,6 +26,7 @@ export const cmd = y
.command(testCommand)
.command(docsCommand)
.command(apolloCommand)
.command(configurationCommand)
.command(metadataCommand as any)
.command(docgenCommand as any)
.command(pullCommand as any)
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 @@ -2,6 +2,7 @@ import { Opts } from './options';
import apollo from './apollo/handler';
import execute from './execute/handler';
import compile from './compile/handler';
import configuration from './configuration/handler';
import test from './test/handler';
import deploy from './deploy/handler';
import docgen from './docgen/handler';
Expand All @@ -19,6 +20,7 @@ import printVersions from './util/print-versions';
export type CommandList =
| 'apollo'
| 'compile'
| 'configuration'
| 'deploy'
| 'docgen'
| 'docs'
Expand All @@ -36,6 +38,7 @@ const handlers = {
apollo,
execute,
compile,
configuration,
test,
deploy,
docgen,
Expand Down Expand Up @@ -84,7 +87,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|apollo)$/.test(options.command!) &&
!/^(pull|deploy|test|version|apollo|configuration)$/.test(options.command!) &&
!options.repoDir
) {
logger.warn(
Expand Down
37 changes: 37 additions & 0 deletions packages/cli/src/configuration/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import yargs from 'yargs';
import * as o from '../options';
import type { Opts } from '../options';
import { build, ensure, override } from '../util/command-builders';

export type ConfigOptions = Pick<
Opts,
'log' | 'logJson' | 'outputPath' | 'outputStdout' | 'configType'
> & {
adaptor: string;
};

const options = [
o.log,
o.logJson,
o.outputPath,
o.configType,
override(o.outputStdout, {
default: true,
}),
];

export default {
command: 'configuration <adaptor>',
handler: ensure('configuration', options),
describe: 'Returns the sample and full configuration of the adaptor. You can use flags (schema, sample or both(default)) to return only the sample or full configuration.',
builder: (yargs) =>
build(options, yargs)
.example(
'configuration adaptor_name@version',
'Returns the sample and full configuration of the adaptor for a given version.'
)
.example(
'configuration adaptor_name --sample',
'Returns only the sample configuration of the adaptor for the latest version.'
),
} as yargs.CommandModule<{}>;
125 changes: 125 additions & 0 deletions packages/cli/src/configuration/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { Logger } from '../util/logger';
import { ConfigOptions } from './command';

import { writeFile, mkdir } from 'node:fs/promises';
import path from 'node:path';

const configurationHandler = async (options: ConfigOptions, logger: Logger) => {
logger.always(`Retrieving configuration for: ${options.adaptor}`);

const { adaptor } = options;

try {
const configData = await getConfig(adaptor, logger);
const filteredConfigData = filterConfigData(configData, options.configType);

await serializeOutput(options, filteredConfigData, logger);

logger.success('Done!');
return filteredConfigData;
} catch (error: any) {
logger.error(`Failed to retrieve configuration`);
logger.error(error);
}
};

const getConfig = async (adaptor: string, logger: Logger) => {
// Fetch the configuration-schema.json file from the CDN
const configPath = `${adaptor}/configuration-schema.json`;

logger.always("Fetching configuration file...");
const configContent = await fetchFile(configPath, logger);
const fullSchema = JSON.parse(configContent);

// Extract required fields and their examples
const requiredFields = fullSchema.required || [];
const properties = fullSchema.properties || {};
const sampleConfig: Record<string, any> = {};

requiredFields.forEach((field: string) => {
const fieldInfo = properties[field];
if (fieldInfo && fieldInfo.examples && fieldInfo.examples.length > 0) {
sampleConfig[field] = fieldInfo.examples[0];
}
});

const configData = {
sample_config: sampleConfig,
full_schema: fullSchema,
};

return configData;
};

const filterConfigData = (configData: any, schemaOption: string | undefined) => {
switch (schemaOption) {
case 'sample':
return { sample_config: configData.sample_config };
case 'schema':
return { full_schema: configData.full_schema };
case 'both':
default:
return configData;
}
};

const write = async (
basePath: string,
filePath: string,
content: string,
logger: Logger
) => {
const ext = path.extname(basePath);
let dir;
if (ext) {
dir = path.dirname(path.resolve(basePath));
} else {
dir = basePath;
}

// Ensure the root dir exists
await mkdir(dir, { recursive: true });

const dest = path.resolve(basePath, filePath);
await writeFile(dest, content);

logger.success(`Wrote content to ${dest}`);
};

// Serialize output to file and stdout
const serializeOutput = async (
options: Pick<ConfigOptions, 'outputStdout' | 'outputPath'>,
result: any,
logger: Logger
) => {
/** Print to disk **/
if (options.outputPath) {
await write(
options.outputPath,
'',
JSON.stringify(result, null, 2),
logger
);
}

/** print to stdout **/
logger.success('Configuration Data:');
logger.always(JSON.stringify(result, undefined, 2));
};

async function fetchFile(path: string, logger: Logger) {
const resolvedPath = `https://cdn.jsdelivr.net/npm/${path}`;

logger.debug("Fetching configuration from: ", resolvedPath);
const response = await fetch(resolvedPath);

if (response.status === 200) {
return response.text();
}

throw new Error(
`Failed getting file at: ${path} got: ${response.status} ${response.statusText}`
);
}

export default configurationHandler;
29 changes: 28 additions & 1 deletion packages/cli/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type Opts = {
cacheSteps?: boolean;
compile?: boolean;
configPath?: string;
configType?: string;
confirm?: boolean;
describe?: string;
end?: string; // workflow end node
Expand Down Expand Up @@ -335,7 +336,7 @@ export const outputPath: CLIOption = {
},
ensure: (opts) => {
// TODO these command specific rules don't sit well here
if (/^(compile|apollo)$/.test(opts.command!)) {
if (/^(compile|apollo|configuration)$/.test(opts.command!)) {
if (opts.outputPath) {
// If a path is set, remove the stdout flag
delete opts.outputStdout;
Expand Down Expand Up @@ -477,3 +478,29 @@ export const sanitize: CLIOption = {
throw new Error(err);
},
};

export const configType: CLIOption = {
name: 'configType',
yargs: {
alias: ['config-type'],
description: 'Specify the schema to return - schema, sample, or both',
default: 'both',
},
ensure: (opts) => {
let didLoadShortcut = false;
['schema', 'sample', 'both'].forEach((shortcut) => {
if (shortcut in opts) {
if (didLoadShortcut) {
throw new Error(
'Invalid shortcut - please only enter one of sample, schema or both'
);
}

opts.configType = shortcut;
// @ts-ignore
delete opts[shortcut];
didLoadShortcut = true;
}
});
},
};
Loading
Loading