diff --git a/README.md b/README.md index 3395f8be5..c622a2961 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ Additional recipes for configuring and deploying the server can be found at [sol | `--loggingLevel, -l` | `"info"`| | | `--rootFilePath, -f` | `"./"` | Folder to start the server in when using a file-based config. | | `--sparqlEndpoint, -s` | | Endpoint to call when using a SPARQL-based config. | +| `--showStackTrace, -t` | false | Whether error stack traces should be shown in responses. | | `--podConfigJson` | `"./pod-config.json"` | JSON file to store pod configuration when using a dynamic config. | | `--idpTemplateFolder` | `"templates/idp"` | Folder containing the templates used for IDP interactions. | diff --git a/src/init/AppRunner.ts b/src/init/AppRunner.ts index 3ab37f344..77401794c 100644 --- a/src/init/AppRunner.ts +++ b/src/init/AppRunner.ts @@ -44,41 +44,32 @@ export class AppRunner { } = {}): void { // Parse the command-line arguments const { argv: params } = yargs(argv.slice(2)) + .strict() .usage('node ./bin/server.js [args]') - .check((args, options): boolean => { - // Only take flags as arguments, not filenames - if (args._ && args._.length > 0) { - throw new Error(`Unsupported arguments: ${args._.join('", "')}`); + .check((args): boolean => { + if (args._.length > 0) { + throw new Error(`Unsupported positional arguments: "${args._.join('", "')}"`); } - for (const key in args) { - // Skip filename arguments (_) and the script name ($0) - if (key !== '_' && key !== '$0') { - // Check if the argument occurs in the provided options list - if (!options[key]) { - throw new Error(`Unknown option: "${key}"`); - } - // Check if the argument actually has a value ('> ./bin/server.js -s' is not valid) - if (!args[key]) { - throw new Error(`Missing value for argument "${key}"`); - } - // Check if the argument only has 1 value - if (Array.isArray(args[key])) { - throw new Error(`Multiple values were provided for: "${key}", [${args[key]}]`); - } + for (const key of Object.keys(args)) { + // We have no options that allow for arrays + const val = args[key]; + if (key !== '_' && Array.isArray(val)) { + throw new Error(`Multiple values were provided for: "${key}": "${val.join('", "')}"`); } } return true; }) .options({ - baseUrl: { type: 'string', alias: 'b' }, - config: { type: 'string', alias: 'c' }, - loggingLevel: { type: 'string', alias: 'l', default: 'info' }, - mainModulePath: { type: 'string', alias: 'm' }, - idpTemplateFolder: { type: 'string' }, - port: { type: 'number', alias: 'p', default: 3000 }, - rootFilePath: { type: 'string', alias: 'f', default: './' }, - sparqlEndpoint: { type: 'string', alias: 's' }, - podConfigJson: { type: 'string', default: './pod-config.json' }, + baseUrl: { type: 'string', alias: 'b', requiresArg: true }, + config: { type: 'string', alias: 'c', requiresArg: true }, + loggingLevel: { type: 'string', alias: 'l', default: 'info', requiresArg: true }, + mainModulePath: { type: 'string', alias: 'm', requiresArg: true }, + idpTemplateFolder: { type: 'string', requiresArg: true }, + port: { type: 'number', alias: 'p', default: 3000, requiresArg: true }, + rootFilePath: { type: 'string', alias: 'f', default: './', requiresArg: true }, + showStackTrace: { type: 'boolean', alias: 't', default: false }, + sparqlEndpoint: { type: 'string', alias: 's', requiresArg: true }, + podConfigJson: { type: 'string', default: './pod-config.json', requiresArg: true }, }) .help(); @@ -129,6 +120,7 @@ export class AppRunner { 'urn:solid-server:default:variable:rootFilePath': this.resolveFilePath(params.rootFilePath), 'urn:solid-server:default:variable:sparqlEndpoint': params.sparqlEndpoint, + 'urn:solid-server:default:variable:showStackTrace': params.showStackTrace, 'urn:solid-server:default:variable:podConfigJson': this.resolveFilePath(params.podConfigJson), 'urn:solid-server:default:variable:idpTemplateFolder': @@ -158,6 +150,7 @@ export interface ConfigVariables { baseUrl?: string; rootFilePath?: string; sparqlEndpoint?: string; + showStackTrace?: boolean; podConfigJson?: string; idpTemplateFolder?: string; } diff --git a/test/integration/DynamicPods.test.ts b/test/integration/DynamicPods.test.ts index 09defde89..7ed55ed8f 100644 --- a/test/integration/DynamicPods.test.ts +++ b/test/integration/DynamicPods.test.ts @@ -38,6 +38,7 @@ describe.each(configs)('A dynamic pod server with template config %s', (template 'urn:solid-server:default:variable:baseUrl': baseUrl, 'urn:solid-server:default:variable:rootFilePath': rootFilePath, 'urn:solid-server:default:variable:podConfigJson': podConfigJson, + 'urn:solid-server:default:variable:showStackTrace': true, 'urn:solid-server:default:variable:idpTemplateFolder': joinFilePath(__dirname, '../../templates/idp'), }; diff --git a/test/integration/Identity.test.ts b/test/integration/Identity.test.ts index fb795a161..49d48ca44 100644 --- a/test/integration/Identity.test.ts +++ b/test/integration/Identity.test.ts @@ -63,6 +63,7 @@ describe('A Solid server with IDP', (): void => { getTestConfigPath('server-memory.json'), { 'urn:solid-server:default:variable:baseUrl': baseUrl, + 'urn:solid-server:default:variable:showStackTrace': true, 'urn:solid-server:default:variable:idpTemplateFolder': joinFilePath(__dirname, '../../templates/idp'), }, ) as Record; diff --git a/test/integration/LdpHandlerWithAuth.test.ts b/test/integration/LdpHandlerWithAuth.test.ts index a44692a98..05c4eb1d1 100644 --- a/test/integration/LdpHandlerWithAuth.test.ts +++ b/test/integration/LdpHandlerWithAuth.test.ts @@ -40,6 +40,7 @@ describe.each(stores)('An LDP handler with auth using %s', (name, { storeConfig, beforeAll(async(): Promise => { const variables: Record = { 'urn:solid-server:default:variable:baseUrl': baseUrl, + 'urn:solid-server:default:variable:showStackTrace': true, 'urn:solid-server:default:variable:rootFilePath': rootFilePath, }; diff --git a/test/integration/LdpHandlerWithoutAuth.test.ts b/test/integration/LdpHandlerWithoutAuth.test.ts index 009c7c5f0..e2249434f 100644 --- a/test/integration/LdpHandlerWithoutAuth.test.ts +++ b/test/integration/LdpHandlerWithoutAuth.test.ts @@ -34,6 +34,7 @@ describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeC beforeAll(async(): Promise => { const variables: Record = { 'urn:solid-server:default:variable:baseUrl': baseUrl, + 'urn:solid-server:default:variable:showStackTrace': true, 'urn:solid-server:default:variable:rootFilePath': rootFilePath, }; diff --git a/test/integration/Middleware.test.ts b/test/integration/Middleware.test.ts index 9410cc7f0..9d7feb393 100644 --- a/test/integration/Middleware.test.ts +++ b/test/integration/Middleware.test.ts @@ -25,6 +25,7 @@ describe('An http server with middleware', (): void => { { 'urn:solid-server:default:LdpHandler': new SimpleHttpHandler(), 'urn:solid-server:default:variable:baseUrl': 'https://example.pod/', + 'urn:solid-server:default:variable:showStackTrace': true, }, ) as BaseHttpServerFactory; server = factory.startServer(port); diff --git a/test/integration/RedisResourceLockerIntegration.test.ts b/test/integration/RedisResourceLockerIntegration.test.ts index 7c5c71584..d31a714b7 100644 --- a/test/integration/RedisResourceLockerIntegration.test.ts +++ b/test/integration/RedisResourceLockerIntegration.test.ts @@ -21,6 +21,7 @@ describeIf('docker', 'A server with a RedisResourceLocker as ResourceLocker', () getTestConfigPath('run-with-redlock.json'), { 'urn:solid-server:default:variable:baseUrl': baseUrl, + 'urn:solid-server:default:variable:showStackTrace': true, }, ) as Record; ({ factory, locker } = instances); diff --git a/test/integration/ServerFetch.test.ts b/test/integration/ServerFetch.test.ts index c91b87e1f..6c6df074b 100644 --- a/test/integration/ServerFetch.test.ts +++ b/test/integration/ServerFetch.test.ts @@ -22,6 +22,7 @@ describe('A Solid server', (): void => { getTestConfigPath('server-memory.json'), { 'urn:solid-server:default:variable:baseUrl': baseUrl, + 'urn:solid-server:default:variable:showStackTrace': true, 'urn:solid-server:default:variable:idpTemplateFolder': '', }, ) as Record; diff --git a/test/integration/SparqlStorage.test.ts b/test/integration/SparqlStorage.test.ts index 490433b98..fdc3047a6 100644 --- a/test/integration/SparqlStorage.test.ts +++ b/test/integration/SparqlStorage.test.ts @@ -18,6 +18,7 @@ describeIf('docker', 'A server with a SPARQL endpoint as storage', (): void => { beforeAll(async(): Promise => { const variables: Record = { 'urn:solid-server:default:variable:baseUrl': baseUrl, + 'urn:solid-server:default:variable:showStackTrace': true, 'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:4000/sparql', }; diff --git a/test/integration/Subdomains.test.ts b/test/integration/Subdomains.test.ts index c1b4b2d1e..4283a9a57 100644 --- a/test/integration/Subdomains.test.ts +++ b/test/integration/Subdomains.test.ts @@ -37,6 +37,7 @@ describe.each(stores)('A subdomain server with %s', (name, { storeConfig, teardo const variables: Record = { 'urn:solid-server:default:variable:baseUrl': baseUrl, 'urn:solid-server:default:variable:rootFilePath': rootFilePath, + 'urn:solid-server:default:variable:showStackTrace': true, 'urn:solid-server:default:variable:idpTemplateFolder': joinFilePath(__dirname, '../../templates/idp'), }; diff --git a/test/integration/WebSocketsProtocol.test.ts b/test/integration/WebSocketsProtocol.test.ts index 17cdeefdd..343bf23ea 100644 --- a/test/integration/WebSocketsProtocol.test.ts +++ b/test/integration/WebSocketsProtocol.test.ts @@ -18,6 +18,7 @@ describe('A server with the Solid WebSockets API behind a proxy', (): void => { getTestConfigPath('server-without-auth.json'), { 'urn:solid-server:default:variable:baseUrl': 'https://example.pod/', + 'urn:solid-server:default:variable:showStackTrace': true, }, ) as HttpServerFactory; server = factory.startServer(port); diff --git a/test/unit/init/AppRunner.test.ts b/test/unit/init/AppRunner.test.ts index 7b5b1af50..af8288dc2 100644 --- a/test/unit/init/AppRunner.test.ts +++ b/test/unit/init/AppRunner.test.ts @@ -44,6 +44,7 @@ describe('AppRunner', (): void => { port: 3000, loggingLevel: 'info', rootFilePath: '/var/cwd/', + showStackTrace: false, podConfigJson: '/var/cwd/pod-config.json', }, ); @@ -67,6 +68,7 @@ describe('AppRunner', (): void => { 'urn:solid-server:default:variable:rootFilePath': '/var/cwd/', 'urn:solid-server:default:variable:sparqlEndpoint': undefined, 'urn:solid-server:default:variable:loggingLevel': 'info', + 'urn:solid-server:default:variable:showStackTrace': false, 'urn:solid-server:default:variable:podConfigJson': '/var/cwd/pod-config.json', 'urn:solid-server:default:variable:idpTemplateFolder': joinFilePath(__dirname, '../../../templates/idp'), }, @@ -107,6 +109,7 @@ describe('AppRunner', (): void => { 'urn:solid-server:default:variable:rootFilePath': '/var/cwd/', 'urn:solid-server:default:variable:sparqlEndpoint': undefined, 'urn:solid-server:default:variable:loggingLevel': 'info', + 'urn:solid-server:default:variable:showStackTrace': false, 'urn:solid-server:default:variable:podConfigJson': '/var/cwd/pod-config.json', 'urn:solid-server:default:variable:idpTemplateFolder': joinFilePath(__dirname, '../../../templates/idp'), }, @@ -127,6 +130,7 @@ describe('AppRunner', (): void => { '-m', 'module/path', '-p', '4000', '-s', 'http://localhost:5000/sparql', + '-t', '--podConfigJson', '/different-path.json', '--idpTemplateFolder', 'templates/idp', ], @@ -154,6 +158,7 @@ describe('AppRunner', (): void => { 'urn:solid-server:default:variable:port': 4000, 'urn:solid-server:default:variable:rootFilePath': '/root', 'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:5000/sparql', + 'urn:solid-server:default:variable:showStackTrace': true, 'urn:solid-server:default:variable:podConfigJson': '/different-path.json', 'urn:solid-server:default:variable:idpTemplateFolder': '/var/cwd/templates/idp', }, @@ -172,6 +177,7 @@ describe('AppRunner', (): void => { '--port', '4000', '--rootFilePath', 'root', '--sparqlEndpoint', 'http://localhost:5000/sparql', + '--showStackTrace', '--podConfigJson', '/different-path.json', '--idpTemplateFolder', 'templates/idp', ], @@ -199,6 +205,7 @@ describe('AppRunner', (): void => { 'urn:solid-server:default:variable:port': 4000, 'urn:solid-server:default:variable:rootFilePath': '/var/cwd/root', 'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:5000/sparql', + 'urn:solid-server:default:variable:showStackTrace': true, 'urn:solid-server:default:variable:podConfigJson': '/different-path.json', 'urn:solid-server:default:variable:idpTemplateFolder': '/var/cwd/templates/idp', }, @@ -217,6 +224,7 @@ describe('AppRunner', (): void => { '-m', 'module/path', '-p', '4000', '-s', 'http://localhost:5000/sparql', + '-t', '--podConfigJson', '/different-path.json', '--idpTemplateFolder', 'templates/idp', ]; @@ -245,6 +253,7 @@ describe('AppRunner', (): void => { 'urn:solid-server:default:variable:port': 4000, 'urn:solid-server:default:variable:rootFilePath': '/root', 'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:5000/sparql', + 'urn:solid-server:default:variable:showStackTrace': true, 'urn:solid-server:default:variable:podConfigJson': '/different-path.json', 'urn:solid-server:default:variable:idpTemplateFolder': '/var/cwd/templates/idp', }, @@ -293,9 +302,7 @@ describe('AppRunner', (): void => { it('exits when unknown options are passed to the main executable.', async(): Promise => { new AppRunner().runCli({ - argv: [ - 'node', 'script', '--foo', - ], + argv: [ 'node', 'script', '--foo' ], }); // Wait until initializer has been called, because we can't await AppRunner.run. @@ -303,16 +310,14 @@ describe('AppRunner', (): void => { setImmediate(resolve); }); - expect(error).toHaveBeenCalledWith('Unknown option: "foo"'); + expect(error).toHaveBeenCalledWith('Unknown argument: foo'); expect(exit).toHaveBeenCalledTimes(1); expect(exit).toHaveBeenCalledWith(1); }); it('exits when no value is passed to the main executable for an argument.', async(): Promise => { new AppRunner().runCli({ - argv: [ - 'node', 'script', '-s', - ], + argv: [ 'node', 'script', '-s' ], }); // Wait until initializer has been called, because we can't await AppRunner.run. @@ -320,16 +325,14 @@ describe('AppRunner', (): void => { setImmediate(resolve); }); - expect(error).toHaveBeenCalledWith('Missing value for argument "s"'); + expect(error).toHaveBeenCalledWith('Not enough arguments following: s'); expect(exit).toHaveBeenCalledTimes(1); expect(exit).toHaveBeenCalledWith(1); }); it('exits when unknown parameters are passed to the main executable.', async(): Promise => { new AppRunner().runCli({ - argv: [ - 'node', 'script', 'foo', 'bar', 'foo.txt', 'bar.txt', - ], + argv: [ 'node', 'script', 'foo', 'bar', 'foo.txt', 'bar.txt' ], }); // Wait until initializer has been called, because we can't await AppRunner.run. @@ -337,17 +340,14 @@ describe('AppRunner', (): void => { setImmediate(resolve); }); - // There seems to be an issue with yargs where the first and last `"` are missing. - expect(error).toHaveBeenCalledWith('Unsupported arguments: foo", "bar", "foo.txt", "bar.txt'); + expect(error).toHaveBeenCalledWith('Unsupported positional arguments: "foo", "bar", "foo.txt", "bar.txt"'); expect(exit).toHaveBeenCalledTimes(1); expect(exit).toHaveBeenCalledWith(1); }); it('exits when multiple values for a parameter are passed.', async(): Promise => { new AppRunner().runCli({ - argv: [ - 'node', 'script', '-ll', - ], + argv: [ 'node', 'script', '-l', 'info', '-l', 'debug' ], }); // Wait until initializer has been called, because we can't await AppRunner.run. @@ -355,7 +355,7 @@ describe('AppRunner', (): void => { setImmediate(resolve); }); - expect(error).toHaveBeenCalledWith('Multiple values were provided for: "l", [info,info]'); + expect(error).toHaveBeenCalledWith('Multiple values were provided for: "l": "info", "debug"'); expect(exit).toHaveBeenCalledTimes(1); expect(exit).toHaveBeenCalledWith(1); });