fix: Change YargsCliExtractor structure to avoid Components.js issues

This commit is contained in:
Joachim Van Herwegen 2022-04-21 10:54:06 +02:00
parent 1de1f7c12a
commit 6f4e70dbb9
6 changed files with 148 additions and 96 deletions

View File

@ -23,5 +23,5 @@
"VariableBindings",
"UnionHandler",
"WinstonLogger",
"YargsArgOptions"
"YargsOptions"
]

View File

@ -5,65 +5,105 @@
"comment": "Extracts CLI arguments into a key/value object. Config and mainModulePath are only defined here so their description is returned.",
"@id": "urn:solid-server-app-setup:default:CliExtractor",
"@type": "YargsCliExtractor",
"parameters": {
"config": {
"parameters": [
{
"@type": "YargsParameter",
"name": "config",
"options": {
"alias": "c",
"requiresArg": true,
"type": "string",
"describe": "The configuration for the server. The default only stores data in memory; to persist to your filesystem, use @css:config/file.json."
}
},
"mainModulePath": {
{
"@type": "YargsParameter",
"name": "mainModulePath",
"options": {
"alias": "m",
"requiresArg": true,
"type": "string",
"describe": "Path from where Components.js will start its lookup when initializing configurations."
}
},
"loggingLevel": {
{
"@type": "YargsParameter",
"name": "loggingLevel",
"options": {
"alias": "l",
"requiresArg": true,
"type": "string",
"describe": "The detail level of logging; useful for debugging problems."
}
},
"baseUrl": {
{
"@type": "YargsParameter",
"name": "baseUrl",
"options": {
"alias": "b",
"requiresArg": true,
"type": "string",
"describe": "The public URL of your server."
}
},
"port": {
{
"@type": "YargsParameter",
"name": "port",
"options": {
"alias": "p",
"requiresArg": true,
"type": "number",
"describe": "The TCP port on which the server runs."
}
},
"rootFilePath": {
{
"@type": "YargsParameter",
"name": "rootFilePath",
"options": {
"alias": "f",
"requiresArg": true,
"type": "string",
"describe": "Root folder of the server, when using a file-based configuration."
}
},
"showStackTrace": {
{
"@type": "YargsParameter",
"name": "showStackTrace",
"options": {
"alias": "t",
"type": "boolean",
"describe": "Enables detailed logging on error pages."
}
},
"sparqlEndpoint": {
{
"@type": "YargsParameter",
"name": "sparqlEndpoint",
"options": {
"alias": "s",
"requiresArg": true,
"type": "string",
"describe": "URL of the SPARQL endpoint, when using a quadstore-based configuration."
}
},
"podConfigJson": {
{
"@type": "YargsParameter",
"name": "podConfigJson",
"options": {
"requiresArg": true,
"type": "string",
"describe": "Path to the file that keeps track of dynamic Pod configurations."
}
},
"seededPodConfigJson": {
{
"@type": "YargsParameter",
"name": "seededPodConfigJson",
"options": {
"requiresArg": true,
"type": "string",
"describe": "Path to the file that will be used to seed pods."
}
},
}
],
"options": {
"usage": "node ./bin/server.js [args]"
}

View File

@ -42,20 +42,28 @@
{
"@id": "urn:solid-server-app-setup:default:CliExtractor",
"@type": "YargsCliExtractor",
"extendedParameters": {
"httpsKey": {
"parameters": [
{
"@type": "YargsParameter",
"name": "httpsKey",
"options": {
"demandOption": true,
"requiresArg": true,
"type": "string",
"describe": "File path to the HTTPS key."
}
},
"httpsCert": {
{
"@type": "YargsParameter",
"name": "httpsCert",
"options": {
"demandOption": true,
"requiresArg": true,
"type": "string",
"describe": "File path to the HTTPS certificate."
}
}
]
},
{
"comment": "Adds resolvers to assign the CLI values to the Components.js variables.",

View File

@ -15,7 +15,7 @@ export class FixedInteractionHandler extends InteractionHandler {
/**
* @param response - @range {json}
*/
public constructor(response: unknown) {
public constructor(response: Record<string, unknown>) {
super();
this.response = JSON.stringify(response);
}

View File

@ -3,7 +3,28 @@ import type { Arguments, Argv, Options } from 'yargs';
import yargs from 'yargs';
import { CliExtractor } from './CliExtractor';
export type YargsArgOptions = Record<string, Options>;
// This type exists to prevent Components.js from erroring on an unknown type
export type YargsOptions = Options;
/**
* This class exists as wrapper around a yargs Options object,
* thereby allowing us to create these in a Components.js configuration.
*
* Format details can be found at https://yargs.js.org/docs/#api-reference-optionskey-opt
*/
export class YargsParameter {
public readonly name: string;
public readonly options: YargsOptions;
/**
* @param name - Name of the parameter. Corresponds to the first parameter passed to the `yargs.options` function.
* @param options - Options for a single parameter that should be parsed. @range {json}
*/
public constructor(name: string, options: Record<string, any>) {
this.name = name;
this.options = options;
}
}
export interface CliOptions {
// Usage string printed in case of CLI errors
@ -24,21 +45,21 @@ export interface CliOptions {
* Specific settings can be enabled through the provided options.
*/
export class YargsCliExtractor extends CliExtractor {
protected readonly yargsArgOptions: YargsArgOptions;
protected readonly yargsArgOptions: Record<string, YargsOptions>;
protected readonly yargvOptions: CliOptions;
/**
* @param parameters - Parameters that should be parsed from the CLI. @range {json}
* Format details can be found at https://yargs.js.org/docs/#api-reference-optionskey-opt
* @param parameters - Parameters that should be parsed from the CLI.
* @param options - Additional options to configure yargs. @range {json}
* @param extendedParameters - The same as @parameters. Separate variable so in Components.js
* we can have both a default set and a user-added version. @range {json}
*
* JSON parameters cannot be optional due to https://github.com/LinkedSoftwareDependencies/Components-Generator.js/issues/87
*/
public constructor(parameters: YargsArgOptions = {}, options: CliOptions = {},
extendedParameters: YargsArgOptions = {}) {
public constructor(parameters: YargsParameter[], options: CliOptions) {
super();
this.yargsArgOptions = { ...parameters, ...extendedParameters };
this.yargvOptions = options;
this.yargsArgOptions = Object.fromEntries(
parameters.map((entry): [string, YargsOptions] => [ entry.name, entry.options ]),
);
this.yargvOptions = { ...options };
}
public async handle(argv: readonly string[]): Promise<Arguments> {

View File

@ -1,19 +1,18 @@
import type { YargsArgOptions } from '../../../../src/init/cli/YargsCliExtractor';
import { YargsCliExtractor } from '../../../../src/init/cli/YargsCliExtractor';
import { YargsParameter, YargsCliExtractor } from '../../../../src/init/cli/YargsCliExtractor';
const error = jest.spyOn(console, 'error').mockImplementation(jest.fn());
const log = jest.spyOn(console, 'log').mockImplementation(jest.fn());
const exit = jest.spyOn(process, 'exit').mockImplementation(jest.fn() as any);
describe('A YargsCliExtractor', (): void => {
const parameters: YargsArgOptions = {
baseUrl: { alias: 'b', requiresArg: true, type: 'string' },
port: { alias: 'p', requiresArg: true, type: 'number' },
};
const parameters: YargsParameter[] = [
new YargsParameter('baseUrl', { alias: 'b', requiresArg: true, type: 'string' }),
new YargsParameter('port', { alias: 'p', requiresArg: true, type: 'number' }),
];
let extractor: YargsCliExtractor;
beforeEach(async(): Promise<void> => {
extractor = new YargsCliExtractor(parameters);
extractor = new YargsCliExtractor(parameters, {});
});
afterEach(async(): Promise<void> => {
@ -36,22 +35,6 @@ describe('A YargsCliExtractor', (): void => {
}));
});
it('defaults to no parameters if none are provided.', async(): Promise<void> => {
extractor = new YargsCliExtractor();
const argv = [ 'node', 'script', '-b', 'http://localhost:3000/', '-p', '3000' ];
await expect(extractor.handle(argv)).resolves.toEqual(expect.objectContaining({}));
});
it('combines parameters and extra parameters.', async(): Promise<void> => {
extractor = new YargsCliExtractor(parameters, {}, { test: { alias: 't', requiresArg: true, type: 'string' }});
const argv = [ 'node', 'script', '-b', 'http://localhost:3000/', '-p', '3000', '-t', 'test' ];
await expect(extractor.handle(argv)).resolves.toEqual(expect.objectContaining({
baseUrl: 'http://localhost:3000/',
port: 3000,
test: 'test',
}));
});
it('prints usage if defined.', async(): Promise<void> => {
extractor = new YargsCliExtractor(parameters, { usage: 'node ./bin/server.js [args]' });
const argv = [ 'node', 'script', '--help' ];